@claude-flow/shared 3.0.0-alpha.1 → 3.0.0-alpha.8

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 (241) hide show
  1. package/.claude-flow/daemon-state.json +135 -0
  2. package/.claude-flow/data/pending-insights.jsonl +2 -0
  3. package/.claude-flow/data/ranked-context.json +5 -0
  4. package/.claude-flow/logs/daemon.log +45 -0
  5. package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_prompt.log +3210 -0
  6. package/.claude-flow/logs/headless/audit_1777379186972_h5un5x_result.log +117 -0
  7. package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_prompt.log +3210 -0
  8. package/.claude-flow/logs/headless/audit_1777379816437_w0eaul_result.log +53 -0
  9. package/.claude-flow/logs/headless/audit_1777380440097_621y8m_prompt.log +3210 -0
  10. package/.claude-flow/logs/headless/audit_1777380440097_621y8m_result.log +75 -0
  11. package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_prompt.log +3504 -0
  12. package/.claude-flow/logs/headless/optimize_1777379306973_an4lmy_result.log +166 -0
  13. package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_prompt.log +3504 -0
  14. package/.claude-flow/logs/headless/optimize_1777380274732_apxz3s_result.log +219 -0
  15. package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_prompt.log +3189 -0
  16. package/.claude-flow/logs/headless/testgaps_1777379546969_dvf2a1_result.log +155 -0
  17. package/.claude-flow/metrics/codebase-map.json +11 -0
  18. package/.claude-flow/metrics/consolidation.json +6 -0
  19. package/.claude-flow/sessions/current.json +13 -0
  20. package/.swarm/hnsw.index +0 -0
  21. package/.swarm/hnsw.metadata.json +1 -0
  22. package/.swarm/memory.db +0 -0
  23. package/.swarm/memory.db-shm +0 -0
  24. package/.swarm/memory.db-wal +0 -0
  25. package/.swarm/schema.sql +305 -0
  26. package/dist/core/config/loader.d.ts.map +1 -1
  27. package/dist/core/config/loader.js +17 -1
  28. package/dist/core/config/loader.js.map +1 -1
  29. package/dist/core/config/schema.d.ts +697 -103
  30. package/dist/core/config/schema.d.ts.map +1 -1
  31. package/dist/core/config/schema.js +3 -1
  32. package/dist/core/config/schema.js.map +1 -1
  33. package/dist/events/event-store.d.ts.map +1 -1
  34. package/dist/events/event-store.js +20 -9
  35. package/dist/events/event-store.js.map +1 -1
  36. package/dist/events/example-usage.js +1 -1
  37. package/dist/events/example-usage.js.map +1 -1
  38. package/dist/events/index.d.ts +2 -0
  39. package/dist/events/index.d.ts.map +1 -1
  40. package/dist/events/index.js +2 -0
  41. package/dist/events/index.js.map +1 -1
  42. package/dist/events/rvf-event-log.d.ts +82 -0
  43. package/dist/events/rvf-event-log.d.ts.map +1 -0
  44. package/dist/events/rvf-event-log.js +340 -0
  45. package/dist/events/rvf-event-log.js.map +1 -0
  46. package/dist/hooks/example-usage.js +3 -3
  47. package/dist/hooks/example-usage.js.map +1 -1
  48. package/dist/hooks/executor.d.ts.map +1 -1
  49. package/dist/hooks/executor.js +7 -4
  50. package/dist/hooks/executor.js.map +1 -1
  51. package/dist/hooks/verify-exports.test.js +6 -6
  52. package/dist/hooks/verify-exports.test.js.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +4 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/mcp/server.d.ts.map +1 -1
  58. package/dist/mcp/server.js +3 -6
  59. package/dist/mcp/server.js.map +1 -1
  60. package/dist/mcp/types.d.ts +4 -6
  61. package/dist/mcp/types.d.ts.map +1 -1
  62. package/dist/mcp/types.js.map +1 -1
  63. package/dist/plugins/official/hive-mind-plugin.js +2 -2
  64. package/dist/plugins/official/hive-mind-plugin.js.map +1 -1
  65. package/dist/plugins/official/maestro-plugin.js +3 -3
  66. package/dist/plugins/official/maestro-plugin.js.map +1 -1
  67. package/dist/services/index.d.ts +7 -0
  68. package/dist/services/index.d.ts.map +1 -0
  69. package/dist/services/index.js +7 -0
  70. package/dist/services/index.js.map +1 -0
  71. package/dist/services/v3-progress.service.d.ts +124 -0
  72. package/dist/services/v3-progress.service.d.ts.map +1 -0
  73. package/dist/services/v3-progress.service.js +402 -0
  74. package/dist/services/v3-progress.service.js.map +1 -0
  75. package/package.json +12 -3
  76. package/ruvector.db +0 -0
  77. package/src/core/config/loader.ts +17 -1
  78. package/src/core/config/schema.ts +3 -1
  79. package/src/events/event-store.ts +18 -9
  80. package/src/events/example-usage.ts +1 -1
  81. package/src/events/index.ts +4 -0
  82. package/src/events/rvf-event-log.ts +427 -0
  83. package/src/hooks/example-usage.ts +3 -3
  84. package/src/hooks/executor.ts +7 -5
  85. package/src/hooks/verify-exports.test.ts +6 -6
  86. package/src/index.ts +5 -0
  87. package/src/mcp/server.ts +3 -6
  88. package/src/mcp/types.ts +4 -6
  89. package/src/plugins/official/hive-mind-plugin.ts +2 -2
  90. package/src/plugins/official/maestro-plugin.ts +3 -3
  91. package/src/services/index.ts +16 -0
  92. package/src/services/v3-progress.service.ts +505 -0
  93. package/tmp.json +0 -0
  94. package/tsconfig.tsbuildinfo +1 -1
  95. package/.agentic-flow/intelligence.json +0 -16
  96. package/__tests__/coverage/base.css +0 -224
  97. package/__tests__/coverage/block-navigation.js +0 -87
  98. package/__tests__/coverage/coverage-final.json +0 -50
  99. package/__tests__/coverage/favicon.png +0 -0
  100. package/__tests__/coverage/index.html +0 -326
  101. package/__tests__/coverage/lcov-report/base.css +0 -224
  102. package/__tests__/coverage/lcov-report/block-navigation.js +0 -87
  103. package/__tests__/coverage/lcov-report/favicon.png +0 -0
  104. package/__tests__/coverage/lcov-report/index.html +0 -326
  105. package/__tests__/coverage/lcov-report/prettify.css +0 -1
  106. package/__tests__/coverage/lcov-report/prettify.js +0 -2
  107. package/__tests__/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  108. package/__tests__/coverage/lcov-report/sorter.js +0 -210
  109. package/__tests__/coverage/lcov-report/src/core/config/defaults.ts.html +0 -706
  110. package/__tests__/coverage/lcov-report/src/core/config/index.html +0 -161
  111. package/__tests__/coverage/lcov-report/src/core/config/loader.ts.html +0 -898
  112. package/__tests__/coverage/lcov-report/src/core/config/schema.ts.html +0 -649
  113. package/__tests__/coverage/lcov-report/src/core/config/validator.ts.html +0 -712
  114. package/__tests__/coverage/lcov-report/src/core/event-bus.ts.html +0 -793
  115. package/__tests__/coverage/lcov-report/src/core/index.html +0 -116
  116. package/__tests__/coverage/lcov-report/src/core/interfaces/event.interface.ts.html +0 -886
  117. package/__tests__/coverage/lcov-report/src/core/interfaces/index.html +0 -116
  118. package/__tests__/coverage/lcov-report/src/core/orchestrator/event-coordinator.ts.html +0 -451
  119. package/__tests__/coverage/lcov-report/src/core/orchestrator/health-monitor.ts.html +0 -727
  120. package/__tests__/coverage/lcov-report/src/core/orchestrator/index.html +0 -176
  121. package/__tests__/coverage/lcov-report/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
  122. package/__tests__/coverage/lcov-report/src/core/orchestrator/session-manager.ts.html +0 -922
  123. package/__tests__/coverage/lcov-report/src/core/orchestrator/task-manager.ts.html +0 -1036
  124. package/__tests__/coverage/lcov-report/src/events/domain-events.ts.html +0 -1837
  125. package/__tests__/coverage/lcov-report/src/events/event-store.ts.html +0 -1849
  126. package/__tests__/coverage/lcov-report/src/events/example-usage.ts.html +0 -964
  127. package/__tests__/coverage/lcov-report/src/events/index.html +0 -176
  128. package/__tests__/coverage/lcov-report/src/events/projections.ts.html +0 -1768
  129. package/__tests__/coverage/lcov-report/src/events/state-reconstructor.ts.html +0 -1132
  130. package/__tests__/coverage/lcov-report/src/events.ts.html +0 -1186
  131. package/__tests__/coverage/lcov-report/src/hooks/example-usage.ts.html +0 -1582
  132. package/__tests__/coverage/lcov-report/src/hooks/executor.ts.html +0 -1222
  133. package/__tests__/coverage/lcov-report/src/hooks/index.html +0 -191
  134. package/__tests__/coverage/lcov-report/src/hooks/registry.ts.html +0 -1084
  135. package/__tests__/coverage/lcov-report/src/hooks/safety/bash-safety.ts.html +0 -1897
  136. package/__tests__/coverage/lcov-report/src/hooks/safety/file-organization.ts.html +0 -1504
  137. package/__tests__/coverage/lcov-report/src/hooks/safety/git-commit.ts.html +0 -1954
  138. package/__tests__/coverage/lcov-report/src/hooks/safety/index.html +0 -146
  139. package/__tests__/coverage/lcov-report/src/hooks/session-hooks.ts.html +0 -1762
  140. package/__tests__/coverage/lcov-report/src/hooks/task-hooks.ts.html +0 -1624
  141. package/__tests__/coverage/lcov-report/src/hooks/types.ts.html +0 -1156
  142. package/__tests__/coverage/lcov-report/src/index.html +0 -176
  143. package/__tests__/coverage/lcov-report/src/mcp/connection-pool.ts.html +0 -1399
  144. package/__tests__/coverage/lcov-report/src/mcp/index.html +0 -176
  145. package/__tests__/coverage/lcov-report/src/mcp/server.ts.html +0 -2407
  146. package/__tests__/coverage/lcov-report/src/mcp/session-manager.ts.html +0 -1369
  147. package/__tests__/coverage/lcov-report/src/mcp/tool-registry.ts.html +0 -1783
  148. package/__tests__/coverage/lcov-report/src/mcp/transport/http.ts.html +0 -1756
  149. package/__tests__/coverage/lcov-report/src/mcp/transport/index.html +0 -146
  150. package/__tests__/coverage/lcov-report/src/mcp/transport/stdio.ts.html +0 -1057
  151. package/__tests__/coverage/lcov-report/src/mcp/transport/websocket.ts.html +0 -1537
  152. package/__tests__/coverage/lcov-report/src/mcp/types.ts.html +0 -1780
  153. package/__tests__/coverage/lcov-report/src/plugin-interface.ts.html +0 -2074
  154. package/__tests__/coverage/lcov-report/src/plugin-loader.ts.html +0 -1999
  155. package/__tests__/coverage/lcov-report/src/plugin-registry.ts.html +0 -1897
  156. package/__tests__/coverage/lcov-report/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
  157. package/__tests__/coverage/lcov-report/src/plugins/official/index.html +0 -131
  158. package/__tests__/coverage/lcov-report/src/plugins/official/maestro-plugin.ts.html +0 -1609
  159. package/__tests__/coverage/lcov-report/src/resilience/bulkhead.ts.html +0 -916
  160. package/__tests__/coverage/lcov-report/src/resilience/circuit-breaker.ts.html +0 -1063
  161. package/__tests__/coverage/lcov-report/src/resilience/index.html +0 -161
  162. package/__tests__/coverage/lcov-report/src/resilience/rate-limiter.ts.html +0 -1345
  163. package/__tests__/coverage/lcov-report/src/resilience/retry.ts.html +0 -757
  164. package/__tests__/coverage/lcov-report/src/security/index.html +0 -131
  165. package/__tests__/coverage/lcov-report/src/security/input-validation.ts.html +0 -880
  166. package/__tests__/coverage/lcov-report/src/security/secure-random.ts.html +0 -562
  167. package/__tests__/coverage/lcov-report/src/types/index.html +0 -131
  168. package/__tests__/coverage/lcov-report/src/types/swarm.types.ts.html +0 -850
  169. package/__tests__/coverage/lcov-report/src/types/task.types.ts.html +0 -700
  170. package/__tests__/coverage/lcov-report/src/types.ts.html +0 -1186
  171. package/__tests__/coverage/lcov-report/src/utils/index.html +0 -116
  172. package/__tests__/coverage/lcov-report/src/utils/secure-logger.ts.html +0 -856
  173. package/__tests__/coverage/lcov.info +0 -19877
  174. package/__tests__/coverage/prettify.css +0 -1
  175. package/__tests__/coverage/prettify.js +0 -2
  176. package/__tests__/coverage/sort-arrow-sprite.png +0 -0
  177. package/__tests__/coverage/sorter.js +0 -210
  178. package/__tests__/coverage/src/core/config/defaults.ts.html +0 -706
  179. package/__tests__/coverage/src/core/config/index.html +0 -161
  180. package/__tests__/coverage/src/core/config/loader.ts.html +0 -898
  181. package/__tests__/coverage/src/core/config/schema.ts.html +0 -649
  182. package/__tests__/coverage/src/core/config/validator.ts.html +0 -712
  183. package/__tests__/coverage/src/core/event-bus.ts.html +0 -793
  184. package/__tests__/coverage/src/core/index.html +0 -116
  185. package/__tests__/coverage/src/core/interfaces/event.interface.ts.html +0 -886
  186. package/__tests__/coverage/src/core/interfaces/index.html +0 -116
  187. package/__tests__/coverage/src/core/orchestrator/event-coordinator.ts.html +0 -451
  188. package/__tests__/coverage/src/core/orchestrator/health-monitor.ts.html +0 -727
  189. package/__tests__/coverage/src/core/orchestrator/index.html +0 -176
  190. package/__tests__/coverage/src/core/orchestrator/lifecycle-manager.ts.html +0 -874
  191. package/__tests__/coverage/src/core/orchestrator/session-manager.ts.html +0 -922
  192. package/__tests__/coverage/src/core/orchestrator/task-manager.ts.html +0 -1036
  193. package/__tests__/coverage/src/events/domain-events.ts.html +0 -1837
  194. package/__tests__/coverage/src/events/event-store.ts.html +0 -1849
  195. package/__tests__/coverage/src/events/example-usage.ts.html +0 -964
  196. package/__tests__/coverage/src/events/index.html +0 -176
  197. package/__tests__/coverage/src/events/projections.ts.html +0 -1768
  198. package/__tests__/coverage/src/events/state-reconstructor.ts.html +0 -1132
  199. package/__tests__/coverage/src/events.ts.html +0 -1186
  200. package/__tests__/coverage/src/hooks/example-usage.ts.html +0 -1582
  201. package/__tests__/coverage/src/hooks/executor.ts.html +0 -1222
  202. package/__tests__/coverage/src/hooks/index.html +0 -191
  203. package/__tests__/coverage/src/hooks/registry.ts.html +0 -1084
  204. package/__tests__/coverage/src/hooks/safety/bash-safety.ts.html +0 -1897
  205. package/__tests__/coverage/src/hooks/safety/file-organization.ts.html +0 -1504
  206. package/__tests__/coverage/src/hooks/safety/git-commit.ts.html +0 -1954
  207. package/__tests__/coverage/src/hooks/safety/index.html +0 -146
  208. package/__tests__/coverage/src/hooks/session-hooks.ts.html +0 -1762
  209. package/__tests__/coverage/src/hooks/task-hooks.ts.html +0 -1624
  210. package/__tests__/coverage/src/hooks/types.ts.html +0 -1156
  211. package/__tests__/coverage/src/index.html +0 -176
  212. package/__tests__/coverage/src/mcp/connection-pool.ts.html +0 -1399
  213. package/__tests__/coverage/src/mcp/index.html +0 -176
  214. package/__tests__/coverage/src/mcp/server.ts.html +0 -2407
  215. package/__tests__/coverage/src/mcp/session-manager.ts.html +0 -1369
  216. package/__tests__/coverage/src/mcp/tool-registry.ts.html +0 -1783
  217. package/__tests__/coverage/src/mcp/transport/http.ts.html +0 -1756
  218. package/__tests__/coverage/src/mcp/transport/index.html +0 -146
  219. package/__tests__/coverage/src/mcp/transport/stdio.ts.html +0 -1057
  220. package/__tests__/coverage/src/mcp/transport/websocket.ts.html +0 -1537
  221. package/__tests__/coverage/src/mcp/types.ts.html +0 -1780
  222. package/__tests__/coverage/src/plugin-interface.ts.html +0 -2074
  223. package/__tests__/coverage/src/plugin-loader.ts.html +0 -1999
  224. package/__tests__/coverage/src/plugin-registry.ts.html +0 -1897
  225. package/__tests__/coverage/src/plugins/official/hive-mind-plugin.ts.html +0 -1075
  226. package/__tests__/coverage/src/plugins/official/index.html +0 -131
  227. package/__tests__/coverage/src/plugins/official/maestro-plugin.ts.html +0 -1609
  228. package/__tests__/coverage/src/resilience/bulkhead.ts.html +0 -916
  229. package/__tests__/coverage/src/resilience/circuit-breaker.ts.html +0 -1063
  230. package/__tests__/coverage/src/resilience/index.html +0 -161
  231. package/__tests__/coverage/src/resilience/rate-limiter.ts.html +0 -1345
  232. package/__tests__/coverage/src/resilience/retry.ts.html +0 -757
  233. package/__tests__/coverage/src/security/index.html +0 -131
  234. package/__tests__/coverage/src/security/input-validation.ts.html +0 -880
  235. package/__tests__/coverage/src/security/secure-random.ts.html +0 -562
  236. package/__tests__/coverage/src/types/index.html +0 -131
  237. package/__tests__/coverage/src/types/swarm.types.ts.html +0 -850
  238. package/__tests__/coverage/src/types/task.types.ts.html +0 -700
  239. package/__tests__/coverage/src/types.ts.html +0 -1186
  240. package/__tests__/coverage/src/utils/index.html +0 -116
  241. package/__tests__/coverage/src/utils/secure-logger.ts.html +0 -856
@@ -0,0 +1,155 @@
1
+ [2026-04-28T12:36:51.594Z] RESULT
2
+ ============================================================
3
+ {
4
+ "success": true,
5
+ "output": "Now I have enough to produce the complete gap analysis with actionable skeletons.\n\n---\n\n## Test Coverage Gap Analysis\n\n### Summary\n\nThe codebase has **5 existing test files** covering hooks and event-store. **11 source modules have zero test coverage**, and gaps exist inside the tested modules.\n\n---\n\n## 1. Entire Modules With No Tests\n\n### 1a. `src/core/event-bus.ts` — EventBus\n\nZero coverage on the core pub/sub system. Missing:\n- Basic `emit` / `on` dispatch\n- `emitAsync` waits for all handlers\n- `once` fires exactly once\n- `off` removes specific handlers\n- `removeAllListeners` scoped vs full\n- `subscribe` with `IEventFilter` (type + source + priority)\n- `IEventSubscription.pause()` / `resume()` suppresses/re-enables dispatch\n- Wildcard `*` handler receives all event types\n- Async handler errors are caught and logged (don't throw)\n\n```ts\n// __tests__/core/event-bus.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventBus, createEventBus } from '../../src/core/event-bus.js';\n\ndescribe('EventBus', () => {\n let bus: EventBus;\n beforeEach(() => { bus = createEventBus(); });\n\n describe('emit / on', () => {\n it('dispatches payload to subscriber', () => {\n const handler = vi.fn();\n bus.on('test:event', handler);\n bus.emit('test:event', { value: 42 });\n expect(handler).toHaveBeenCalledOnce();\n expect(handler.mock.calls[0][0].payload).toEqual({ value: 42 });\n });\n\n it('does not dispatch to wrong type', () => {\n const handler = vi.fn();\n bus.on('other:event', handler);\n bus.emit('test:event', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('dispatches to wildcard subscribers', () => {\n const handler = vi.fn();\n bus.on('*', handler);\n bus.emit('anything', {});\n expect(handler).toHaveBeenCalled();\n });\n });\n\n describe('emitAsync', () => {\n it('waits for async handlers before resolving', async () => {\n const order: number[] = [];\n bus.on('ev', async () => { await new Promise(r => setTimeout(r, 10)); order.push(1); });\n await bus.emitAsync('ev', {});\n order.push(2);\n expect(order[0]).toBe(1);\n });\n });\n\n describe('once', () => {\n it('fires handler exactly once', async () => {\n const handler = vi.fn();\n bus.once('ev', handler);\n bus.emit('ev', {});\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n });\n\n describe('off', () => {\n it('removes specific handler', () => {\n const handler = vi.fn();\n bus.on('ev', handler);\n bus.off('ev', handler);\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('removeAllListeners', () => {\n it('removes all handlers for a type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('ev', h1); bus.on('ev', h2);\n bus.removeAllListeners('ev');\n bus.emit('ev', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n\n it('removes all handlers when called without type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('a', h1); bus.on('b', h2);\n bus.removeAllListeners();\n bus.emit('a', {}); bus.emit('b', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n });\n\n describe('subscription lifecycle', () => {\n it('pause suppresses dispatch, resume re-enables', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.pause();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n sub.resume();\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('unsubscribe removes handler', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.unsubscribe();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('listenerCount / eventNames', () => {\n it('tracks listener counts', () => {\n bus.on('ev', vi.fn()); bus.on('ev', vi.fn());\n expect(bus.listenerCount('ev')).toBe(2);\n expect(bus.listenerCount('other')).toBe(0);\n });\n\n it('returns registered event names', () => {\n bus.on('a', vi.fn()); bus.on('b', vi.fn());\n expect(bus.eventNames()).toContain('a');\n expect(bus.eventNames()).toContain('b');\n });\n });\n\n describe('error handling', () => {\n it('catches errors in async handlers without throwing', async () => {\n bus.on('ev', async () => { throw new Error('boom'); });\n await expect(bus.emitAsync('ev', {})).resolves.toBeUndefined();\n });\n });\n});\n```\n\n---\n\n### 1b. `src/core/config/` — Config Module\n\nNo tests for `validator.ts`, `defaults.ts`, or `loader.ts`. Missing:\n\n- `validateAgentConfig` / `validateTaskConfig` / `validateSwarmConfig` — valid and invalid inputs\n- `ConfigValidator.validateOrThrow()` throws with readable message on invalid data\n- `ConfigValidator.isValid()` returns boolean\n- Schema defaults are applied (e.g., `capabilities` defaults to `[]` when omitted)\n- `mergeWithDefaults` deep-merges properly\n- `ConfigLoader.load()` returns defaults when no file found\n- `ConfigLoader.load()` merges env vars (`CLAUDE_FLOW_MAX_AGENTS`, `CLAUDE_FLOW_MEMORY_TYPE`)\n- `ConfigLoader.load()` warns about invalid JSON config file\n\n```ts\n// __tests__/core/config/validator.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateAgentConfig, validateTaskConfig, validateSwarmConfig,\n validateMemoryConfig, validateSystemConfig, ConfigValidator,\n} from '../../../src/core/config/index.js';\n\ndescribe('validateAgentConfig', () => {\n it('accepts valid agent config', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'Coder', type: 'coder' });\n expect(result.success).toBe(true);\n expect(result.data?.capabilities).toEqual([]); // default applied\n expect(result.data?.priority).toBe(50); // default\n });\n\n it('rejects missing required fields', () => {\n const result = validateAgentConfig({ name: 'Coder' });\n expect(result.success).toBe(false);\n expect(result.errors?.some(e => e.path === 'id')).toBe(true);\n });\n\n it('rejects priority out of range', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'X', type: 'coder', priority: 101 });\n expect(result.success).toBe(false);\n });\n});\n\ndescribe('validateSwarmConfig', () => {\n it('rejects unknown topology', () => {\n const result = validateSwarmConfig({ topology: 'unknown' });\n expect(result.success).toBe(false);\n });\n\n it('accepts all valid topologies', () => {\n const topologies = ['hierarchical', 'mesh', 'ring', 'star', 'adaptive', 'hierarchical-mesh'];\n for (const topology of topologies) {\n expect(validateSwarmConfig({ topology }).success).toBe(true);\n }\n });\n});\n\ndescribe('ConfigValidator', () => {\n it('validateOrThrow throws with readable message', () => {\n expect(() => ConfigValidator.validateAgentOrThrow({ name: 'X' }))\n .toThrow('Invalid agent configuration');\n });\n\n it('isValid returns false for invalid data', () => {\n const { AgentConfigSchema } = require('../../../src/core/config/schema.js');\n expect(ConfigValidator.isValid(AgentConfigSchema, {})).toBe(false);\n });\n});\n```\n\n```ts\n// __tests__/core/config/defaults.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { mergeWithDefaults, agentTypePresets, defaultSystemConfig } from '../../../src/core/config/defaults.js';\n\ndescribe('mergeWithDefaults', () => {\n it('applies defaults for missing keys', () => {\n const result = mergeWithDefaults({ a: 1 } as any, { a: 0, b: 2 } as any);\n expect(result.a).toBe(1);\n expect(result.b).toBe(2);\n });\n\n it('override wins over default', () => {\n const result = mergeWithDefaults({ key: 'override' } as any, { key: 'default' } as any);\n expect(result.key).toBe('override');\n });\n});\n\ndescribe('agentTypePresets', () => {\n it('has preset for all core agent types', () => {\n const expectedTypes = ['coder', 'reviewer', 'tester', 'researcher', 'planner', 'coordinator'];\n for (const type of expectedTypes) {\n expect(agentTypePresets[type]).toBeDefined();\n }\n });\n\n it('coordinator has highest priority', () => {\n expect(agentTypePresets.coordinator!.priority).toBeGreaterThan(\n agentTypePresets.coder!.priority!\n );\n });\n});\n```\n\n---\n\n### 1c. `src/resilience/circuit-breaker.ts` — CircuitBreaker\n\nZero coverage. Missing state transitions: `CLOSED → OPEN → HALF_OPEN → CLOSED`.\n\n```ts\n// __tests__/resilience/circuit-breaker.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { CircuitBreaker, CircuitBreakerState } from '../../src/resilience/circuit-breaker.js';\n\ndescribe('CircuitBreaker', () => {\n let breaker: CircuitBreaker;\n\n beforeEach(() => {\n breaker = new CircuitBreaker({\n name: 'test',\n failureThreshold: 3,\n successThreshold: 2,\n timeout: 100,\n rollingWindow: 5000,\n volumeThreshold: 3,\n });\n });\n\n it('starts in CLOSED state', () => {\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('executes function successfully in CLOSED state', async () => {\n const result = await breaker.execute(() => Promise.resolve('ok'));\n expect(result).toBe('ok');\n });\n\n it('transitions to OPEN after failure threshold is met', async () => {\n const failFn = () => Promise.reject(new Error('fail'));\n for (let i = 0; i < 3; i++) {\n await breaker.execute(failFn).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('rejects immediately when OPEN without fallback', async () => {\n // Force open via failures\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await expect(breaker.execute(() => Promise.resolve('ok')))\n .rejects.toThrow(\"Circuit breaker 'test' is open\");\n expect(breaker.getStats().rejectedRequests).toBe(1);\n });\n\n it('uses fallback when OPEN', async () => {\n const breakerWithFallback = new CircuitBreaker({\n name: 'fb', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n fallback: () => 'fallback-value',\n });\n await breakerWithFallback.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n const result = await breakerWithFallback.execute(() => Promise.resolve('ok'));\n expect(result).toBe('fallback-value');\n });\n\n it('transitions OPEN → HALF_OPEN after timeout', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n await new Promise(r => setTimeout(r, 150));\n expect(breaker.getState()).toBe(CircuitBreakerState.HALF_OPEN);\n });\n\n it('transitions HALF_OPEN → CLOSED after success threshold', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n // Now in HALF_OPEN — 2 successes needed\n await breaker.execute(() => Promise.resolve('ok'));\n await breaker.execute(() => Promise.resolve('ok'));\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('transitions HALF_OPEN → OPEN on failure', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n await breaker.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('respects custom isFailure predicate', async () => {\n const selective = new CircuitBreaker({\n name: 'sel', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n isFailure: (e) => e.message === 'real-failure',\n });\n // This error is ignored\n await selective.execute(() => Promise.reject(new Error('ignore'))).catch(() => {});\n expect(selective.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('reset returns to CLOSED', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n breaker.reset();\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('emits stateChange event', async () => {\n const listener = vi.fn();\n breaker.on('stateChange', listener);\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(listener).toHaveBeenCalledWith(\n expect.objectContaining({ from: CircuitBreakerState.CLOSED, to: CircuitBreakerState.OPEN })\n );\n });\n\n it('does not trip below volumeThreshold', async () => {\n // volumeThreshold = 3, failureThreshold = 3\n // Only 2 requests — should not trip\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n});\n```\n\n---\n\n### 1d. `src/resilience/retry.ts` — Retry\n\n```ts\n// __tests__/resilience/retry.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { retry, withRetry, RetryableErrors } from '../../src/resilience/retry.js';\n\ndescribe('retry', () => {\n it('returns result on first success', async () => {\n const fn = vi.fn(() => Promise.resolve('value'));\n const result = await retry(fn, { maxAttempts: 3 });\n expect(result.success).toBe(true);\n expect(result.result).toBe('value');\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('retries on failure and eventually succeeds', async () => {\n let calls = 0;\n const fn = vi.fn(() => {\n calls++;\n if (calls < 3) return Promise.reject(new Error('fail'));\n return Promise.resolve('ok');\n });\n const result = await retry(fn, { maxAttempts: 5, initialDelay: 1 });\n expect(result.success).toBe(true);\n expect(result.attempts).toBe(3);\n });\n\n it('returns failure after exhausting all attempts', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('always fails')));\n const result = await retry(fn, { maxAttempts: 3, initialDelay: 1 });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(3);\n expect(result.errors).toHaveLength(3);\n });\n\n it('stops immediately for non-retryable errors', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('permanent')));\n const result = await retry(fn, {\n maxAttempts: 5,\n initialDelay: 1,\n retryableErrors: (e) => e.message !== 'permanent',\n });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('calls onRetry callback with attempt info', async () => {\n const onRetry = vi.fn();\n const fn = vi.fn()\n .mockRejectedValueOnce(new Error('fail'))\n .mockResolvedValueOnce('ok');\n await retry(fn, { maxAttempts: 3, initialDelay: 1, onRetry });\n expect(onRetry).toHaveBeenCalledOnce();\n expect(onRetry.mock.calls[0][1]).toBe(1); // attempt number\n });\n\n it('respects maxDelay cap', async () => {\n const delays: number[] = [];\n const fn = vi.fn(() => Promise.reject(new Error('fail')));\n const onRetry = (_: Error, __: number, delay: number) => delays.push(delay);\n await retry(fn, {\n maxAttempts: 5, initialDelay: 1000, backoffMultiplier: 10,\n maxDelay: 500, jitter: 0, onRetry,\n });\n expect(delays.every(d => d <= 500)).toBe(true);\n });\n});\n\ndescribe('RetryableErrors', () => {\n it('network detects ECONNRESET', () => {\n expect(RetryableErrors.network(new Error('ECONNRESET'))).toBe(true);\n expect(RetryableErrors.network(new Error('syntax error'))).toBe(false);\n });\n\n it('rateLimit detects 429', () => {\n expect(RetryableErrors.rateLimit(new Error('429 Too Many'))).toBe(true);\n });\n\n it('transient combines network + rateLimit + 5xx', () => {\n expect(RetryableErrors.transient(new Error('ETIMEDOUT'))).toBe(true);\n expect(RetryableErrors.transient(new Error('500 Internal Server Error'))).toBe(true);\n expect(RetryableErrors.transient(new Error('validation error'))).toBe(false);\n });\n});\n```\n\n---\n\n### 1e. `src/resilience/bulkhead.ts` — Bulkhead\n\n```ts\n// __tests__/resilience/bulkhead.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { Bulkhead, createSemaphore } from '../../src/resilience/bulkhead.js';\n\ndescribe('Bulkhead', () => {\n it('executes functions up to maxConcurrent immediately', async () => {\n const bh = new Bulkhead({ name: 'test', maxConcurrent: 2, maxQueue: 5, queueTimeout: 1000 });\n const results = await Promise.all([\n bh.execute(() => Promise.resolve(1)),\n bh.execute(() => Promise.resolve(2)),\n ]);\n expect(results).toEqual([1, 2]);\n expect(bh.getStats().completed).toBe(2);\n });\n\n it('queues excess requests and processes them', async () => {\n const bh = new Bulkhead({ name: 'bh', maxConcurrent: 1, maxQueue: 5, queueTimeout: 1000 });\n let resolve1!: () => void;\n const p1 = new Promise<void>(r => { resolve1 = r; });\n\n const result1 = bh.execute(() => p1.then(() => 'first'));\n const result2 = bh.execute(() => Promise.resolve('second'));\n\n resolve1();\n const [r1, r2] = await Promise.all([result1, result2]);\n expect(r1).toBe('first');\n expect(r2).toBe('second');\n });\n\n it('rejects when queue is full', async () => {\n const bh = new Bulkhead({ name: 'full', maxConcurrent: 1, maxQueue: 1, queueTimeout: 5000 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker); // occupies concurrency slot\n bh.execute(() => Promise.resolve('queued')); // fills queue\n\n await expect(bh.execute(() => Promise.resolve('overflow')))\n .rejects.toThrow(\"Bulkhead 'full' is full\");\n expect(bh.getStats().rejected).toBe(1);\n\n resolve();\n });\n\n it('times out queued items', async () => {\n const bh = new Bulkhead({ name: 'to', maxConcurrent: 1, maxQueue: 5, queueTimeout: 50 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker);\n const queued = bh.execute(() => Promise.resolve('ok'));\n\n await expect(queued).rejects.toThrow('queue timeout');\n expect(bh.getStats().timedOut).toBe(1);\n\n resolve();\n });\n\n it('calls onRejected callback on full', async () => {\n const onRejected = vi.fn();\n const bh = new Bulkhead({ name: 'cb', maxConcurrent: 1, maxQueue: 0, queueTimeout: 1000, onRejected });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n bh.execute(() => blocker);\n await bh.execute(() => Promise.resolve()).catch(() => {});\n expect(onRejected).toHaveBeenCalledWith('full');\n resolve();\n });\n});\n\ndescribe('createSemaphore', () => {\n it('limits concurrent access', async () => {\n const sem = createSemaphore(2);\n let active = 0;\n let maxActive = 0;\n\n const task = async () => {\n await sem.acquire();\n active++;\n maxActive = Math.max(maxActive, active);\n await new Promise(r => setTimeout(r, 10));\n active--;\n sem.release();\n };\n\n await Promise.all([task(), task(), task(), task()]);\n expect(maxActive).toBeLessThanOrEqual(2);\n });\n});\n```\n\n---\n\n### 1f. `src/resilience/rate-limiter.ts` — SlidingWindowRateLimiter & TokenBucketRateLimiter\n\n```ts\n// __tests__/resilience/rate-limiter.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { SlidingWindowRateLimiter, TokenBucketRateLimiter } from '../../src/resilience/rate-limiter.js';\n\ndescribe('SlidingWindowRateLimiter', () => {\n it('allows requests up to limit', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 3, windowMs: 10000 });\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false); // 4th blocked\n limiter.destroy();\n });\n\n it('tracks remaining and used counts', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 5, windowMs: 10000 });\n limiter.consume('k');\n limiter.consume('k');\n const r = limiter.consume('k');\n expect(r.used).toBe(3);\n expect(r.remaining).toBe(2);\n limiter.destroy();\n });\n\n it('check does not consume a token', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.check('k'); // peek only\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false);\n limiter.destroy();\n });\n\n it('reset clears a key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.consume('k');\n limiter.reset('k');\n expect(limiter.consume('k').allowed).toBe(true);\n limiter.destroy();\n });\n\n it('tracks separately by key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n expect(limiter.consume('user-1').allowed).toBe(true);\n expect(limiter.consume('user-1').allowed).toBe(false);\n expect(limiter.consume('user-2').allowed).toBe(true); // separate key\n limiter.destroy();\n });\n});\n\ndescribe('TokenBucketRateLimiter', () => {\n it('starts with full bucket', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 5, windowMs: 1000 });\n const r = limiter.check();\n expect(r.remaining).toBe(5);\n expect(r.allowed).toBe(true);\n limiter.destroy();\n });\n\n it('consumes tokens on each request', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 2, windowMs: 10000 });\n limiter.consume();\n const r = limiter.consume();\n expect(r.remaining).toBe(0);\n expect(limiter.consume().allowed).toBe(false);\n limiter.destroy();\n });\n});\n```\n\n---\n\n### 1g. `src/security/input-validation.ts`\n\nZero coverage on all validation functions.\n\n```ts\n// __tests__/security/input-validation.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateInput, sanitizeString, validatePath,\n validateCommand, validateTags, isValidIdentifier, escapeForSql,\n} from '../../src/security/input-validation.js';\n\ndescribe('validateInput', () => {\n it('returns valid: true for valid string', () => {\n const r = validateInput('hello');\n expect(r.valid).toBe(true);\n expect(r.sanitized).toBe('hello');\n });\n\n it('trims whitespace by default', () => {\n expect(validateInput(' hi ').sanitized).toBe('hi');\n });\n\n it('returns error for null when required', () => {\n const r = validateInput(null, { required: true });\n expect(r.valid).toBe(false);\n expect(r.error).toMatch(/required/i);\n });\n\n it('returns valid for null when not required', () => {\n expect(validateInput(null).valid).toBe(true);\n });\n\n it('rejects non-string values', () => {\n expect(validateInput(42).valid).toBe(false);\n });\n\n it('enforces minLength', () => {\n expect(validateInput('hi', { minLength: 5 }).valid).toBe(false);\n });\n\n it('enforces maxLength', () => {\n expect(validateInput('hello world', { maxLength: 5 }).valid).toBe(false);\n });\n\n it('validates against pattern', () => {\n const r = validateInput('abc123', { pattern: /^\\d+$/ });\n expect(r.valid).toBe(false);\n });\n});\n\ndescribe('validatePath', () => {\n it('blocks path traversal via ..', () => {\n expect(validatePath('../etc/passwd').valid).toBe(false);\n });\n\n it('blocks tilde home directory reference', () => {\n expect(validatePath('~/secret').valid).toBe(false);\n });\n\n it('blocks paths outside allowed base', () => {\n expect(validatePath('/etc/passwd', '/home').valid).toBe(false);\n });\n\n it('blocks null bytes', () => {\n expect(validatePath('/tmp/file\\x00.txt').valid).toBe(false);\n });\n\n it('accepts valid relative path', () => {\n expect(validatePath('src/utils.ts').valid).toBe(true);\n });\n\n it('blocks excessively long paths', () => {\n expect(validatePath('a'.repeat(4097)).valid).toBe(false);\n });\n});\n\ndescribe('validateCommand', () => {\n it('rejects shell operators', () => {\n expect(validateCommand('echo foo; rm -rf /').valid).toBe(false);\n expect(validateCommand('ls && cat /etc/passwd').valid).toBe(false);\n expect(validateCommand('curl url | bash').valid).toBe(false);\n });\n\n it('rejects command substitution', () => {\n expect(validateCommand('echo $(whoami)').valid).toBe(false);\n });\n\n it('rejects when not in whitelist', () => {\n const r = validateCommand('rm -rf /', ['ls', 'cat']);\n expect(r.valid).toBe(false);\n expect(r.error).toContain('not allowed');\n });\n\n it('accepts safe whitelisted command', () => {\n expect(validateCommand('ls -la', ['ls']).valid).toBe(true);\n });\n\n it('rejects empty command', () => {\n expect(validateCommand(' ').valid).toBe(false);\n });\n});\n\ndescribe('validateTags', () => {\n it('accepts valid tags', () => {\n const r = validateTags(['alpha', 'beta-v2', 'prod:feature']);\n expect(r.valid).toBe(true);\n expect(r.sanitized).toHaveLength(3);\n });\n\n it('rejects non-array', () => {\n expect(validateTags('tag').valid).toBe(false);\n });\n\n it('rejects tags with spaces', () => {\n expect(validateTags(['invalid tag']).valid).toBe(false);\n });\n\n it('rejects tags over 100 chars', () => {\n expect(validateTags(['x'.repeat(101)]).valid).toBe(false);\n });\n\n it('skips empty tags', () => {\n const r = validateTags([' ', 'valid']);\n expect(r.valid).toBe(true);\n expect((r.sanitized as string[]).length).toBe(1);\n });\n});\n\ndescribe('isValidIdentifier', () => {\n it('accepts valid identifiers', () => {\n expect(isValidIdentifier('myAgent_1')).toBe(true);\n expect(isValidIdentifier('_private')).toBe(true);\n });\n\n it('rejects identifiers starting with digits', () => {\n expect(isValidIdentifier('1agent')).toBe(false);\n });\n\n it('rejects identifiers over 256 chars', () => {\n expect(isValidIdentifier('a'.repeat(257))).toBe(false);\n });\n});\n```\n\n---\n\n### 1h. `src/security/secure-random.ts`\n\n```ts\n// __tests__/security/secure-random.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n generateSecureId, generateUUID, generateSecureToken,\n generateShortId, generateSessionId, secureRandomInt, secureRandomChoice, secureShuffleArray,\n} from '../../src/security/secure-random.js';\n\ndescribe('generateSecureId', () => {\n it('generates unique IDs', () => {\n const ids = new Set(Array.from({ length: 100 }, () => generateSecureId()));\n expect(ids.size).toBe(100);\n });\n\n it('applies prefix', () => {\n expect(generateSecureId('agent')).toMatch(/^agent_/);\n });\n});\n\ndescribe('generateUUID', () => {\n it('generates valid UUID v4 format', () => {\n const uuid = generateUUID();\n expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);\n });\n});\n\ndescribe('secureRandomInt', () => {\n it('returns value within [min, max]', () => {\n for (let i = 0; i < 100; i++) {\n const v = secureRandomInt(5, 10);\n expect(v).toBeGreaterThanOrEqual(5);\n expect(v).toBeLessThanOrEqual(10);\n }\n });\n\n it('handles single-value range', () => {\n expect(secureRandomInt(7, 7)).toBe(7);\n });\n});\n\ndescribe('secureRandomChoice', () => {\n it('selects element from array', () => {\n const arr = ['a', 'b', 'c'];\n const result = secureRandomChoice(arr);\n expect(arr).toContain(result);\n });\n\n it('throws on empty array', () => {\n expect(() => secureRandomChoice([])).toThrow();\n });\n});\n\ndescribe('secureShuffleArray', () => {\n it('returns array with same elements', () => {\n const arr = [1, 2, 3, 4, 5];\n const shuffled = secureShuffleArray(arr);\n expect(shuffled).toHaveLength(arr.length);\n expect(shuffled.sort()).toEqual([...arr].sort());\n });\n\n it('does not mutate original array', () => {\n const arr = [1, 2, 3];\n const orig = [...arr];\n secureShuffleArray(arr);\n expect(arr).toEqual(orig);\n });\n});\n```\n\n---\n\n## 2. Gaps in Existing Tests\n\n### 2a. `src/hooks/hooks.test.ts` — HookRegistry & HookExecutor gaps\n\n**Untested in `HookRegistry`:**\n- `unregisterAll(event)` removes only that event's hooks\n- `unregisterAll()` removes all hooks and resets counter\n- `getHook(hookId)` returns definition or `undefined`\n- `count(event)` vs `count()` — scoped vs total\n\n**Untested in `HookExecutor`:**\n- `continueChain === false` stops the chain without setting `aborted = true`\n- `collectResults: false` (default) returns empty `results[]`\n- `executeParallel` throws when `events.length !== contexts.length`\n- `executeParallel` respects `maxParallel` batch size (emits in batches, not all at once)\n- `executeSequential` aborts entire chain when intermediate event fails and `continueOnError` is false\n\n```ts\n// Additional tests to add to src/hooks/hooks.test.ts:\n\ndescribe('HookRegistry — missing coverage', () => {\n it('unregisterAll(event) removes only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n\n const removed = registry.unregisterAll(HookEvent.PreToolUse);\n expect(removed).toBe(2);\n expect(registry.count(HookEvent.PreToolUse)).toBe(0);\n expect(registry.count(HookEvent.PostToolUse)).toBe(1);\n });\n\n it('unregisterAll() removes everything', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n const removed = registry.unregisterAll();\n expect(removed).toBe(2);\n expect(registry.count()).toBe(0);\n });\n\n it('getHook returns definition for valid id', () => {\n const handler = vi.fn();\n const id = registry.register(HookEvent.PreEdit, handler);\n const def = registry.getHook(id);\n expect(def).toBeDefined();\n expect(def?.handler).toBe(handler);\n });\n\n it('getHook returns undefined for unknown id', () => {\n expect(registry.getHook('no-such-id')).toBeUndefined();\n });\n\n it('count(event) counts only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n expect(registry.count(HookEvent.PreToolUse)).toBe(2);\n expect(registry.count()).toBe(3);\n });\n});\n\ndescribe('HookExecutor — missing coverage', () => {\n it('continueChain=false stops chain but aborted remains false', async () => {\n const h1 = vi.fn(async () => ({ success: true }));\n const h2 = vi.fn(async () => ({ success: true, continueChain: false }));\n const h3 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1, HookPriority.High);\n registry.register(HookEvent.PreEdit, h2, HookPriority.Normal);\n registry.register(HookEvent.PreEdit, h3, HookPriority.Low);\n\n const result = await executor.execute(HookEvent.PreEdit, { event: HookEvent.PreEdit, timestamp: new Date() });\n expect(result.hooksExecuted).toBe(2);\n expect(h3).not.toHaveBeenCalled();\n expect(result.aborted).toBe(false); // chain stopped but not aborted\n });\n\n it('collectResults=false returns empty results array', async () => {\n registry.register(HookEvent.PreEdit, async () => ({ success: true }));\n const result = await executor.execute(\n HookEvent.PreEdit,\n { event: HookEvent.PreEdit, timestamp: new Date() },\n { collectResults: false }\n );\n expect(result.results).toHaveLength(0);\n });\n\n it('executeParallel throws when arrays have different lengths', async () => {\n await expect(\n executor.executeParallel(\n [HookEvent.PreEdit],\n [{ event: HookEvent.PreEdit, timestamp: new Date() }, { event: HookEvent.PostEdit, timestamp: new Date() }]\n )\n ).rejects.toThrow('same length');\n });\n\n it('executeSequential aborts after failing event when continueOnError is false', async () => {\n const h1 = vi.fn(async () => { throw new Error('fail'); });\n const h2 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1);\n registry.register(HookEvent.PostEdit, h2);\n\n const result = await executor.executeSequential(\n [HookEvent.PreEdit, HookEvent.PostEdit],\n { event: HookEvent.PreEdit, timestamp: new Date() }\n );\n expect(result.aborted).toBe(true);\n expect(h2).not.toHaveBeenCalled();\n });\n});\n```\n\n---\n\n### 2b. `src/events/event-store.test.ts` — EventStore gaps\n\n**Missing:**\n- `ensureInitialized` throws when called before `initialize()`\n- `afterTimestamp` / `beforeTimestamp` filters in `query()`\n- `fromVersion` filter in `query()`\n- `snapshot:recommended` event emitted when version hits `snapshotThreshold`\n- `event:appended` event fired per append\n- `getSnapshot` returns `null` for unknown aggregate\n- Stats `oldestEvent` / `newestEvent` set after appending\n- Stats `snapshotCount` increments after `saveSnapshot`\n\n```ts\n// Additional tests to add to src/events/event-store.test.ts:\n\ndescribe('EventStore — missing coverage', () => {\n it('throws when methods called before initialize()', async () => {\n const uninit = new EventStore({ databasePath: ':memory:' });\n await expect(uninit.append(createAgentSpawnedEvent('a', 'coder', 'core', [])))\n .rejects.toThrow('not initialized');\n });\n\n it('emits event:appended for each append', async () => {\n const handler = vi.fn();\n eventStore.on('event:appended', handler);\n await eventStore.append(createAgentSpawnedEvent('a1', 'coder', 'core', []));\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('emits snapshot:recommended when version hits threshold', async () => {\n const store = new EventStore({ databasePath: ':memory:', snapshotThreshold: 2 });\n await store.initialize();\n const handler = vi.fn();\n store.on('snapshot:recommended', handler);\n\n await store.append(createAgentSpawnedEvent('agg', 'coder', 'core', []));\n await store.append(createAgentStartedEvent('agg'));\n expect(handler).toHaveBeenCalledOnce();\n await store.shutdown();\n });\n\n it('query filters by afterTimestamp', async () => {\n const before = Date.now();\n await new Promise(r => setTimeout(r, 5));\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const events = await eventStore.query({ afterTimestamp: before });\n expect(events).toHaveLength(1);\n });\n\n it('query filters by beforeTimestamp', async () => {\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const after = Date.now() + 1000;\n const events = await eventStore.query({ beforeTimestamp: after });\n expect(events.length).toBeGreaterThan(0);\n const noEvents = await eventStore.query({ beforeTimestamp: 0 });\n expect(noEvents).toHaveLength(0);\n });\n\n it('query filters by fromVersion', async () => {\n await eventStore.append(createAgentSpawnedEvent('v', 'coder', 'core', []));\n await eventStore.append(createAgentStartedEvent('v'));\n const events = await eventStore.query({ aggregateIds: ['v'], fromVersion: 2 });\n expect(events).toHaveLength(1);\n expect(events[0].version).toBe(2);\n });\n\n it('getSnapshot returns null for unknown aggregate', async () => {\n const snap = await eventStore.getSnapshot('no-such-aggregate');\n expect(snap).toBeNull();\n });\n\n it('stats show oldest/newest timestamps after appending', async () => {\n await eventStore.append(createAgentSpawnedEvent('a', 'coder', 'core', []));\n const stats = await eventStore.getStats();\n expect(stats.oldestEvent).not.toBeNull();\n expect(stats.newestEvent).not.toBeNull();\n expect(stats.newestEvent!).toBeGreaterThanOrEqual(stats.oldestEvent!);\n });\n\n it('stats count snapshots', async () => {\n await eventStore.saveSnapshot({\n aggregateId: 'a1', aggregateType: 'agent',\n version: 1, state: {}, timestamp: Date.now(),\n });\n const stats = await eventStore.getStats();\n expect(stats.snapshotCount).toBe(1);\n });\n});\n```\n\n---\n\n### 2c. `src/core/orchestrator/event-coordinator.ts` gaps\n\nThe `EventCoordinator` class has no test file at all.\n\n```ts\n// __tests__/core/orchestrator/event-coordinator.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventCoordinator } from '../../../src/core/orchestrator/event-coordinator.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\nimport { SystemEventTypes } from '../../../src/core/interfaces/event.interface.js';\n\ndescribe('EventCoordinator', () => {\n let coordinator: EventCoordinator;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(async () => {\n bus = createEventBus();\n coordinator = new EventCoordinator(bus);\n await coordinator.initialize();\n });\n\n afterEach(async () => { await coordinator.shutdown(); });\n\n it('registers handlers and routes events', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('test:event', handler);\n await bus.emitAsync('test:event', { data: 1 });\n expect(handler).toHaveBeenCalled();\n });\n\n it('unregisters handlers', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('ev', handler);\n coordinator.unregisterHandler('ev', handler);\n await bus.emitAsync('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('initialize is idempotent', async () => {\n await coordinator.initialize(); // second call — should not throw or double-register\n expect(coordinator.isInitialized()).toBe(true);\n });\n\n it('shutdown clears handlers', async () => {\n coordinator.registerHandler('ev', vi.fn());\n await coordinator.shutdown();\n expect(coordinator.getRegisteredTypes()).toHaveLength(0);\n expect(coordinator.isInitialized()).toBe(false);\n });\n\n it('getHandlerCount returns correct count', () => {\n coordinator.registerHandler('ev', vi.fn());\n coordinator.registerHandler('ev', vi.fn());\n expect(coordinator.getHandlerCount('ev')).toBe(2);\n expect(coordinator.getHandlerCount('other')).toBe(0);\n });\n\n it('handler errors do not throw from route()', async () => {\n coordinator.registerHandler('ev', async () => { throw new Error('boom'); });\n await expect(coordinator.route({ id: '1', type: 'ev', timestamp: new Date(), source: 'test', payload: {} }))\n .resolves.toBeUndefined();\n });\n});\n```\n\n---\n\n## 3. Integration Test Gaps\n\nThese cross-cutting scenarios are not covered anywhere:\n\n| Scenario | Missing Test |\n|---|---|\n| Config load → validate → use in TaskManager | No integration test |\n| EventBus ↔ EventCoordinator: coordinator routes events dispatched on bus | No integration test |\n| HookExecutor with real EventBus: `hooks:error` event emitted on handler exception | Partially in hooks.test.ts but only verifies execution, not bus event shape |\n| TaskManager.cleanup() removes tasks older than threshold | TaskManager has no tests at all |\n| TaskQueue priority ordering with `updatePriority()` reordering | No test |\n| SessionHooksManager + InMemorySessionStorage: full round-trip store and list | Partially tested but `list()` not verified independently |\n\n```ts\n// __tests__/core/orchestrator/task-manager.test.ts (skeleton)\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { TaskQueue, TaskManager } from '../../../src/core/orchestrator/task-manager.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\n\ndescribe('TaskQueue', () => {\n let queue: TaskQueue;\n beforeEach(() => { queue = new TaskQueue(); });\n\n it('dequeues in priority order', async () => {\n const make = (id: string, priority: number) =>\n ({ id, type: 'test', description: '', priority, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('low', 10));\n await queue.enqueue(make('high', 90));\n await queue.enqueue(make('mid', 50));\n expect((await queue.dequeue())!.id).toBe('high');\n expect((await queue.dequeue())!.id).toBe('mid');\n expect((await queue.dequeue())!.id).toBe('low');\n });\n\n it('updatePriority re-sorts queue', async () => {\n const make = (id: string, p: number) =>\n ({ id, type: 't', description: '', priority: p, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('a', 10));\n await queue.enqueue(make('b', 20));\n await queue.updatePriority('a', 100);\n expect((await queue.dequeue())!.id).toBe('a');\n });\n\n it('remove returns false for missing task', async () => {\n expect(await queue.remove('ghost')).toBe(false);\n });\n});\n\ndescribe('TaskManager', () => {\n let manager: TaskManager;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(() => {\n bus = createEventBus();\n manager = new TaskManager(bus);\n });\n\n it('createTask emits TASK_CREATED event', async () => {\n const handler = vi.fn();\n bus.on('task:created', handler);\n await manager.createTask({ type: 'work', description: 'Do stuff' });\n expect(handler).toHaveBeenCalled();\n });\n\n it('assignTask throws for unknown task', async () => {\n await expect(manager.assignTask('ghost', 'agent-1')).rejects.toThrow('Task not found');\n });\n\n it('getTasks filters by status', async () => {\n const t1 = await manager.createTask({ type: 'a', description: 'd' });\n await manager.createTask({ type: 'b', description: 'd' });\n await manager.assignTask(t1.id, 'agent-1');\n const assigned = manager.getTasks({ status: 'assigned' });\n expect(assigned).toHaveLength(1);\n expect(assigned[0].id).toBe(t1.id);\n });\n\n it('getMetrics tracks completed and failed counts', async () => {\n const t = await manager.createTask({ type: 'x', description: 'd' });\n await manager.completeTask(t.id, { taskId: t.id, success: true, duration: 100 });\n const m = manager.getMetrics();\n expect(m.completedTasks).toBe(1);\n expect(m.totalTasks).toBe(1);\n });\n});\n```\n\n---\n\n## Priority Order for Implementation\n\n| Priority | Module | Risk if untested |\n|---|---|---|\n| **Critical** | `src/security/input-validation.ts` | Security bugs go undetected (path traversal, injection) |\n| **Critical** | `src/resilience/circuit-breaker.ts` | State machine bugs cause cascading failures in prod |\n| **High** | `src/core/event-bus.ts` | Core pub/sub is foundational — silent dispatch bugs |\n| **High** | `src/core/config/validator.ts` | Invalid configs silently accepted or cryptically rejected |\n| **High** | `src/resilience/retry.ts` | Retry exhaustion / non-retryable filtering bugs |\n| **Medium** | `src/core/orchestrator/event-coordinator.ts` | Event routing gaps not caught |\n| **Medium** | `src/core/orchestrator/task-manager.ts` | TaskQueue priority ordering bugs |\n| **Medium** | `src/resilience/bulkhead.ts` + `rate-limiter.ts` | Resource protection failures |\n| **Lower** | `src/security/secure-random.ts` | Wraps well-tested Node crypto — lower risk |\n| **Lower** | Event-store edge cases | Functional core is tested; edge cases are non-critical |\n",
6
+ "parsedOutput": {
7
+ "sections": [
8
+ {
9
+ "title": "Test Coverage Gap Analysis",
10
+ "content": "\n",
11
+ "level": 2
12
+ },
13
+ {
14
+ "title": "Summary",
15
+ "content": "\nThe codebase has **5 existing test files** covering hooks and event-store. **11 source modules have zero test coverage**, and gaps exist inside the tested modules.\n\n---\n\n",
16
+ "level": 3
17
+ },
18
+ {
19
+ "title": "1. Entire Modules With No Tests",
20
+ "content": "\n",
21
+ "level": 2
22
+ },
23
+ {
24
+ "title": "1a. `src/core/event-bus.ts` — EventBus",
25
+ "content": "\nZero coverage on the core pub/sub system. Missing:\n- Basic `emit` / `on` dispatch\n- `emitAsync` waits for all handlers\n- `once` fires exactly once\n- `off` removes specific handlers\n- `removeAllListeners` scoped vs full\n- `subscribe` with `IEventFilter` (type + source + priority)\n- `IEventSubscription.pause()` / `resume()` suppresses/re-enables dispatch\n- Wildcard `*` handler receives all event types\n- Async handler errors are caught and logged (don't throw)\n\n```ts\n// __tests__/core/event-bus.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventBus, createEventBus } from '../../src/core/event-bus.js';\n\ndescribe('EventBus', () => {\n let bus: EventBus;\n beforeEach(() => { bus = createEventBus(); });\n\n describe('emit / on', () => {\n it('dispatches payload to subscriber', () => {\n const handler = vi.fn();\n bus.on('test:event', handler);\n bus.emit('test:event', { value: 42 });\n expect(handler).toHaveBeenCalledOnce();\n expect(handler.mock.calls[0][0].payload).toEqual({ value: 42 });\n });\n\n it('does not dispatch to wrong type', () => {\n const handler = vi.fn();\n bus.on('other:event', handler);\n bus.emit('test:event', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('dispatches to wildcard subscribers', () => {\n const handler = vi.fn();\n bus.on('*', handler);\n bus.emit('anything', {});\n expect(handler).toHaveBeenCalled();\n });\n });\n\n describe('emitAsync', () => {\n it('waits for async handlers before resolving', async () => {\n const order: number[] = [];\n bus.on('ev', async () => { await new Promise(r => setTimeout(r, 10)); order.push(1); });\n await bus.emitAsync('ev', {});\n order.push(2);\n expect(order[0]).toBe(1);\n });\n });\n\n describe('once', () => {\n it('fires handler exactly once', async () => {\n const handler = vi.fn();\n bus.once('ev', handler);\n bus.emit('ev', {});\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n });\n\n describe('off', () => {\n it('removes specific handler', () => {\n const handler = vi.fn();\n bus.on('ev', handler);\n bus.off('ev', handler);\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('removeAllListeners', () => {\n it('removes all handlers for a type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('ev', h1); bus.on('ev', h2);\n bus.removeAllListeners('ev');\n bus.emit('ev', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n\n it('removes all handlers when called without type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('a', h1); bus.on('b', h2);\n bus.removeAllListeners();\n bus.emit('a', {}); bus.emit('b', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n });\n\n describe('subscription lifecycle', () => {\n it('pause suppresses dispatch, resume re-enables', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.pause();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n sub.resume();\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('unsubscribe removes handler', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.unsubscribe();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('listenerCount / eventNames', () => {\n it('tracks listener counts', () => {\n bus.on('ev', vi.fn()); bus.on('ev', vi.fn());\n expect(bus.listenerCount('ev')).toBe(2);\n expect(bus.listenerCount('other')).toBe(0);\n });\n\n it('returns registered event names', () => {\n bus.on('a', vi.fn()); bus.on('b', vi.fn());\n expect(bus.eventNames()).toContain('a');\n expect(bus.eventNames()).toContain('b');\n });\n });\n\n describe('error handling', () => {\n it('catches errors in async handlers without throwing', async () => {\n bus.on('ev', async () => { throw new Error('boom'); });\n await expect(bus.emitAsync('ev', {})).resolves.toBeUndefined();\n });\n });\n});\n```\n\n---\n\n",
26
+ "level": 3
27
+ },
28
+ {
29
+ "title": "1b. `src/core/config/` — Config Module",
30
+ "content": "\nNo tests for `validator.ts`, `defaults.ts`, or `loader.ts`. Missing:\n\n- `validateAgentConfig` / `validateTaskConfig` / `validateSwarmConfig` — valid and invalid inputs\n- `ConfigValidator.validateOrThrow()` throws with readable message on invalid data\n- `ConfigValidator.isValid()` returns boolean\n- Schema defaults are applied (e.g., `capabilities` defaults to `[]` when omitted)\n- `mergeWithDefaults` deep-merges properly\n- `ConfigLoader.load()` returns defaults when no file found\n- `ConfigLoader.load()` merges env vars (`CLAUDE_FLOW_MAX_AGENTS`, `CLAUDE_FLOW_MEMORY_TYPE`)\n- `ConfigLoader.load()` warns about invalid JSON config file\n\n```ts\n// __tests__/core/config/validator.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateAgentConfig, validateTaskConfig, validateSwarmConfig,\n validateMemoryConfig, validateSystemConfig, ConfigValidator,\n} from '../../../src/core/config/index.js';\n\ndescribe('validateAgentConfig', () => {\n it('accepts valid agent config', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'Coder', type: 'coder' });\n expect(result.success).toBe(true);\n expect(result.data?.capabilities).toEqual([]); // default applied\n expect(result.data?.priority).toBe(50); // default\n });\n\n it('rejects missing required fields', () => {\n const result = validateAgentConfig({ name: 'Coder' });\n expect(result.success).toBe(false);\n expect(result.errors?.some(e => e.path === 'id')).toBe(true);\n });\n\n it('rejects priority out of range', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'X', type: 'coder', priority: 101 });\n expect(result.success).toBe(false);\n });\n});\n\ndescribe('validateSwarmConfig', () => {\n it('rejects unknown topology', () => {\n const result = validateSwarmConfig({ topology: 'unknown' });\n expect(result.success).toBe(false);\n });\n\n it('accepts all valid topologies', () => {\n const topologies = ['hierarchical', 'mesh', 'ring', 'star', 'adaptive', 'hierarchical-mesh'];\n for (const topology of topologies) {\n expect(validateSwarmConfig({ topology }).success).toBe(true);\n }\n });\n});\n\ndescribe('ConfigValidator', () => {\n it('validateOrThrow throws with readable message', () => {\n expect(() => ConfigValidator.validateAgentOrThrow({ name: 'X' }))\n .toThrow('Invalid agent configuration');\n });\n\n it('isValid returns false for invalid data', () => {\n const { AgentConfigSchema } = require('../../../src/core/config/schema.js');\n expect(ConfigValidator.isValid(AgentConfigSchema, {})).toBe(false);\n });\n});\n```\n\n```ts\n// __tests__/core/config/defaults.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { mergeWithDefaults, agentTypePresets, defaultSystemConfig } from '../../../src/core/config/defaults.js';\n\ndescribe('mergeWithDefaults', () => {\n it('applies defaults for missing keys', () => {\n const result = mergeWithDefaults({ a: 1 } as any, { a: 0, b: 2 } as any);\n expect(result.a).toBe(1);\n expect(result.b).toBe(2);\n });\n\n it('override wins over default', () => {\n const result = mergeWithDefaults({ key: 'override' } as any, { key: 'default' } as any);\n expect(result.key).toBe('override');\n });\n});\n\ndescribe('agentTypePresets', () => {\n it('has preset for all core agent types', () => {\n const expectedTypes = ['coder', 'reviewer', 'tester', 'researcher', 'planner', 'coordinator'];\n for (const type of expectedTypes) {\n expect(agentTypePresets[type]).toBeDefined();\n }\n });\n\n it('coordinator has highest priority', () => {\n expect(agentTypePresets.coordinator!.priority).toBeGreaterThan(\n agentTypePresets.coder!.priority!\n );\n });\n});\n```\n\n---\n\n",
31
+ "level": 3
32
+ },
33
+ {
34
+ "title": "1c. `src/resilience/circuit-breaker.ts` — CircuitBreaker",
35
+ "content": "\nZero coverage. Missing state transitions: `CLOSED → OPEN → HALF_OPEN → CLOSED`.\n\n```ts\n// __tests__/resilience/circuit-breaker.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { CircuitBreaker, CircuitBreakerState } from '../../src/resilience/circuit-breaker.js';\n\ndescribe('CircuitBreaker', () => {\n let breaker: CircuitBreaker;\n\n beforeEach(() => {\n breaker = new CircuitBreaker({\n name: 'test',\n failureThreshold: 3,\n successThreshold: 2,\n timeout: 100,\n rollingWindow: 5000,\n volumeThreshold: 3,\n });\n });\n\n it('starts in CLOSED state', () => {\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('executes function successfully in CLOSED state', async () => {\n const result = await breaker.execute(() => Promise.resolve('ok'));\n expect(result).toBe('ok');\n });\n\n it('transitions to OPEN after failure threshold is met', async () => {\n const failFn = () => Promise.reject(new Error('fail'));\n for (let i = 0; i < 3; i++) {\n await breaker.execute(failFn).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('rejects immediately when OPEN without fallback', async () => {\n // Force open via failures\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await expect(breaker.execute(() => Promise.resolve('ok')))\n .rejects.toThrow(\"Circuit breaker 'test' is open\");\n expect(breaker.getStats().rejectedRequests).toBe(1);\n });\n\n it('uses fallback when OPEN', async () => {\n const breakerWithFallback = new CircuitBreaker({\n name: 'fb', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n fallback: () => 'fallback-value',\n });\n await breakerWithFallback.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n const result = await breakerWithFallback.execute(() => Promise.resolve('ok'));\n expect(result).toBe('fallback-value');\n });\n\n it('transitions OPEN → HALF_OPEN after timeout', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n await new Promise(r => setTimeout(r, 150));\n expect(breaker.getState()).toBe(CircuitBreakerState.HALF_OPEN);\n });\n\n it('transitions HALF_OPEN → CLOSED after success threshold', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n // Now in HALF_OPEN — 2 successes needed\n await breaker.execute(() => Promise.resolve('ok'));\n await breaker.execute(() => Promise.resolve('ok'));\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('transitions HALF_OPEN → OPEN on failure', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n await breaker.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('respects custom isFailure predicate', async () => {\n const selective = new CircuitBreaker({\n name: 'sel', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n isFailure: (e) => e.message === 'real-failure',\n });\n // This error is ignored\n await selective.execute(() => Promise.reject(new Error('ignore'))).catch(() => {});\n expect(selective.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('reset returns to CLOSED', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n breaker.reset();\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('emits stateChange event', async () => {\n const listener = vi.fn();\n breaker.on('stateChange', listener);\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(listener).toHaveBeenCalledWith(\n expect.objectContaining({ from: CircuitBreakerState.CLOSED, to: CircuitBreakerState.OPEN })\n );\n });\n\n it('does not trip below volumeThreshold', async () => {\n // volumeThreshold = 3, failureThreshold = 3\n // Only 2 requests — should not trip\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n});\n```\n\n---\n\n",
36
+ "level": 3
37
+ },
38
+ {
39
+ "title": "1d. `src/resilience/retry.ts` — Retry",
40
+ "content": "\n```ts\n// __tests__/resilience/retry.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { retry, withRetry, RetryableErrors } from '../../src/resilience/retry.js';\n\ndescribe('retry', () => {\n it('returns result on first success', async () => {\n const fn = vi.fn(() => Promise.resolve('value'));\n const result = await retry(fn, { maxAttempts: 3 });\n expect(result.success).toBe(true);\n expect(result.result).toBe('value');\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('retries on failure and eventually succeeds', async () => {\n let calls = 0;\n const fn = vi.fn(() => {\n calls++;\n if (calls < 3) return Promise.reject(new Error('fail'));\n return Promise.resolve('ok');\n });\n const result = await retry(fn, { maxAttempts: 5, initialDelay: 1 });\n expect(result.success).toBe(true);\n expect(result.attempts).toBe(3);\n });\n\n it('returns failure after exhausting all attempts', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('always fails')));\n const result = await retry(fn, { maxAttempts: 3, initialDelay: 1 });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(3);\n expect(result.errors).toHaveLength(3);\n });\n\n it('stops immediately for non-retryable errors', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('permanent')));\n const result = await retry(fn, {\n maxAttempts: 5,\n initialDelay: 1,\n retryableErrors: (e) => e.message !== 'permanent',\n });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('calls onRetry callback with attempt info', async () => {\n const onRetry = vi.fn();\n const fn = vi.fn()\n .mockRejectedValueOnce(new Error('fail'))\n .mockResolvedValueOnce('ok');\n await retry(fn, { maxAttempts: 3, initialDelay: 1, onRetry });\n expect(onRetry).toHaveBeenCalledOnce();\n expect(onRetry.mock.calls[0][1]).toBe(1); // attempt number\n });\n\n it('respects maxDelay cap', async () => {\n const delays: number[] = [];\n const fn = vi.fn(() => Promise.reject(new Error('fail')));\n const onRetry = (_: Error, __: number, delay: number) => delays.push(delay);\n await retry(fn, {\n maxAttempts: 5, initialDelay: 1000, backoffMultiplier: 10,\n maxDelay: 500, jitter: 0, onRetry,\n });\n expect(delays.every(d => d <= 500)).toBe(true);\n });\n});\n\ndescribe('RetryableErrors', () => {\n it('network detects ECONNRESET', () => {\n expect(RetryableErrors.network(new Error('ECONNRESET'))).toBe(true);\n expect(RetryableErrors.network(new Error('syntax error'))).toBe(false);\n });\n\n it('rateLimit detects 429', () => {\n expect(RetryableErrors.rateLimit(new Error('429 Too Many'))).toBe(true);\n });\n\n it('transient combines network + rateLimit + 5xx', () => {\n expect(RetryableErrors.transient(new Error('ETIMEDOUT'))).toBe(true);\n expect(RetryableErrors.transient(new Error('500 Internal Server Error'))).toBe(true);\n expect(RetryableErrors.transient(new Error('validation error'))).toBe(false);\n });\n});\n```\n\n---\n\n",
41
+ "level": 3
42
+ },
43
+ {
44
+ "title": "1e. `src/resilience/bulkhead.ts` — Bulkhead",
45
+ "content": "\n```ts\n// __tests__/resilience/bulkhead.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { Bulkhead, createSemaphore } from '../../src/resilience/bulkhead.js';\n\ndescribe('Bulkhead', () => {\n it('executes functions up to maxConcurrent immediately', async () => {\n const bh = new Bulkhead({ name: 'test', maxConcurrent: 2, maxQueue: 5, queueTimeout: 1000 });\n const results = await Promise.all([\n bh.execute(() => Promise.resolve(1)),\n bh.execute(() => Promise.resolve(2)),\n ]);\n expect(results).toEqual([1, 2]);\n expect(bh.getStats().completed).toBe(2);\n });\n\n it('queues excess requests and processes them', async () => {\n const bh = new Bulkhead({ name: 'bh', maxConcurrent: 1, maxQueue: 5, queueTimeout: 1000 });\n let resolve1!: () => void;\n const p1 = new Promise<void>(r => { resolve1 = r; });\n\n const result1 = bh.execute(() => p1.then(() => 'first'));\n const result2 = bh.execute(() => Promise.resolve('second'));\n\n resolve1();\n const [r1, r2] = await Promise.all([result1, result2]);\n expect(r1).toBe('first');\n expect(r2).toBe('second');\n });\n\n it('rejects when queue is full', async () => {\n const bh = new Bulkhead({ name: 'full', maxConcurrent: 1, maxQueue: 1, queueTimeout: 5000 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker); // occupies concurrency slot\n bh.execute(() => Promise.resolve('queued')); // fills queue\n\n await expect(bh.execute(() => Promise.resolve('overflow')))\n .rejects.toThrow(\"Bulkhead 'full' is full\");\n expect(bh.getStats().rejected).toBe(1);\n\n resolve();\n });\n\n it('times out queued items', async () => {\n const bh = new Bulkhead({ name: 'to', maxConcurrent: 1, maxQueue: 5, queueTimeout: 50 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker);\n const queued = bh.execute(() => Promise.resolve('ok'));\n\n await expect(queued).rejects.toThrow('queue timeout');\n expect(bh.getStats().timedOut).toBe(1);\n\n resolve();\n });\n\n it('calls onRejected callback on full', async () => {\n const onRejected = vi.fn();\n const bh = new Bulkhead({ name: 'cb', maxConcurrent: 1, maxQueue: 0, queueTimeout: 1000, onRejected });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n bh.execute(() => blocker);\n await bh.execute(() => Promise.resolve()).catch(() => {});\n expect(onRejected).toHaveBeenCalledWith('full');\n resolve();\n });\n});\n\ndescribe('createSemaphore', () => {\n it('limits concurrent access', async () => {\n const sem = createSemaphore(2);\n let active = 0;\n let maxActive = 0;\n\n const task = async () => {\n await sem.acquire();\n active++;\n maxActive = Math.max(maxActive, active);\n await new Promise(r => setTimeout(r, 10));\n active--;\n sem.release();\n };\n\n await Promise.all([task(), task(), task(), task()]);\n expect(maxActive).toBeLessThanOrEqual(2);\n });\n});\n```\n\n---\n\n",
46
+ "level": 3
47
+ },
48
+ {
49
+ "title": "1f. `src/resilience/rate-limiter.ts` — SlidingWindowRateLimiter & TokenBucketRateLimiter",
50
+ "content": "\n```ts\n// __tests__/resilience/rate-limiter.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { SlidingWindowRateLimiter, TokenBucketRateLimiter } from '../../src/resilience/rate-limiter.js';\n\ndescribe('SlidingWindowRateLimiter', () => {\n it('allows requests up to limit', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 3, windowMs: 10000 });\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false); // 4th blocked\n limiter.destroy();\n });\n\n it('tracks remaining and used counts', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 5, windowMs: 10000 });\n limiter.consume('k');\n limiter.consume('k');\n const r = limiter.consume('k');\n expect(r.used).toBe(3);\n expect(r.remaining).toBe(2);\n limiter.destroy();\n });\n\n it('check does not consume a token', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.check('k'); // peek only\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false);\n limiter.destroy();\n });\n\n it('reset clears a key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.consume('k');\n limiter.reset('k');\n expect(limiter.consume('k').allowed).toBe(true);\n limiter.destroy();\n });\n\n it('tracks separately by key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n expect(limiter.consume('user-1').allowed).toBe(true);\n expect(limiter.consume('user-1').allowed).toBe(false);\n expect(limiter.consume('user-2').allowed).toBe(true); // separate key\n limiter.destroy();\n });\n});\n\ndescribe('TokenBucketRateLimiter', () => {\n it('starts with full bucket', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 5, windowMs: 1000 });\n const r = limiter.check();\n expect(r.remaining).toBe(5);\n expect(r.allowed).toBe(true);\n limiter.destroy();\n });\n\n it('consumes tokens on each request', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 2, windowMs: 10000 });\n limiter.consume();\n const r = limiter.consume();\n expect(r.remaining).toBe(0);\n expect(limiter.consume().allowed).toBe(false);\n limiter.destroy();\n });\n});\n```\n\n---\n\n",
51
+ "level": 3
52
+ },
53
+ {
54
+ "title": "1g. `src/security/input-validation.ts`",
55
+ "content": "\nZero coverage on all validation functions.\n\n```ts\n// __tests__/security/input-validation.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateInput, sanitizeString, validatePath,\n validateCommand, validateTags, isValidIdentifier, escapeForSql,\n} from '../../src/security/input-validation.js';\n\ndescribe('validateInput', () => {\n it('returns valid: true for valid string', () => {\n const r = validateInput('hello');\n expect(r.valid).toBe(true);\n expect(r.sanitized).toBe('hello');\n });\n\n it('trims whitespace by default', () => {\n expect(validateInput(' hi ').sanitized).toBe('hi');\n });\n\n it('returns error for null when required', () => {\n const r = validateInput(null, { required: true });\n expect(r.valid).toBe(false);\n expect(r.error).toMatch(/required/i);\n });\n\n it('returns valid for null when not required', () => {\n expect(validateInput(null).valid).toBe(true);\n });\n\n it('rejects non-string values', () => {\n expect(validateInput(42).valid).toBe(false);\n });\n\n it('enforces minLength', () => {\n expect(validateInput('hi', { minLength: 5 }).valid).toBe(false);\n });\n\n it('enforces maxLength', () => {\n expect(validateInput('hello world', { maxLength: 5 }).valid).toBe(false);\n });\n\n it('validates against pattern', () => {\n const r = validateInput('abc123', { pattern: /^\\d+$/ });\n expect(r.valid).toBe(false);\n });\n});\n\ndescribe('validatePath', () => {\n it('blocks path traversal via ..', () => {\n expect(validatePath('../etc/passwd').valid).toBe(false);\n });\n\n it('blocks tilde home directory reference', () => {\n expect(validatePath('~/secret').valid).toBe(false);\n });\n\n it('blocks paths outside allowed base', () => {\n expect(validatePath('/etc/passwd', '/home').valid).toBe(false);\n });\n\n it('blocks null bytes', () => {\n expect(validatePath('/tmp/file\\x00.txt').valid).toBe(false);\n });\n\n it('accepts valid relative path', () => {\n expect(validatePath('src/utils.ts').valid).toBe(true);\n });\n\n it('blocks excessively long paths', () => {\n expect(validatePath('a'.repeat(4097)).valid).toBe(false);\n });\n});\n\ndescribe('validateCommand', () => {\n it('rejects shell operators', () => {\n expect(validateCommand('echo foo; rm -rf /').valid).toBe(false);\n expect(validateCommand('ls && cat /etc/passwd').valid).toBe(false);\n expect(validateCommand('curl url | bash').valid).toBe(false);\n });\n\n it('rejects command substitution', () => {\n expect(validateCommand('echo $(whoami)').valid).toBe(false);\n });\n\n it('rejects when not in whitelist', () => {\n const r = validateCommand('rm -rf /', ['ls', 'cat']);\n expect(r.valid).toBe(false);\n expect(r.error).toContain('not allowed');\n });\n\n it('accepts safe whitelisted command', () => {\n expect(validateCommand('ls -la', ['ls']).valid).toBe(true);\n });\n\n it('rejects empty command', () => {\n expect(validateCommand(' ').valid).toBe(false);\n });\n});\n\ndescribe('validateTags', () => {\n it('accepts valid tags', () => {\n const r = validateTags(['alpha', 'beta-v2', 'prod:feature']);\n expect(r.valid).toBe(true);\n expect(r.sanitized).toHaveLength(3);\n });\n\n it('rejects non-array', () => {\n expect(validateTags('tag').valid).toBe(false);\n });\n\n it('rejects tags with spaces', () => {\n expect(validateTags(['invalid tag']).valid).toBe(false);\n });\n\n it('rejects tags over 100 chars', () => {\n expect(validateTags(['x'.repeat(101)]).valid).toBe(false);\n });\n\n it('skips empty tags', () => {\n const r = validateTags([' ', 'valid']);\n expect(r.valid).toBe(true);\n expect((r.sanitized as string[]).length).toBe(1);\n });\n});\n\ndescribe('isValidIdentifier', () => {\n it('accepts valid identifiers', () => {\n expect(isValidIdentifier('myAgent_1')).toBe(true);\n expect(isValidIdentifier('_private')).toBe(true);\n });\n\n it('rejects identifiers starting with digits', () => {\n expect(isValidIdentifier('1agent')).toBe(false);\n });\n\n it('rejects identifiers over 256 chars', () => {\n expect(isValidIdentifier('a'.repeat(257))).toBe(false);\n });\n});\n```\n\n---\n\n",
56
+ "level": 3
57
+ },
58
+ {
59
+ "title": "1h. `src/security/secure-random.ts`",
60
+ "content": "\n```ts\n// __tests__/security/secure-random.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n generateSecureId, generateUUID, generateSecureToken,\n generateShortId, generateSessionId, secureRandomInt, secureRandomChoice, secureShuffleArray,\n} from '../../src/security/secure-random.js';\n\ndescribe('generateSecureId', () => {\n it('generates unique IDs', () => {\n const ids = new Set(Array.from({ length: 100 }, () => generateSecureId()));\n expect(ids.size).toBe(100);\n });\n\n it('applies prefix', () => {\n expect(generateSecureId('agent')).toMatch(/^agent_/);\n });\n});\n\ndescribe('generateUUID', () => {\n it('generates valid UUID v4 format', () => {\n const uuid = generateUUID();\n expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);\n });\n});\n\ndescribe('secureRandomInt', () => {\n it('returns value within [min, max]', () => {\n for (let i = 0; i < 100; i++) {\n const v = secureRandomInt(5, 10);\n expect(v).toBeGreaterThanOrEqual(5);\n expect(v).toBeLessThanOrEqual(10);\n }\n });\n\n it('handles single-value range', () => {\n expect(secureRandomInt(7, 7)).toBe(7);\n });\n});\n\ndescribe('secureRandomChoice', () => {\n it('selects element from array', () => {\n const arr = ['a', 'b', 'c'];\n const result = secureRandomChoice(arr);\n expect(arr).toContain(result);\n });\n\n it('throws on empty array', () => {\n expect(() => secureRandomChoice([])).toThrow();\n });\n});\n\ndescribe('secureShuffleArray', () => {\n it('returns array with same elements', () => {\n const arr = [1, 2, 3, 4, 5];\n const shuffled = secureShuffleArray(arr);\n expect(shuffled).toHaveLength(arr.length);\n expect(shuffled.sort()).toEqual([...arr].sort());\n });\n\n it('does not mutate original array', () => {\n const arr = [1, 2, 3];\n const orig = [...arr];\n secureShuffleArray(arr);\n expect(arr).toEqual(orig);\n });\n});\n```\n\n---\n\n",
61
+ "level": 3
62
+ },
63
+ {
64
+ "title": "2. Gaps in Existing Tests",
65
+ "content": "\n",
66
+ "level": 2
67
+ },
68
+ {
69
+ "title": "2a. `src/hooks/hooks.test.ts` — HookRegistry & HookExecutor gaps",
70
+ "content": "\n**Untested in `HookRegistry`:**\n- `unregisterAll(event)` removes only that event's hooks\n- `unregisterAll()` removes all hooks and resets counter\n- `getHook(hookId)` returns definition or `undefined`\n- `count(event)` vs `count()` — scoped vs total\n\n**Untested in `HookExecutor`:**\n- `continueChain === false` stops the chain without setting `aborted = true`\n- `collectResults: false` (default) returns empty `results[]`\n- `executeParallel` throws when `events.length !== contexts.length`\n- `executeParallel` respects `maxParallel` batch size (emits in batches, not all at once)\n- `executeSequential` aborts entire chain when intermediate event fails and `continueOnError` is false\n\n```ts\n// Additional tests to add to src/hooks/hooks.test.ts:\n\ndescribe('HookRegistry — missing coverage', () => {\n it('unregisterAll(event) removes only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n\n const removed = registry.unregisterAll(HookEvent.PreToolUse);\n expect(removed).toBe(2);\n expect(registry.count(HookEvent.PreToolUse)).toBe(0);\n expect(registry.count(HookEvent.PostToolUse)).toBe(1);\n });\n\n it('unregisterAll() removes everything', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n const removed = registry.unregisterAll();\n expect(removed).toBe(2);\n expect(registry.count()).toBe(0);\n });\n\n it('getHook returns definition for valid id', () => {\n const handler = vi.fn();\n const id = registry.register(HookEvent.PreEdit, handler);\n const def = registry.getHook(id);\n expect(def).toBeDefined();\n expect(def?.handler).toBe(handler);\n });\n\n it('getHook returns undefined for unknown id', () => {\n expect(registry.getHook('no-such-id')).toBeUndefined();\n });\n\n it('count(event) counts only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n expect(registry.count(HookEvent.PreToolUse)).toBe(2);\n expect(registry.count()).toBe(3);\n });\n});\n\ndescribe('HookExecutor — missing coverage', () => {\n it('continueChain=false stops chain but aborted remains false', async () => {\n const h1 = vi.fn(async () => ({ success: true }));\n const h2 = vi.fn(async () => ({ success: true, continueChain: false }));\n const h3 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1, HookPriority.High);\n registry.register(HookEvent.PreEdit, h2, HookPriority.Normal);\n registry.register(HookEvent.PreEdit, h3, HookPriority.Low);\n\n const result = await executor.execute(HookEvent.PreEdit, { event: HookEvent.PreEdit, timestamp: new Date() });\n expect(result.hooksExecuted).toBe(2);\n expect(h3).not.toHaveBeenCalled();\n expect(result.aborted).toBe(false); // chain stopped but not aborted\n });\n\n it('collectResults=false returns empty results array', async () => {\n registry.register(HookEvent.PreEdit, async () => ({ success: true }));\n const result = await executor.execute(\n HookEvent.PreEdit,\n { event: HookEvent.PreEdit, timestamp: new Date() },\n { collectResults: false }\n );\n expect(result.results).toHaveLength(0);\n });\n\n it('executeParallel throws when arrays have different lengths', async () => {\n await expect(\n executor.executeParallel(\n [HookEvent.PreEdit],\n [{ event: HookEvent.PreEdit, timestamp: new Date() }, { event: HookEvent.PostEdit, timestamp: new Date() }]\n )\n ).rejects.toThrow('same length');\n });\n\n it('executeSequential aborts after failing event when continueOnError is false', async () => {\n const h1 = vi.fn(async () => { throw new Error('fail'); });\n const h2 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1);\n registry.register(HookEvent.PostEdit, h2);\n\n const result = await executor.executeSequential(\n [HookEvent.PreEdit, HookEvent.PostEdit],\n { event: HookEvent.PreEdit, timestamp: new Date() }\n );\n expect(result.aborted).toBe(true);\n expect(h2).not.toHaveBeenCalled();\n });\n});\n```\n\n---\n\n",
71
+ "level": 3
72
+ },
73
+ {
74
+ "title": "2b. `src/events/event-store.test.ts` — EventStore gaps",
75
+ "content": "\n**Missing:**\n- `ensureInitialized` throws when called before `initialize()`\n- `afterTimestamp` / `beforeTimestamp` filters in `query()`\n- `fromVersion` filter in `query()`\n- `snapshot:recommended` event emitted when version hits `snapshotThreshold`\n- `event:appended` event fired per append\n- `getSnapshot` returns `null` for unknown aggregate\n- Stats `oldestEvent` / `newestEvent` set after appending\n- Stats `snapshotCount` increments after `saveSnapshot`\n\n```ts\n// Additional tests to add to src/events/event-store.test.ts:\n\ndescribe('EventStore — missing coverage', () => {\n it('throws when methods called before initialize()', async () => {\n const uninit = new EventStore({ databasePath: ':memory:' });\n await expect(uninit.append(createAgentSpawnedEvent('a', 'coder', 'core', [])))\n .rejects.toThrow('not initialized');\n });\n\n it('emits event:appended for each append', async () => {\n const handler = vi.fn();\n eventStore.on('event:appended', handler);\n await eventStore.append(createAgentSpawnedEvent('a1', 'coder', 'core', []));\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('emits snapshot:recommended when version hits threshold', async () => {\n const store = new EventStore({ databasePath: ':memory:', snapshotThreshold: 2 });\n await store.initialize();\n const handler = vi.fn();\n store.on('snapshot:recommended', handler);\n\n await store.append(createAgentSpawnedEvent('agg', 'coder', 'core', []));\n await store.append(createAgentStartedEvent('agg'));\n expect(handler).toHaveBeenCalledOnce();\n await store.shutdown();\n });\n\n it('query filters by afterTimestamp', async () => {\n const before = Date.now();\n await new Promise(r => setTimeout(r, 5));\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const events = await eventStore.query({ afterTimestamp: before });\n expect(events).toHaveLength(1);\n });\n\n it('query filters by beforeTimestamp', async () => {\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const after = Date.now() + 1000;\n const events = await eventStore.query({ beforeTimestamp: after });\n expect(events.length).toBeGreaterThan(0);\n const noEvents = await eventStore.query({ beforeTimestamp: 0 });\n expect(noEvents).toHaveLength(0);\n });\n\n it('query filters by fromVersion', async () => {\n await eventStore.append(createAgentSpawnedEvent('v', 'coder', 'core', []));\n await eventStore.append(createAgentStartedEvent('v'));\n const events = await eventStore.query({ aggregateIds: ['v'], fromVersion: 2 });\n expect(events).toHaveLength(1);\n expect(events[0].version).toBe(2);\n });\n\n it('getSnapshot returns null for unknown aggregate', async () => {\n const snap = await eventStore.getSnapshot('no-such-aggregate');\n expect(snap).toBeNull();\n });\n\n it('stats show oldest/newest timestamps after appending', async () => {\n await eventStore.append(createAgentSpawnedEvent('a', 'coder', 'core', []));\n const stats = await eventStore.getStats();\n expect(stats.oldestEvent).not.toBeNull();\n expect(stats.newestEvent).not.toBeNull();\n expect(stats.newestEvent!).toBeGreaterThanOrEqual(stats.oldestEvent!);\n });\n\n it('stats count snapshots', async () => {\n await eventStore.saveSnapshot({\n aggregateId: 'a1', aggregateType: 'agent',\n version: 1, state: {}, timestamp: Date.now(),\n });\n const stats = await eventStore.getStats();\n expect(stats.snapshotCount).toBe(1);\n });\n});\n```\n\n---\n\n",
76
+ "level": 3
77
+ },
78
+ {
79
+ "title": "2c. `src/core/orchestrator/event-coordinator.ts` gaps",
80
+ "content": "\nThe `EventCoordinator` class has no test file at all.\n\n```ts\n// __tests__/core/orchestrator/event-coordinator.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventCoordinator } from '../../../src/core/orchestrator/event-coordinator.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\nimport { SystemEventTypes } from '../../../src/core/interfaces/event.interface.js';\n\ndescribe('EventCoordinator', () => {\n let coordinator: EventCoordinator;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(async () => {\n bus = createEventBus();\n coordinator = new EventCoordinator(bus);\n await coordinator.initialize();\n });\n\n afterEach(async () => { await coordinator.shutdown(); });\n\n it('registers handlers and routes events', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('test:event', handler);\n await bus.emitAsync('test:event', { data: 1 });\n expect(handler).toHaveBeenCalled();\n });\n\n it('unregisters handlers', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('ev', handler);\n coordinator.unregisterHandler('ev', handler);\n await bus.emitAsync('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('initialize is idempotent', async () => {\n await coordinator.initialize(); // second call — should not throw or double-register\n expect(coordinator.isInitialized()).toBe(true);\n });\n\n it('shutdown clears handlers', async () => {\n coordinator.registerHandler('ev', vi.fn());\n await coordinator.shutdown();\n expect(coordinator.getRegisteredTypes()).toHaveLength(0);\n expect(coordinator.isInitialized()).toBe(false);\n });\n\n it('getHandlerCount returns correct count', () => {\n coordinator.registerHandler('ev', vi.fn());\n coordinator.registerHandler('ev', vi.fn());\n expect(coordinator.getHandlerCount('ev')).toBe(2);\n expect(coordinator.getHandlerCount('other')).toBe(0);\n });\n\n it('handler errors do not throw from route()', async () => {\n coordinator.registerHandler('ev', async () => { throw new Error('boom'); });\n await expect(coordinator.route({ id: '1', type: 'ev', timestamp: new Date(), source: 'test', payload: {} }))\n .resolves.toBeUndefined();\n });\n});\n```\n\n---\n\n",
81
+ "level": 3
82
+ },
83
+ {
84
+ "title": "3. Integration Test Gaps",
85
+ "content": "\nThese cross-cutting scenarios are not covered anywhere:\n\n| Scenario | Missing Test |\n|---|---|\n| Config load → validate → use in TaskManager | No integration test |\n| EventBus ↔ EventCoordinator: coordinator routes events dispatched on bus | No integration test |\n| HookExecutor with real EventBus: `hooks:error` event emitted on handler exception | Partially in hooks.test.ts but only verifies execution, not bus event shape |\n| TaskManager.cleanup() removes tasks older than threshold | TaskManager has no tests at all |\n| TaskQueue priority ordering with `updatePriority()` reordering | No test |\n| SessionHooksManager + InMemorySessionStorage: full round-trip store and list | Partially tested but `list()` not verified independently |\n\n```ts\n// __tests__/core/orchestrator/task-manager.test.ts (skeleton)\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { TaskQueue, TaskManager } from '../../../src/core/orchestrator/task-manager.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\n\ndescribe('TaskQueue', () => {\n let queue: TaskQueue;\n beforeEach(() => { queue = new TaskQueue(); });\n\n it('dequeues in priority order', async () => {\n const make = (id: string, priority: number) =>\n ({ id, type: 'test', description: '', priority, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('low', 10));\n await queue.enqueue(make('high', 90));\n await queue.enqueue(make('mid', 50));\n expect((await queue.dequeue())!.id).toBe('high');\n expect((await queue.dequeue())!.id).toBe('mid');\n expect((await queue.dequeue())!.id).toBe('low');\n });\n\n it('updatePriority re-sorts queue', async () => {\n const make = (id: string, p: number) =>\n ({ id, type: 't', description: '', priority: p, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('a', 10));\n await queue.enqueue(make('b', 20));\n await queue.updatePriority('a', 100);\n expect((await queue.dequeue())!.id).toBe('a');\n });\n\n it('remove returns false for missing task', async () => {\n expect(await queue.remove('ghost')).toBe(false);\n });\n});\n\ndescribe('TaskManager', () => {\n let manager: TaskManager;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(() => {\n bus = createEventBus();\n manager = new TaskManager(bus);\n });\n\n it('createTask emits TASK_CREATED event', async () => {\n const handler = vi.fn();\n bus.on('task:created', handler);\n await manager.createTask({ type: 'work', description: 'Do stuff' });\n expect(handler).toHaveBeenCalled();\n });\n\n it('assignTask throws for unknown task', async () => {\n await expect(manager.assignTask('ghost', 'agent-1')).rejects.toThrow('Task not found');\n });\n\n it('getTasks filters by status', async () => {\n const t1 = await manager.createTask({ type: 'a', description: 'd' });\n await manager.createTask({ type: 'b', description: 'd' });\n await manager.assignTask(t1.id, 'agent-1');\n const assigned = manager.getTasks({ status: 'assigned' });\n expect(assigned).toHaveLength(1);\n expect(assigned[0].id).toBe(t1.id);\n });\n\n it('getMetrics tracks completed and failed counts', async () => {\n const t = await manager.createTask({ type: 'x', description: 'd' });\n await manager.completeTask(t.id, { taskId: t.id, success: true, duration: 100 });\n const m = manager.getMetrics();\n expect(m.completedTasks).toBe(1);\n expect(m.totalTasks).toBe(1);\n });\n});\n```\n\n---\n\n",
86
+ "level": 2
87
+ },
88
+ {
89
+ "title": "Priority Order for Implementation",
90
+ "content": "| Priority | Module | Risk if untested |\n|---|---|---|\n| **Critical** | `src/security/input-validation.ts` | Security bugs go undetected (path traversal, injection) |\n| **Critical** | `src/resilience/circuit-breaker.ts` | State machine bugs cause cascading failures in prod |\n| **High** | `src/core/event-bus.ts` | Core pub/sub is foundational — silent dispatch bugs |\n| **High** | `src/core/config/validator.ts` | Invalid configs silently accepted or cryptically rejected |\n| **High** | `src/resilience/retry.ts` | Retry exhaustion / non-retryable filtering bugs |\n| **Medium** | `src/core/orchestrator/event-coordinator.ts` | Event routing gaps not caught |\n| **Medium** | `src/core/orchestrator/task-manager.ts` | TaskQueue priority ordering bugs |\n| **Medium** | `src/resilience/bulkhead.ts` + `rate-limiter.ts` | Resource protection failures |\n| **Lower** | `src/security/secure-random.ts` | Wraps well-tested Node crypto — lower risk |\n| **Lower** | Event-store edge cases | Functional core is tested; edge cases are non-critical |",
91
+ "level": 2
92
+ }
93
+ ],
94
+ "codeBlocks": [
95
+ {
96
+ "language": "ts",
97
+ "code": "// __tests__/core/event-bus.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventBus, createEventBus } from '../../src/core/event-bus.js';\n\ndescribe('EventBus', () => {\n let bus: EventBus;\n beforeEach(() => { bus = createEventBus(); });\n\n describe('emit / on', () => {\n it('dispatches payload to subscriber', () => {\n const handler = vi.fn();\n bus.on('test:event', handler);\n bus.emit('test:event', { value: 42 });\n expect(handler).toHaveBeenCalledOnce();\n expect(handler.mock.calls[0][0].payload).toEqual({ value: 42 });\n });\n\n it('does not dispatch to wrong type', () => {\n const handler = vi.fn();\n bus.on('other:event', handler);\n bus.emit('test:event', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('dispatches to wildcard subscribers', () => {\n const handler = vi.fn();\n bus.on('*', handler);\n bus.emit('anything', {});\n expect(handler).toHaveBeenCalled();\n });\n });\n\n describe('emitAsync', () => {\n it('waits for async handlers before resolving', async () => {\n const order: number[] = [];\n bus.on('ev', async () => { await new Promise(r => setTimeout(r, 10)); order.push(1); });\n await bus.emitAsync('ev', {});\n order.push(2);\n expect(order[0]).toBe(1);\n });\n });\n\n describe('once', () => {\n it('fires handler exactly once', async () => {\n const handler = vi.fn();\n bus.once('ev', handler);\n bus.emit('ev', {});\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n });\n\n describe('off', () => {\n it('removes specific handler', () => {\n const handler = vi.fn();\n bus.on('ev', handler);\n bus.off('ev', handler);\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('removeAllListeners', () => {\n it('removes all handlers for a type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('ev', h1); bus.on('ev', h2);\n bus.removeAllListeners('ev');\n bus.emit('ev', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n\n it('removes all handlers when called without type', () => {\n const h1 = vi.fn(), h2 = vi.fn();\n bus.on('a', h1); bus.on('b', h2);\n bus.removeAllListeners();\n bus.emit('a', {}); bus.emit('b', {});\n expect(h1).not.toHaveBeenCalled();\n expect(h2).not.toHaveBeenCalled();\n });\n });\n\n describe('subscription lifecycle', () => {\n it('pause suppresses dispatch, resume re-enables', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.pause();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n sub.resume();\n bus.emit('ev', {});\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('unsubscribe removes handler', () => {\n const handler = vi.fn();\n const sub = bus.on('ev', handler);\n sub.unsubscribe();\n bus.emit('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n });\n\n describe('listenerCount / eventNames', () => {\n it('tracks listener counts', () => {\n bus.on('ev', vi.fn()); bus.on('ev', vi.fn());\n expect(bus.listenerCount('ev')).toBe(2);\n expect(bus.listenerCount('other')).toBe(0);\n });\n\n it('returns registered event names', () => {\n bus.on('a', vi.fn()); bus.on('b', vi.fn());\n expect(bus.eventNames()).toContain('a');\n expect(bus.eventNames()).toContain('b');\n });\n });\n\n describe('error handling', () => {\n it('catches errors in async handlers without throwing', async () => {\n bus.on('ev', async () => { throw new Error('boom'); });\n await expect(bus.emitAsync('ev', {})).resolves.toBeUndefined();\n });\n });\n});"
98
+ },
99
+ {
100
+ "language": "ts",
101
+ "code": "// __tests__/core/config/validator.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateAgentConfig, validateTaskConfig, validateSwarmConfig,\n validateMemoryConfig, validateSystemConfig, ConfigValidator,\n} from '../../../src/core/config/index.js';\n\ndescribe('validateAgentConfig', () => {\n it('accepts valid agent config', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'Coder', type: 'coder' });\n expect(result.success).toBe(true);\n expect(result.data?.capabilities).toEqual([]); // default applied\n expect(result.data?.priority).toBe(50); // default\n });\n\n it('rejects missing required fields', () => {\n const result = validateAgentConfig({ name: 'Coder' });\n expect(result.success).toBe(false);\n expect(result.errors?.some(e => e.path === 'id')).toBe(true);\n });\n\n it('rejects priority out of range', () => {\n const result = validateAgentConfig({ id: 'a1', name: 'X', type: 'coder', priority: 101 });\n expect(result.success).toBe(false);\n });\n});\n\ndescribe('validateSwarmConfig', () => {\n it('rejects unknown topology', () => {\n const result = validateSwarmConfig({ topology: 'unknown' });\n expect(result.success).toBe(false);\n });\n\n it('accepts all valid topologies', () => {\n const topologies = ['hierarchical', 'mesh', 'ring', 'star', 'adaptive', 'hierarchical-mesh'];\n for (const topology of topologies) {\n expect(validateSwarmConfig({ topology }).success).toBe(true);\n }\n });\n});\n\ndescribe('ConfigValidator', () => {\n it('validateOrThrow throws with readable message', () => {\n expect(() => ConfigValidator.validateAgentOrThrow({ name: 'X' }))\n .toThrow('Invalid agent configuration');\n });\n\n it('isValid returns false for invalid data', () => {\n const { AgentConfigSchema } = require('../../../src/core/config/schema.js');\n expect(ConfigValidator.isValid(AgentConfigSchema, {})).toBe(false);\n });\n});"
102
+ },
103
+ {
104
+ "language": "ts",
105
+ "code": "// __tests__/core/config/defaults.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { mergeWithDefaults, agentTypePresets, defaultSystemConfig } from '../../../src/core/config/defaults.js';\n\ndescribe('mergeWithDefaults', () => {\n it('applies defaults for missing keys', () => {\n const result = mergeWithDefaults({ a: 1 } as any, { a: 0, b: 2 } as any);\n expect(result.a).toBe(1);\n expect(result.b).toBe(2);\n });\n\n it('override wins over default', () => {\n const result = mergeWithDefaults({ key: 'override' } as any, { key: 'default' } as any);\n expect(result.key).toBe('override');\n });\n});\n\ndescribe('agentTypePresets', () => {\n it('has preset for all core agent types', () => {\n const expectedTypes = ['coder', 'reviewer', 'tester', 'researcher', 'planner', 'coordinator'];\n for (const type of expectedTypes) {\n expect(agentTypePresets[type]).toBeDefined();\n }\n });\n\n it('coordinator has highest priority', () => {\n expect(agentTypePresets.coordinator!.priority).toBeGreaterThan(\n agentTypePresets.coder!.priority!\n );\n });\n});"
106
+ },
107
+ {
108
+ "language": "ts",
109
+ "code": "// __tests__/resilience/circuit-breaker.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { CircuitBreaker, CircuitBreakerState } from '../../src/resilience/circuit-breaker.js';\n\ndescribe('CircuitBreaker', () => {\n let breaker: CircuitBreaker;\n\n beforeEach(() => {\n breaker = new CircuitBreaker({\n name: 'test',\n failureThreshold: 3,\n successThreshold: 2,\n timeout: 100,\n rollingWindow: 5000,\n volumeThreshold: 3,\n });\n });\n\n it('starts in CLOSED state', () => {\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('executes function successfully in CLOSED state', async () => {\n const result = await breaker.execute(() => Promise.resolve('ok'));\n expect(result).toBe('ok');\n });\n\n it('transitions to OPEN after failure threshold is met', async () => {\n const failFn = () => Promise.reject(new Error('fail'));\n for (let i = 0; i < 3; i++) {\n await breaker.execute(failFn).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('rejects immediately when OPEN without fallback', async () => {\n // Force open via failures\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await expect(breaker.execute(() => Promise.resolve('ok')))\n .rejects.toThrow(\"Circuit breaker 'test' is open\");\n expect(breaker.getStats().rejectedRequests).toBe(1);\n });\n\n it('uses fallback when OPEN', async () => {\n const breakerWithFallback = new CircuitBreaker({\n name: 'fb', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n fallback: () => 'fallback-value',\n });\n await breakerWithFallback.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n const result = await breakerWithFallback.execute(() => Promise.resolve('ok'));\n expect(result).toBe('fallback-value');\n });\n\n it('transitions OPEN → HALF_OPEN after timeout', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n await new Promise(r => setTimeout(r, 150));\n expect(breaker.getState()).toBe(CircuitBreakerState.HALF_OPEN);\n });\n\n it('transitions HALF_OPEN → CLOSED after success threshold', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n // Now in HALF_OPEN — 2 successes needed\n await breaker.execute(() => Promise.resolve('ok'));\n await breaker.execute(() => Promise.resolve('ok'));\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('transitions HALF_OPEN → OPEN on failure', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n await new Promise(r => setTimeout(r, 150));\n await breaker.execute(() => Promise.reject(new Error('fail'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.OPEN);\n });\n\n it('respects custom isFailure predicate', async () => {\n const selective = new CircuitBreaker({\n name: 'sel', failureThreshold: 1, successThreshold: 1,\n timeout: 100, rollingWindow: 5000, volumeThreshold: 1,\n isFailure: (e) => e.message === 'real-failure',\n });\n // This error is ignored\n await selective.execute(() => Promise.reject(new Error('ignore'))).catch(() => {});\n expect(selective.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('reset returns to CLOSED', async () => {\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n breaker.reset();\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n\n it('emits stateChange event', async () => {\n const listener = vi.fn();\n breaker.on('stateChange', listener);\n for (let i = 0; i < 3; i++) {\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n }\n expect(listener).toHaveBeenCalledWith(\n expect.objectContaining({ from: CircuitBreakerState.CLOSED, to: CircuitBreakerState.OPEN })\n );\n });\n\n it('does not trip below volumeThreshold', async () => {\n // volumeThreshold = 3, failureThreshold = 3\n // Only 2 requests — should not trip\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n await breaker.execute(() => Promise.reject(new Error('x'))).catch(() => {});\n expect(breaker.getState()).toBe(CircuitBreakerState.CLOSED);\n });\n});"
110
+ },
111
+ {
112
+ "language": "ts",
113
+ "code": "// __tests__/resilience/retry.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { retry, withRetry, RetryableErrors } from '../../src/resilience/retry.js';\n\ndescribe('retry', () => {\n it('returns result on first success', async () => {\n const fn = vi.fn(() => Promise.resolve('value'));\n const result = await retry(fn, { maxAttempts: 3 });\n expect(result.success).toBe(true);\n expect(result.result).toBe('value');\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('retries on failure and eventually succeeds', async () => {\n let calls = 0;\n const fn = vi.fn(() => {\n calls++;\n if (calls < 3) return Promise.reject(new Error('fail'));\n return Promise.resolve('ok');\n });\n const result = await retry(fn, { maxAttempts: 5, initialDelay: 1 });\n expect(result.success).toBe(true);\n expect(result.attempts).toBe(3);\n });\n\n it('returns failure after exhausting all attempts', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('always fails')));\n const result = await retry(fn, { maxAttempts: 3, initialDelay: 1 });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(3);\n expect(result.errors).toHaveLength(3);\n });\n\n it('stops immediately for non-retryable errors', async () => {\n const fn = vi.fn(() => Promise.reject(new Error('permanent')));\n const result = await retry(fn, {\n maxAttempts: 5,\n initialDelay: 1,\n retryableErrors: (e) => e.message !== 'permanent',\n });\n expect(result.success).toBe(false);\n expect(result.attempts).toBe(1);\n expect(fn).toHaveBeenCalledOnce();\n });\n\n it('calls onRetry callback with attempt info', async () => {\n const onRetry = vi.fn();\n const fn = vi.fn()\n .mockRejectedValueOnce(new Error('fail'))\n .mockResolvedValueOnce('ok');\n await retry(fn, { maxAttempts: 3, initialDelay: 1, onRetry });\n expect(onRetry).toHaveBeenCalledOnce();\n expect(onRetry.mock.calls[0][1]).toBe(1); // attempt number\n });\n\n it('respects maxDelay cap', async () => {\n const delays: number[] = [];\n const fn = vi.fn(() => Promise.reject(new Error('fail')));\n const onRetry = (_: Error, __: number, delay: number) => delays.push(delay);\n await retry(fn, {\n maxAttempts: 5, initialDelay: 1000, backoffMultiplier: 10,\n maxDelay: 500, jitter: 0, onRetry,\n });\n expect(delays.every(d => d <= 500)).toBe(true);\n });\n});\n\ndescribe('RetryableErrors', () => {\n it('network detects ECONNRESET', () => {\n expect(RetryableErrors.network(new Error('ECONNRESET'))).toBe(true);\n expect(RetryableErrors.network(new Error('syntax error'))).toBe(false);\n });\n\n it('rateLimit detects 429', () => {\n expect(RetryableErrors.rateLimit(new Error('429 Too Many'))).toBe(true);\n });\n\n it('transient combines network + rateLimit + 5xx', () => {\n expect(RetryableErrors.transient(new Error('ETIMEDOUT'))).toBe(true);\n expect(RetryableErrors.transient(new Error('500 Internal Server Error'))).toBe(true);\n expect(RetryableErrors.transient(new Error('validation error'))).toBe(false);\n });\n});"
114
+ },
115
+ {
116
+ "language": "ts",
117
+ "code": "// __tests__/resilience/bulkhead.test.ts\nimport { describe, it, expect, vi } from 'vitest';\nimport { Bulkhead, createSemaphore } from '../../src/resilience/bulkhead.js';\n\ndescribe('Bulkhead', () => {\n it('executes functions up to maxConcurrent immediately', async () => {\n const bh = new Bulkhead({ name: 'test', maxConcurrent: 2, maxQueue: 5, queueTimeout: 1000 });\n const results = await Promise.all([\n bh.execute(() => Promise.resolve(1)),\n bh.execute(() => Promise.resolve(2)),\n ]);\n expect(results).toEqual([1, 2]);\n expect(bh.getStats().completed).toBe(2);\n });\n\n it('queues excess requests and processes them', async () => {\n const bh = new Bulkhead({ name: 'bh', maxConcurrent: 1, maxQueue: 5, queueTimeout: 1000 });\n let resolve1!: () => void;\n const p1 = new Promise<void>(r => { resolve1 = r; });\n\n const result1 = bh.execute(() => p1.then(() => 'first'));\n const result2 = bh.execute(() => Promise.resolve('second'));\n\n resolve1();\n const [r1, r2] = await Promise.all([result1, result2]);\n expect(r1).toBe('first');\n expect(r2).toBe('second');\n });\n\n it('rejects when queue is full', async () => {\n const bh = new Bulkhead({ name: 'full', maxConcurrent: 1, maxQueue: 1, queueTimeout: 5000 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker); // occupies concurrency slot\n bh.execute(() => Promise.resolve('queued')); // fills queue\n\n await expect(bh.execute(() => Promise.resolve('overflow')))\n .rejects.toThrow(\"Bulkhead 'full' is full\");\n expect(bh.getStats().rejected).toBe(1);\n\n resolve();\n });\n\n it('times out queued items', async () => {\n const bh = new Bulkhead({ name: 'to', maxConcurrent: 1, maxQueue: 5, queueTimeout: 50 });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n\n bh.execute(() => blocker);\n const queued = bh.execute(() => Promise.resolve('ok'));\n\n await expect(queued).rejects.toThrow('queue timeout');\n expect(bh.getStats().timedOut).toBe(1);\n\n resolve();\n });\n\n it('calls onRejected callback on full', async () => {\n const onRejected = vi.fn();\n const bh = new Bulkhead({ name: 'cb', maxConcurrent: 1, maxQueue: 0, queueTimeout: 1000, onRejected });\n let resolve!: () => void;\n const blocker = new Promise<void>(r => { resolve = r; });\n bh.execute(() => blocker);\n await bh.execute(() => Promise.resolve()).catch(() => {});\n expect(onRejected).toHaveBeenCalledWith('full');\n resolve();\n });\n});\n\ndescribe('createSemaphore', () => {\n it('limits concurrent access', async () => {\n const sem = createSemaphore(2);\n let active = 0;\n let maxActive = 0;\n\n const task = async () => {\n await sem.acquire();\n active++;\n maxActive = Math.max(maxActive, active);\n await new Promise(r => setTimeout(r, 10));\n active--;\n sem.release();\n };\n\n await Promise.all([task(), task(), task(), task()]);\n expect(maxActive).toBeLessThanOrEqual(2);\n });\n});"
118
+ },
119
+ {
120
+ "language": "ts",
121
+ "code": "// __tests__/resilience/rate-limiter.test.ts\nimport { describe, it, expect } from 'vitest';\nimport { SlidingWindowRateLimiter, TokenBucketRateLimiter } from '../../src/resilience/rate-limiter.js';\n\ndescribe('SlidingWindowRateLimiter', () => {\n it('allows requests up to limit', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 3, windowMs: 10000 });\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false); // 4th blocked\n limiter.destroy();\n });\n\n it('tracks remaining and used counts', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 5, windowMs: 10000 });\n limiter.consume('k');\n limiter.consume('k');\n const r = limiter.consume('k');\n expect(r.used).toBe(3);\n expect(r.remaining).toBe(2);\n limiter.destroy();\n });\n\n it('check does not consume a token', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.check('k'); // peek only\n expect(limiter.consume('k').allowed).toBe(true);\n expect(limiter.consume('k').allowed).toBe(false);\n limiter.destroy();\n });\n\n it('reset clears a key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n limiter.consume('k');\n limiter.reset('k');\n expect(limiter.consume('k').allowed).toBe(true);\n limiter.destroy();\n });\n\n it('tracks separately by key', () => {\n const limiter = new SlidingWindowRateLimiter({ maxRequests: 1, windowMs: 10000 });\n expect(limiter.consume('user-1').allowed).toBe(true);\n expect(limiter.consume('user-1').allowed).toBe(false);\n expect(limiter.consume('user-2').allowed).toBe(true); // separate key\n limiter.destroy();\n });\n});\n\ndescribe('TokenBucketRateLimiter', () => {\n it('starts with full bucket', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 5, windowMs: 1000 });\n const r = limiter.check();\n expect(r.remaining).toBe(5);\n expect(r.allowed).toBe(true);\n limiter.destroy();\n });\n\n it('consumes tokens on each request', () => {\n const limiter = new TokenBucketRateLimiter({ maxRequests: 2, windowMs: 10000 });\n limiter.consume();\n const r = limiter.consume();\n expect(r.remaining).toBe(0);\n expect(limiter.consume().allowed).toBe(false);\n limiter.destroy();\n });\n});"
122
+ },
123
+ {
124
+ "language": "ts",
125
+ "code": "// __tests__/security/input-validation.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n validateInput, sanitizeString, validatePath,\n validateCommand, validateTags, isValidIdentifier, escapeForSql,\n} from '../../src/security/input-validation.js';\n\ndescribe('validateInput', () => {\n it('returns valid: true for valid string', () => {\n const r = validateInput('hello');\n expect(r.valid).toBe(true);\n expect(r.sanitized).toBe('hello');\n });\n\n it('trims whitespace by default', () => {\n expect(validateInput(' hi ').sanitized).toBe('hi');\n });\n\n it('returns error for null when required', () => {\n const r = validateInput(null, { required: true });\n expect(r.valid).toBe(false);\n expect(r.error).toMatch(/required/i);\n });\n\n it('returns valid for null when not required', () => {\n expect(validateInput(null).valid).toBe(true);\n });\n\n it('rejects non-string values', () => {\n expect(validateInput(42).valid).toBe(false);\n });\n\n it('enforces minLength', () => {\n expect(validateInput('hi', { minLength: 5 }).valid).toBe(false);\n });\n\n it('enforces maxLength', () => {\n expect(validateInput('hello world', { maxLength: 5 }).valid).toBe(false);\n });\n\n it('validates against pattern', () => {\n const r = validateInput('abc123', { pattern: /^\\d+$/ });\n expect(r.valid).toBe(false);\n });\n});\n\ndescribe('validatePath', () => {\n it('blocks path traversal via ..', () => {\n expect(validatePath('../etc/passwd').valid).toBe(false);\n });\n\n it('blocks tilde home directory reference', () => {\n expect(validatePath('~/secret').valid).toBe(false);\n });\n\n it('blocks paths outside allowed base', () => {\n expect(validatePath('/etc/passwd', '/home').valid).toBe(false);\n });\n\n it('blocks null bytes', () => {\n expect(validatePath('/tmp/file\\x00.txt').valid).toBe(false);\n });\n\n it('accepts valid relative path', () => {\n expect(validatePath('src/utils.ts').valid).toBe(true);\n });\n\n it('blocks excessively long paths', () => {\n expect(validatePath('a'.repeat(4097)).valid).toBe(false);\n });\n});\n\ndescribe('validateCommand', () => {\n it('rejects shell operators', () => {\n expect(validateCommand('echo foo; rm -rf /').valid).toBe(false);\n expect(validateCommand('ls && cat /etc/passwd').valid).toBe(false);\n expect(validateCommand('curl url | bash').valid).toBe(false);\n });\n\n it('rejects command substitution', () => {\n expect(validateCommand('echo $(whoami)').valid).toBe(false);\n });\n\n it('rejects when not in whitelist', () => {\n const r = validateCommand('rm -rf /', ['ls', 'cat']);\n expect(r.valid).toBe(false);\n expect(r.error).toContain('not allowed');\n });\n\n it('accepts safe whitelisted command', () => {\n expect(validateCommand('ls -la', ['ls']).valid).toBe(true);\n });\n\n it('rejects empty command', () => {\n expect(validateCommand(' ').valid).toBe(false);\n });\n});\n\ndescribe('validateTags', () => {\n it('accepts valid tags', () => {\n const r = validateTags(['alpha', 'beta-v2', 'prod:feature']);\n expect(r.valid).toBe(true);\n expect(r.sanitized).toHaveLength(3);\n });\n\n it('rejects non-array', () => {\n expect(validateTags('tag').valid).toBe(false);\n });\n\n it('rejects tags with spaces', () => {\n expect(validateTags(['invalid tag']).valid).toBe(false);\n });\n\n it('rejects tags over 100 chars', () => {\n expect(validateTags(['x'.repeat(101)]).valid).toBe(false);\n });\n\n it('skips empty tags', () => {\n const r = validateTags([' ', 'valid']);\n expect(r.valid).toBe(true);\n expect((r.sanitized as string[]).length).toBe(1);\n });\n});\n\ndescribe('isValidIdentifier', () => {\n it('accepts valid identifiers', () => {\n expect(isValidIdentifier('myAgent_1')).toBe(true);\n expect(isValidIdentifier('_private')).toBe(true);\n });\n\n it('rejects identifiers starting with digits', () => {\n expect(isValidIdentifier('1agent')).toBe(false);\n });\n\n it('rejects identifiers over 256 chars', () => {\n expect(isValidIdentifier('a'.repeat(257))).toBe(false);\n });\n});"
126
+ },
127
+ {
128
+ "language": "ts",
129
+ "code": "// __tests__/security/secure-random.test.ts\nimport { describe, it, expect } from 'vitest';\nimport {\n generateSecureId, generateUUID, generateSecureToken,\n generateShortId, generateSessionId, secureRandomInt, secureRandomChoice, secureShuffleArray,\n} from '../../src/security/secure-random.js';\n\ndescribe('generateSecureId', () => {\n it('generates unique IDs', () => {\n const ids = new Set(Array.from({ length: 100 }, () => generateSecureId()));\n expect(ids.size).toBe(100);\n });\n\n it('applies prefix', () => {\n expect(generateSecureId('agent')).toMatch(/^agent_/);\n });\n});\n\ndescribe('generateUUID', () => {\n it('generates valid UUID v4 format', () => {\n const uuid = generateUUID();\n expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);\n });\n});\n\ndescribe('secureRandomInt', () => {\n it('returns value within [min, max]', () => {\n for (let i = 0; i < 100; i++) {\n const v = secureRandomInt(5, 10);\n expect(v).toBeGreaterThanOrEqual(5);\n expect(v).toBeLessThanOrEqual(10);\n }\n });\n\n it('handles single-value range', () => {\n expect(secureRandomInt(7, 7)).toBe(7);\n });\n});\n\ndescribe('secureRandomChoice', () => {\n it('selects element from array', () => {\n const arr = ['a', 'b', 'c'];\n const result = secureRandomChoice(arr);\n expect(arr).toContain(result);\n });\n\n it('throws on empty array', () => {\n expect(() => secureRandomChoice([])).toThrow();\n });\n});\n\ndescribe('secureShuffleArray', () => {\n it('returns array with same elements', () => {\n const arr = [1, 2, 3, 4, 5];\n const shuffled = secureShuffleArray(arr);\n expect(shuffled).toHaveLength(arr.length);\n expect(shuffled.sort()).toEqual([...arr].sort());\n });\n\n it('does not mutate original array', () => {\n const arr = [1, 2, 3];\n const orig = [...arr];\n secureShuffleArray(arr);\n expect(arr).toEqual(orig);\n });\n});"
130
+ },
131
+ {
132
+ "language": "ts",
133
+ "code": "// Additional tests to add to src/hooks/hooks.test.ts:\n\ndescribe('HookRegistry — missing coverage', () => {\n it('unregisterAll(event) removes only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n\n const removed = registry.unregisterAll(HookEvent.PreToolUse);\n expect(removed).toBe(2);\n expect(registry.count(HookEvent.PreToolUse)).toBe(0);\n expect(registry.count(HookEvent.PostToolUse)).toBe(1);\n });\n\n it('unregisterAll() removes everything', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n const removed = registry.unregisterAll();\n expect(removed).toBe(2);\n expect(registry.count()).toBe(0);\n });\n\n it('getHook returns definition for valid id', () => {\n const handler = vi.fn();\n const id = registry.register(HookEvent.PreEdit, handler);\n const def = registry.getHook(id);\n expect(def).toBeDefined();\n expect(def?.handler).toBe(handler);\n });\n\n it('getHook returns undefined for unknown id', () => {\n expect(registry.getHook('no-such-id')).toBeUndefined();\n });\n\n it('count(event) counts only that event', () => {\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PreToolUse, vi.fn());\n registry.register(HookEvent.PostToolUse, vi.fn());\n expect(registry.count(HookEvent.PreToolUse)).toBe(2);\n expect(registry.count()).toBe(3);\n });\n});\n\ndescribe('HookExecutor — missing coverage', () => {\n it('continueChain=false stops chain but aborted remains false', async () => {\n const h1 = vi.fn(async () => ({ success: true }));\n const h2 = vi.fn(async () => ({ success: true, continueChain: false }));\n const h3 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1, HookPriority.High);\n registry.register(HookEvent.PreEdit, h2, HookPriority.Normal);\n registry.register(HookEvent.PreEdit, h3, HookPriority.Low);\n\n const result = await executor.execute(HookEvent.PreEdit, { event: HookEvent.PreEdit, timestamp: new Date() });\n expect(result.hooksExecuted).toBe(2);\n expect(h3).not.toHaveBeenCalled();\n expect(result.aborted).toBe(false); // chain stopped but not aborted\n });\n\n it('collectResults=false returns empty results array', async () => {\n registry.register(HookEvent.PreEdit, async () => ({ success: true }));\n const result = await executor.execute(\n HookEvent.PreEdit,\n { event: HookEvent.PreEdit, timestamp: new Date() },\n { collectResults: false }\n );\n expect(result.results).toHaveLength(0);\n });\n\n it('executeParallel throws when arrays have different lengths', async () => {\n await expect(\n executor.executeParallel(\n [HookEvent.PreEdit],\n [{ event: HookEvent.PreEdit, timestamp: new Date() }, { event: HookEvent.PostEdit, timestamp: new Date() }]\n )\n ).rejects.toThrow('same length');\n });\n\n it('executeSequential aborts after failing event when continueOnError is false', async () => {\n const h1 = vi.fn(async () => { throw new Error('fail'); });\n const h2 = vi.fn(async () => ({ success: true }));\n registry.register(HookEvent.PreEdit, h1);\n registry.register(HookEvent.PostEdit, h2);\n\n const result = await executor.executeSequential(\n [HookEvent.PreEdit, HookEvent.PostEdit],\n { event: HookEvent.PreEdit, timestamp: new Date() }\n );\n expect(result.aborted).toBe(true);\n expect(h2).not.toHaveBeenCalled();\n });\n});"
134
+ },
135
+ {
136
+ "language": "ts",
137
+ "code": "// Additional tests to add to src/events/event-store.test.ts:\n\ndescribe('EventStore — missing coverage', () => {\n it('throws when methods called before initialize()', async () => {\n const uninit = new EventStore({ databasePath: ':memory:' });\n await expect(uninit.append(createAgentSpawnedEvent('a', 'coder', 'core', [])))\n .rejects.toThrow('not initialized');\n });\n\n it('emits event:appended for each append', async () => {\n const handler = vi.fn();\n eventStore.on('event:appended', handler);\n await eventStore.append(createAgentSpawnedEvent('a1', 'coder', 'core', []));\n expect(handler).toHaveBeenCalledOnce();\n });\n\n it('emits snapshot:recommended when version hits threshold', async () => {\n const store = new EventStore({ databasePath: ':memory:', snapshotThreshold: 2 });\n await store.initialize();\n const handler = vi.fn();\n store.on('snapshot:recommended', handler);\n\n await store.append(createAgentSpawnedEvent('agg', 'coder', 'core', []));\n await store.append(createAgentStartedEvent('agg'));\n expect(handler).toHaveBeenCalledOnce();\n await store.shutdown();\n });\n\n it('query filters by afterTimestamp', async () => {\n const before = Date.now();\n await new Promise(r => setTimeout(r, 5));\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const events = await eventStore.query({ afterTimestamp: before });\n expect(events).toHaveLength(1);\n });\n\n it('query filters by beforeTimestamp', async () => {\n await eventStore.append(createAgentSpawnedEvent('x', 'coder', 'core', []));\n const after = Date.now() + 1000;\n const events = await eventStore.query({ beforeTimestamp: after });\n expect(events.length).toBeGreaterThan(0);\n const noEvents = await eventStore.query({ beforeTimestamp: 0 });\n expect(noEvents).toHaveLength(0);\n });\n\n it('query filters by fromVersion', async () => {\n await eventStore.append(createAgentSpawnedEvent('v', 'coder', 'core', []));\n await eventStore.append(createAgentStartedEvent('v'));\n const events = await eventStore.query({ aggregateIds: ['v'], fromVersion: 2 });\n expect(events).toHaveLength(1);\n expect(events[0].version).toBe(2);\n });\n\n it('getSnapshot returns null for unknown aggregate', async () => {\n const snap = await eventStore.getSnapshot('no-such-aggregate');\n expect(snap).toBeNull();\n });\n\n it('stats show oldest/newest timestamps after appending', async () => {\n await eventStore.append(createAgentSpawnedEvent('a', 'coder', 'core', []));\n const stats = await eventStore.getStats();\n expect(stats.oldestEvent).not.toBeNull();\n expect(stats.newestEvent).not.toBeNull();\n expect(stats.newestEvent!).toBeGreaterThanOrEqual(stats.oldestEvent!);\n });\n\n it('stats count snapshots', async () => {\n await eventStore.saveSnapshot({\n aggregateId: 'a1', aggregateType: 'agent',\n version: 1, state: {}, timestamp: Date.now(),\n });\n const stats = await eventStore.getStats();\n expect(stats.snapshotCount).toBe(1);\n });\n});"
138
+ },
139
+ {
140
+ "language": "ts",
141
+ "code": "// __tests__/core/orchestrator/event-coordinator.test.ts\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { EventCoordinator } from '../../../src/core/orchestrator/event-coordinator.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\nimport { SystemEventTypes } from '../../../src/core/interfaces/event.interface.js';\n\ndescribe('EventCoordinator', () => {\n let coordinator: EventCoordinator;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(async () => {\n bus = createEventBus();\n coordinator = new EventCoordinator(bus);\n await coordinator.initialize();\n });\n\n afterEach(async () => { await coordinator.shutdown(); });\n\n it('registers handlers and routes events', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('test:event', handler);\n await bus.emitAsync('test:event', { data: 1 });\n expect(handler).toHaveBeenCalled();\n });\n\n it('unregisters handlers', async () => {\n const handler = vi.fn();\n coordinator.registerHandler('ev', handler);\n coordinator.unregisterHandler('ev', handler);\n await bus.emitAsync('ev', {});\n expect(handler).not.toHaveBeenCalled();\n });\n\n it('initialize is idempotent', async () => {\n await coordinator.initialize(); // second call — should not throw or double-register\n expect(coordinator.isInitialized()).toBe(true);\n });\n\n it('shutdown clears handlers', async () => {\n coordinator.registerHandler('ev', vi.fn());\n await coordinator.shutdown();\n expect(coordinator.getRegisteredTypes()).toHaveLength(0);\n expect(coordinator.isInitialized()).toBe(false);\n });\n\n it('getHandlerCount returns correct count', () => {\n coordinator.registerHandler('ev', vi.fn());\n coordinator.registerHandler('ev', vi.fn());\n expect(coordinator.getHandlerCount('ev')).toBe(2);\n expect(coordinator.getHandlerCount('other')).toBe(0);\n });\n\n it('handler errors do not throw from route()', async () => {\n coordinator.registerHandler('ev', async () => { throw new Error('boom'); });\n await expect(coordinator.route({ id: '1', type: 'ev', timestamp: new Date(), source: 'test', payload: {} }))\n .resolves.toBeUndefined();\n });\n});"
142
+ },
143
+ {
144
+ "language": "ts",
145
+ "code": "// __tests__/core/orchestrator/task-manager.test.ts (skeleton)\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { TaskQueue, TaskManager } from '../../../src/core/orchestrator/task-manager.js';\nimport { createEventBus } from '../../../src/core/event-bus.js';\n\ndescribe('TaskQueue', () => {\n let queue: TaskQueue;\n beforeEach(() => { queue = new TaskQueue(); });\n\n it('dequeues in priority order', async () => {\n const make = (id: string, priority: number) =>\n ({ id, type: 'test', description: '', priority, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('low', 10));\n await queue.enqueue(make('high', 90));\n await queue.enqueue(make('mid', 50));\n expect((await queue.dequeue())!.id).toBe('high');\n expect((await queue.dequeue())!.id).toBe('mid');\n expect((await queue.dequeue())!.id).toBe('low');\n });\n\n it('updatePriority re-sorts queue', async () => {\n const make = (id: string, p: number) =>\n ({ id, type: 't', description: '', priority: p, createdAt: new Date(), status: 'pending' as const });\n await queue.enqueue(make('a', 10));\n await queue.enqueue(make('b', 20));\n await queue.updatePriority('a', 100);\n expect((await queue.dequeue())!.id).toBe('a');\n });\n\n it('remove returns false for missing task', async () => {\n expect(await queue.remove('ghost')).toBe(false);\n });\n});\n\ndescribe('TaskManager', () => {\n let manager: TaskManager;\n let bus: ReturnType<typeof createEventBus>;\n\n beforeEach(() => {\n bus = createEventBus();\n manager = new TaskManager(bus);\n });\n\n it('createTask emits TASK_CREATED event', async () => {\n const handler = vi.fn();\n bus.on('task:created', handler);\n await manager.createTask({ type: 'work', description: 'Do stuff' });\n expect(handler).toHaveBeenCalled();\n });\n\n it('assignTask throws for unknown task', async () => {\n await expect(manager.assignTask('ghost', 'agent-1')).rejects.toThrow('Task not found');\n });\n\n it('getTasks filters by status', async () => {\n const t1 = await manager.createTask({ type: 'a', description: 'd' });\n await manager.createTask({ type: 'b', description: 'd' });\n await manager.assignTask(t1.id, 'agent-1');\n const assigned = manager.getTasks({ status: 'assigned' });\n expect(assigned).toHaveLength(1);\n expect(assigned[0].id).toBe(t1.id);\n });\n\n it('getMetrics tracks completed and failed counts', async () => {\n const t = await manager.createTask({ type: 'x', description: 'd' });\n await manager.completeTask(t.id, { taskId: t.id, success: true, duration: 100 });\n const m = manager.getMetrics();\n expect(m.completedTasks).toBe(1);\n expect(m.totalTasks).toBe(1);\n });\n});"
146
+ }
147
+ ]
148
+ },
149
+ "durationMs": 264624,
150
+ "model": "sonnet",
151
+ "sandboxMode": "permissive",
152
+ "workerType": "testgaps",
153
+ "timestamp": "2026-04-28T12:36:51.593Z",
154
+ "executionId": "testgaps_1777379546969_dvf2a1"
155
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "timestamp": "2026-04-28T12:54:27.007Z",
3
+ "projectRoot": "/Users/cohen/Projects/ruflo/v3/@claude-flow/shared",
4
+ "structure": {
5
+ "hasPackageJson": true,
6
+ "hasTsConfig": true,
7
+ "hasClaudeConfig": false,
8
+ "hasClaudeFlow": true
9
+ },
10
+ "scannedAt": 1777380867007
11
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "timestamp": "2026-04-28T12:30:26.971Z",
3
+ "patternsConsolidated": 0,
4
+ "memoryCleaned": 0,
5
+ "duplicatesRemoved": 0
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "session-1777379065535",
3
+ "startedAt": "2026-04-28T12:24:25.535Z",
4
+ "platform": "darwin",
5
+ "cwd": "/Users/cohen/Projects/ruflo/v3/@claude-flow/shared",
6
+ "context": {},
7
+ "metrics": {
8
+ "edits": 2,
9
+ "commands": 0,
10
+ "tasks": 0,
11
+ "errors": 0
12
+ }
13
+ }
Binary file
@@ -0,0 +1 @@
1
+ [["entry_1777379065632_2bc6n",{"id":"entry_1777379065632_2bc6n","key":"-","namespace":"security_analysis","content":"name: RuVector Security Analysis\ndescription: Completed security audit of ruvector codebase\ntype: project\n---"}],["entry_1777379065806_kajfc",{"id":"entry_1777379065806_kajfc","key":"date-2026-04-02","namespace":"security_analysis","content":"Completed comprehensive security analysis of the ruvector npm package codebase across all critical components including CLI, MCP server, and core modules.\n\n**Key Findings:**\n- No hardcoded secrets or API keys detected\n- Good input validation in place for shell commands\n- Path validation is implemented with comprehensive checks\n- Most critical vulnerabilities mitigated through sanitization\n\n**Critical Issues:** 0\n**High Issues:** 2 \n**Medium Issues:** 3\n**Low Issues:** 2\n**Risk Score:** 28/100\n\n"}],["entry_1777379065826_8zi4lo",{"id":"entry_1777379065826_8zi4lo","key":"regtest","namespace":"sessions","content":"Session started: regtest"}],["entry_1777379065834_og8bcw",{"id":"entry_1777379065834_og8bcw","key":"test-123","namespace":"tasks","content":"Task completed: test-123"}],["entry_1777379065842_mq07aw",{"id":"entry_1777379065842_mq07aw","key":"test-fix-1","namespace":"tasks","content":"Task completed: test-fix-1"}]]
Binary file
Binary file
Binary file