@herdctl/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (520) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-test.log +219 -0
  3. package/.turbo/turbo-typecheck.log +4 -0
  4. package/coverage/base.css +224 -0
  5. package/coverage/block-navigation.js +87 -0
  6. package/coverage/coverage-final.json +51 -0
  7. package/coverage/favicon.png +0 -0
  8. package/coverage/index.html +251 -0
  9. package/coverage/prettify.css +1 -0
  10. package/coverage/prettify.js +2 -0
  11. package/coverage/sort-arrow-sprite.png +0 -0
  12. package/coverage/sorter.js +210 -0
  13. package/coverage/src/config/index.html +191 -0
  14. package/coverage/src/config/index.ts.html +442 -0
  15. package/coverage/src/config/interpolate.ts.html +652 -0
  16. package/coverage/src/config/loader.ts.html +1501 -0
  17. package/coverage/src/config/merge.ts.html +823 -0
  18. package/coverage/src/config/parser.ts.html +1213 -0
  19. package/coverage/src/config/schema.ts.html +1123 -0
  20. package/coverage/src/fleet-manager/errors.ts.html +2326 -0
  21. package/coverage/src/fleet-manager/event-types.ts.html +1219 -0
  22. package/coverage/src/fleet-manager/fleet-manager.ts.html +7030 -0
  23. package/coverage/src/fleet-manager/index.html +206 -0
  24. package/coverage/src/fleet-manager/index.ts.html +469 -0
  25. package/coverage/src/fleet-manager/job-manager.ts.html +2074 -0
  26. package/coverage/src/fleet-manager/job-queue.ts.html +2479 -0
  27. package/coverage/src/fleet-manager/types.ts.html +2602 -0
  28. package/coverage/src/index.html +116 -0
  29. package/coverage/src/index.ts.html +181 -0
  30. package/coverage/src/runner/errors.ts.html +1006 -0
  31. package/coverage/src/runner/index.html +191 -0
  32. package/coverage/src/runner/index.ts.html +256 -0
  33. package/coverage/src/runner/job-executor.ts.html +1429 -0
  34. package/coverage/src/runner/message-processor.ts.html +1150 -0
  35. package/coverage/src/runner/sdk-adapter.ts.html +658 -0
  36. package/coverage/src/runner/types.ts.html +559 -0
  37. package/coverage/src/scheduler/errors.ts.html +388 -0
  38. package/coverage/src/scheduler/index.html +206 -0
  39. package/coverage/src/scheduler/index.ts.html +244 -0
  40. package/coverage/src/scheduler/interval.ts.html +652 -0
  41. package/coverage/src/scheduler/schedule-runner.ts.html +1411 -0
  42. package/coverage/src/scheduler/schedule-state.ts.html +718 -0
  43. package/coverage/src/scheduler/scheduler.ts.html +1795 -0
  44. package/coverage/src/scheduler/types.ts.html +733 -0
  45. package/coverage/src/state/directory.ts.html +736 -0
  46. package/coverage/src/state/errors.ts.html +376 -0
  47. package/coverage/src/state/fleet-state.ts.html +937 -0
  48. package/coverage/src/state/index.html +221 -0
  49. package/coverage/src/state/index.ts.html +322 -0
  50. package/coverage/src/state/job-metadata.ts.html +1420 -0
  51. package/coverage/src/state/job-output.ts.html +1033 -0
  52. package/coverage/src/state/schemas/fleet-state.ts.html +445 -0
  53. package/coverage/src/state/schemas/index.html +176 -0
  54. package/coverage/src/state/schemas/index.ts.html +286 -0
  55. package/coverage/src/state/schemas/job-metadata.ts.html +628 -0
  56. package/coverage/src/state/schemas/job-output.ts.html +616 -0
  57. package/coverage/src/state/schemas/session-info.ts.html +361 -0
  58. package/coverage/src/state/session.ts.html +844 -0
  59. package/coverage/src/state/types.ts.html +262 -0
  60. package/coverage/src/state/utils/atomic.ts.html +748 -0
  61. package/coverage/src/state/utils/index.html +146 -0
  62. package/coverage/src/state/utils/index.ts.html +103 -0
  63. package/coverage/src/state/utils/reads.ts.html +1621 -0
  64. package/coverage/src/work-sources/adapters/github.ts.html +3583 -0
  65. package/coverage/src/work-sources/adapters/index.html +131 -0
  66. package/coverage/src/work-sources/adapters/index.ts.html +277 -0
  67. package/coverage/src/work-sources/errors.ts.html +298 -0
  68. package/coverage/src/work-sources/index.html +176 -0
  69. package/coverage/src/work-sources/index.ts.html +529 -0
  70. package/coverage/src/work-sources/manager.ts.html +1324 -0
  71. package/coverage/src/work-sources/registry.ts.html +619 -0
  72. package/coverage/src/work-sources/types.ts.html +568 -0
  73. package/dist/config/__tests__/agent.test.d.ts +2 -0
  74. package/dist/config/__tests__/agent.test.d.ts.map +1 -0
  75. package/dist/config/__tests__/agent.test.js +752 -0
  76. package/dist/config/__tests__/agent.test.js.map +1 -0
  77. package/dist/config/__tests__/interpolate.test.d.ts +2 -0
  78. package/dist/config/__tests__/interpolate.test.d.ts.map +1 -0
  79. package/dist/config/__tests__/interpolate.test.js +509 -0
  80. package/dist/config/__tests__/interpolate.test.js.map +1 -0
  81. package/dist/config/__tests__/loader.test.d.ts +2 -0
  82. package/dist/config/__tests__/loader.test.d.ts.map +1 -0
  83. package/dist/config/__tests__/loader.test.js +631 -0
  84. package/dist/config/__tests__/loader.test.js.map +1 -0
  85. package/dist/config/__tests__/merge.test.d.ts +2 -0
  86. package/dist/config/__tests__/merge.test.d.ts.map +1 -0
  87. package/dist/config/__tests__/merge.test.js +672 -0
  88. package/dist/config/__tests__/merge.test.js.map +1 -0
  89. package/dist/config/__tests__/parser.test.d.ts +2 -0
  90. package/dist/config/__tests__/parser.test.d.ts.map +1 -0
  91. package/dist/config/__tests__/parser.test.js +476 -0
  92. package/dist/config/__tests__/parser.test.js.map +1 -0
  93. package/dist/config/__tests__/schema.test.d.ts +2 -0
  94. package/dist/config/__tests__/schema.test.d.ts.map +1 -0
  95. package/dist/config/__tests__/schema.test.js +776 -0
  96. package/dist/config/__tests__/schema.test.js.map +1 -0
  97. package/dist/config/index.d.ts +11 -0
  98. package/dist/config/index.d.ts.map +1 -0
  99. package/dist/config/index.js +26 -0
  100. package/dist/config/index.js.map +1 -0
  101. package/dist/config/interpolate.d.ts +76 -0
  102. package/dist/config/interpolate.d.ts.map +1 -0
  103. package/dist/config/interpolate.js +143 -0
  104. package/dist/config/interpolate.js.map +1 -0
  105. package/dist/config/loader.d.ts +147 -0
  106. package/dist/config/loader.d.ts.map +1 -0
  107. package/dist/config/loader.js +336 -0
  108. package/dist/config/loader.js.map +1 -0
  109. package/dist/config/merge.d.ts +84 -0
  110. package/dist/config/merge.d.ts.map +1 -0
  111. package/dist/config/merge.js +138 -0
  112. package/dist/config/merge.js.map +1 -0
  113. package/dist/config/parser.d.ts +143 -0
  114. package/dist/config/parser.d.ts.map +1 -0
  115. package/dist/config/parser.js +316 -0
  116. package/dist/config/parser.js.map +1 -0
  117. package/dist/config/schema.d.ts +1906 -0
  118. package/dist/config/schema.d.ts.map +1 -0
  119. package/dist/config/schema.js +268 -0
  120. package/dist/config/schema.js.map +1 -0
  121. package/dist/fleet-manager/__tests__/coverage.test.d.ts +13 -0
  122. package/dist/fleet-manager/__tests__/coverage.test.d.ts.map +1 -0
  123. package/dist/fleet-manager/__tests__/coverage.test.js +2282 -0
  124. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -0
  125. package/dist/fleet-manager/__tests__/errors.test.d.ts +7 -0
  126. package/dist/fleet-manager/__tests__/errors.test.d.ts.map +1 -0
  127. package/dist/fleet-manager/__tests__/errors.test.js +557 -0
  128. package/dist/fleet-manager/__tests__/errors.test.js.map +1 -0
  129. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts +7 -0
  130. package/dist/fleet-manager/__tests__/event-helpers.test.d.ts.map +1 -0
  131. package/dist/fleet-manager/__tests__/event-helpers.test.js +368 -0
  132. package/dist/fleet-manager/__tests__/event-helpers.test.js.map +1 -0
  133. package/dist/fleet-manager/__tests__/integration.test.d.ts +11 -0
  134. package/dist/fleet-manager/__tests__/integration.test.d.ts.map +1 -0
  135. package/dist/fleet-manager/__tests__/integration.test.js +949 -0
  136. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -0
  137. package/dist/fleet-manager/__tests__/job-control.test.d.ts +7 -0
  138. package/dist/fleet-manager/__tests__/job-control.test.d.ts.map +1 -0
  139. package/dist/fleet-manager/__tests__/job-control.test.js +215 -0
  140. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -0
  141. package/dist/fleet-manager/__tests__/job-manager.test.d.ts +7 -0
  142. package/dist/fleet-manager/__tests__/job-manager.test.d.ts.map +1 -0
  143. package/dist/fleet-manager/__tests__/job-manager.test.js +659 -0
  144. package/dist/fleet-manager/__tests__/job-manager.test.js.map +1 -0
  145. package/dist/fleet-manager/__tests__/job-queue.test.d.ts +5 -0
  146. package/dist/fleet-manager/__tests__/job-queue.test.d.ts.map +1 -0
  147. package/dist/fleet-manager/__tests__/job-queue.test.js +315 -0
  148. package/dist/fleet-manager/__tests__/job-queue.test.js.map +1 -0
  149. package/dist/fleet-manager/__tests__/reload.test.d.ts +7 -0
  150. package/dist/fleet-manager/__tests__/reload.test.d.ts.map +1 -0
  151. package/dist/fleet-manager/__tests__/reload.test.js +609 -0
  152. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -0
  153. package/dist/fleet-manager/__tests__/status-queries.test.d.ts +7 -0
  154. package/dist/fleet-manager/__tests__/status-queries.test.d.ts.map +1 -0
  155. package/dist/fleet-manager/__tests__/status-queries.test.js +488 -0
  156. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -0
  157. package/dist/fleet-manager/__tests__/trigger.test.d.ts +7 -0
  158. package/dist/fleet-manager/__tests__/trigger.test.d.ts.map +1 -0
  159. package/dist/fleet-manager/__tests__/trigger.test.js +471 -0
  160. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -0
  161. package/dist/fleet-manager/errors.d.ts +407 -0
  162. package/dist/fleet-manager/errors.d.ts.map +1 -0
  163. package/dist/fleet-manager/errors.js +569 -0
  164. package/dist/fleet-manager/errors.js.map +1 -0
  165. package/dist/fleet-manager/event-types.d.ts +302 -0
  166. package/dist/fleet-manager/event-types.d.ts.map +1 -0
  167. package/dist/fleet-manager/event-types.js +9 -0
  168. package/dist/fleet-manager/event-types.js.map +1 -0
  169. package/dist/fleet-manager/fleet-manager.d.ts +699 -0
  170. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -0
  171. package/dist/fleet-manager/fleet-manager.js +1906 -0
  172. package/dist/fleet-manager/fleet-manager.js.map +1 -0
  173. package/dist/fleet-manager/index.d.ts +17 -0
  174. package/dist/fleet-manager/index.d.ts.map +1 -0
  175. package/dist/fleet-manager/index.js +29 -0
  176. package/dist/fleet-manager/index.js.map +1 -0
  177. package/dist/fleet-manager/job-manager.d.ts +271 -0
  178. package/dist/fleet-manager/job-manager.d.ts.map +1 -0
  179. package/dist/fleet-manager/job-manager.js +443 -0
  180. package/dist/fleet-manager/job-manager.js.map +1 -0
  181. package/dist/fleet-manager/job-queue.d.ts +422 -0
  182. package/dist/fleet-manager/job-queue.d.ts.map +1 -0
  183. package/dist/fleet-manager/job-queue.js +448 -0
  184. package/dist/fleet-manager/job-queue.js.map +1 -0
  185. package/dist/fleet-manager/types.d.ts +680 -0
  186. package/dist/fleet-manager/types.d.ts.map +1 -0
  187. package/dist/fleet-manager/types.js +8 -0
  188. package/dist/fleet-manager/types.js.map +1 -0
  189. package/dist/index.d.ts +20 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +26 -0
  192. package/dist/index.js.map +1 -0
  193. package/dist/runner/__tests__/errors.test.d.ts +2 -0
  194. package/dist/runner/__tests__/errors.test.d.ts.map +1 -0
  195. package/dist/runner/__tests__/errors.test.js +264 -0
  196. package/dist/runner/__tests__/errors.test.js.map +1 -0
  197. package/dist/runner/__tests__/job-executor.test.d.ts +2 -0
  198. package/dist/runner/__tests__/job-executor.test.d.ts.map +1 -0
  199. package/dist/runner/__tests__/job-executor.test.js +1345 -0
  200. package/dist/runner/__tests__/job-executor.test.js.map +1 -0
  201. package/dist/runner/__tests__/message-processor.test.d.ts +2 -0
  202. package/dist/runner/__tests__/message-processor.test.d.ts.map +1 -0
  203. package/dist/runner/__tests__/message-processor.test.js +768 -0
  204. package/dist/runner/__tests__/message-processor.test.js.map +1 -0
  205. package/dist/runner/__tests__/sdk-adapter.test.d.ts +2 -0
  206. package/dist/runner/__tests__/sdk-adapter.test.d.ts.map +1 -0
  207. package/dist/runner/__tests__/sdk-adapter.test.js +554 -0
  208. package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -0
  209. package/dist/runner/errors.d.ts +121 -0
  210. package/dist/runner/errors.d.ts.map +1 -0
  211. package/dist/runner/errors.js +212 -0
  212. package/dist/runner/errors.js.map +1 -0
  213. package/dist/runner/index.d.ts +12 -0
  214. package/dist/runner/index.d.ts.map +1 -0
  215. package/dist/runner/index.js +15 -0
  216. package/dist/runner/index.js.map +1 -0
  217. package/dist/runner/job-executor.d.ts +98 -0
  218. package/dist/runner/job-executor.d.ts.map +1 -0
  219. package/dist/runner/job-executor.js +333 -0
  220. package/dist/runner/job-executor.js.map +1 -0
  221. package/dist/runner/message-processor.d.ts +45 -0
  222. package/dist/runner/message-processor.d.ts.map +1 -0
  223. package/dist/runner/message-processor.js +294 -0
  224. package/dist/runner/message-processor.js.map +1 -0
  225. package/dist/runner/sdk-adapter.d.ts +60 -0
  226. package/dist/runner/sdk-adapter.d.ts.map +1 -0
  227. package/dist/runner/sdk-adapter.js +138 -0
  228. package/dist/runner/sdk-adapter.js.map +1 -0
  229. package/dist/runner/types.d.ts +135 -0
  230. package/dist/runner/types.d.ts.map +1 -0
  231. package/dist/runner/types.js +7 -0
  232. package/dist/runner/types.js.map +1 -0
  233. package/dist/scheduler/__tests__/errors.test.d.ts +2 -0
  234. package/dist/scheduler/__tests__/errors.test.d.ts.map +1 -0
  235. package/dist/scheduler/__tests__/errors.test.js +101 -0
  236. package/dist/scheduler/__tests__/errors.test.js.map +1 -0
  237. package/dist/scheduler/__tests__/interval.test.d.ts +2 -0
  238. package/dist/scheduler/__tests__/interval.test.d.ts.map +1 -0
  239. package/dist/scheduler/__tests__/interval.test.js +419 -0
  240. package/dist/scheduler/__tests__/interval.test.js.map +1 -0
  241. package/dist/scheduler/__tests__/schedule-runner.test.d.ts +2 -0
  242. package/dist/scheduler/__tests__/schedule-runner.test.d.ts.map +1 -0
  243. package/dist/scheduler/__tests__/schedule-runner.test.js +634 -0
  244. package/dist/scheduler/__tests__/schedule-runner.test.js.map +1 -0
  245. package/dist/scheduler/__tests__/schedule-state.test.d.ts +2 -0
  246. package/dist/scheduler/__tests__/schedule-state.test.d.ts.map +1 -0
  247. package/dist/scheduler/__tests__/schedule-state.test.js +572 -0
  248. package/dist/scheduler/__tests__/schedule-state.test.js.map +1 -0
  249. package/dist/scheduler/__tests__/scheduler.test.d.ts +2 -0
  250. package/dist/scheduler/__tests__/scheduler.test.d.ts.map +1 -0
  251. package/dist/scheduler/__tests__/scheduler.test.js +987 -0
  252. package/dist/scheduler/__tests__/scheduler.test.js.map +1 -0
  253. package/dist/scheduler/errors.d.ts +61 -0
  254. package/dist/scheduler/errors.d.ts.map +1 -0
  255. package/dist/scheduler/errors.js +81 -0
  256. package/dist/scheduler/errors.js.map +1 -0
  257. package/dist/scheduler/index.d.ts +13 -0
  258. package/dist/scheduler/index.d.ts.map +1 -0
  259. package/dist/scheduler/index.js +17 -0
  260. package/dist/scheduler/index.js.map +1 -0
  261. package/dist/scheduler/interval.d.ts +64 -0
  262. package/dist/scheduler/interval.d.ts.map +1 -0
  263. package/dist/scheduler/interval.js +139 -0
  264. package/dist/scheduler/interval.js.map +1 -0
  265. package/dist/scheduler/schedule-runner.d.ts +149 -0
  266. package/dist/scheduler/schedule-runner.d.ts.map +1 -0
  267. package/dist/scheduler/schedule-runner.js +277 -0
  268. package/dist/scheduler/schedule-runner.js.map +1 -0
  269. package/dist/scheduler/schedule-state.d.ts +105 -0
  270. package/dist/scheduler/schedule-state.d.ts.map +1 -0
  271. package/dist/scheduler/schedule-state.js +151 -0
  272. package/dist/scheduler/schedule-state.js.map +1 -0
  273. package/dist/scheduler/scheduler.d.ts +138 -0
  274. package/dist/scheduler/scheduler.d.ts.map +1 -0
  275. package/dist/scheduler/scheduler.js +423 -0
  276. package/dist/scheduler/scheduler.js.map +1 -0
  277. package/dist/scheduler/types.d.ts +160 -0
  278. package/dist/scheduler/types.d.ts.map +1 -0
  279. package/dist/scheduler/types.js +8 -0
  280. package/dist/scheduler/types.js.map +1 -0
  281. package/dist/state/__tests__/directory.test.d.ts +2 -0
  282. package/dist/state/__tests__/directory.test.d.ts.map +1 -0
  283. package/dist/state/__tests__/directory.test.js +414 -0
  284. package/dist/state/__tests__/directory.test.js.map +1 -0
  285. package/dist/state/__tests__/fleet-state.test.d.ts +2 -0
  286. package/dist/state/__tests__/fleet-state.test.d.ts.map +1 -0
  287. package/dist/state/__tests__/fleet-state.test.js +696 -0
  288. package/dist/state/__tests__/fleet-state.test.js.map +1 -0
  289. package/dist/state/__tests__/job-metadata-schema.test.d.ts +2 -0
  290. package/dist/state/__tests__/job-metadata-schema.test.d.ts.map +1 -0
  291. package/dist/state/__tests__/job-metadata-schema.test.js +329 -0
  292. package/dist/state/__tests__/job-metadata-schema.test.js.map +1 -0
  293. package/dist/state/__tests__/job-metadata.test.d.ts +2 -0
  294. package/dist/state/__tests__/job-metadata.test.d.ts.map +1 -0
  295. package/dist/state/__tests__/job-metadata.test.js +667 -0
  296. package/dist/state/__tests__/job-metadata.test.js.map +1 -0
  297. package/dist/state/__tests__/job-output.test.d.ts +2 -0
  298. package/dist/state/__tests__/job-output.test.d.ts.map +1 -0
  299. package/dist/state/__tests__/job-output.test.js +672 -0
  300. package/dist/state/__tests__/job-output.test.js.map +1 -0
  301. package/dist/state/__tests__/session-schema.test.d.ts +2 -0
  302. package/dist/state/__tests__/session-schema.test.d.ts.map +1 -0
  303. package/dist/state/__tests__/session-schema.test.js +323 -0
  304. package/dist/state/__tests__/session-schema.test.js.map +1 -0
  305. package/dist/state/__tests__/session.test.d.ts +2 -0
  306. package/dist/state/__tests__/session.test.d.ts.map +1 -0
  307. package/dist/state/__tests__/session.test.js +468 -0
  308. package/dist/state/__tests__/session.test.js.map +1 -0
  309. package/dist/state/directory.d.ts +42 -0
  310. package/dist/state/directory.d.ts.map +1 -0
  311. package/dist/state/directory.js +170 -0
  312. package/dist/state/directory.js.map +1 -0
  313. package/dist/state/errors.d.ts +44 -0
  314. package/dist/state/errors.d.ts.map +1 -0
  315. package/dist/state/errors.js +82 -0
  316. package/dist/state/errors.js.map +1 -0
  317. package/dist/state/fleet-state.d.ts +126 -0
  318. package/dist/state/fleet-state.d.ts.map +1 -0
  319. package/dist/state/fleet-state.js +196 -0
  320. package/dist/state/fleet-state.js.map +1 -0
  321. package/dist/state/index.d.ts +21 -0
  322. package/dist/state/index.d.ts.map +1 -0
  323. package/dist/state/index.js +30 -0
  324. package/dist/state/index.js.map +1 -0
  325. package/dist/state/job-metadata.d.ts +151 -0
  326. package/dist/state/job-metadata.d.ts.map +1 -0
  327. package/dist/state/job-metadata.js +287 -0
  328. package/dist/state/job-metadata.js.map +1 -0
  329. package/dist/state/job-output.d.ts +116 -0
  330. package/dist/state/job-output.d.ts.map +1 -0
  331. package/dist/state/job-output.js +218 -0
  332. package/dist/state/job-output.js.map +1 -0
  333. package/dist/state/schemas/__tests__/job-output.test.d.ts +2 -0
  334. package/dist/state/schemas/__tests__/job-output.test.d.ts.map +1 -0
  335. package/dist/state/schemas/__tests__/job-output.test.js +279 -0
  336. package/dist/state/schemas/__tests__/job-output.test.js.map +1 -0
  337. package/dist/state/schemas/fleet-state.d.ts +249 -0
  338. package/dist/state/schemas/fleet-state.d.ts.map +1 -0
  339. package/dist/state/schemas/fleet-state.js +97 -0
  340. package/dist/state/schemas/fleet-state.js.map +1 -0
  341. package/dist/state/schemas/index.d.ts +10 -0
  342. package/dist/state/schemas/index.d.ts.map +1 -0
  343. package/dist/state/schemas/index.js +10 -0
  344. package/dist/state/schemas/index.js.map +1 -0
  345. package/dist/state/schemas/job-metadata.d.ts +118 -0
  346. package/dist/state/schemas/job-metadata.d.ts.map +1 -0
  347. package/dist/state/schemas/job-metadata.js +123 -0
  348. package/dist/state/schemas/job-metadata.js.map +1 -0
  349. package/dist/state/schemas/job-output.d.ts +291 -0
  350. package/dist/state/schemas/job-output.d.ts.map +1 -0
  351. package/dist/state/schemas/job-output.js +132 -0
  352. package/dist/state/schemas/job-output.js.map +1 -0
  353. package/dist/state/schemas/session-info.d.ts +65 -0
  354. package/dist/state/schemas/session-info.d.ts.map +1 -0
  355. package/dist/state/schemas/session-info.js +58 -0
  356. package/dist/state/schemas/session-info.js.map +1 -0
  357. package/dist/state/session.d.ts +92 -0
  358. package/dist/state/session.d.ts.map +1 -0
  359. package/dist/state/session.js +173 -0
  360. package/dist/state/session.js.map +1 -0
  361. package/dist/state/types.d.ts +54 -0
  362. package/dist/state/types.d.ts.map +1 -0
  363. package/dist/state/types.js +18 -0
  364. package/dist/state/types.js.map +1 -0
  365. package/dist/state/utils/__tests__/atomic.test.d.ts +2 -0
  366. package/dist/state/utils/__tests__/atomic.test.d.ts.map +1 -0
  367. package/dist/state/utils/__tests__/atomic.test.js +537 -0
  368. package/dist/state/utils/__tests__/atomic.test.js.map +1 -0
  369. package/dist/state/utils/__tests__/reads.test.d.ts +2 -0
  370. package/dist/state/utils/__tests__/reads.test.d.ts.map +1 -0
  371. package/dist/state/utils/__tests__/reads.test.js +792 -0
  372. package/dist/state/utils/__tests__/reads.test.js.map +1 -0
  373. package/dist/state/utils/atomic.d.ts +89 -0
  374. package/dist/state/utils/atomic.d.ts.map +1 -0
  375. package/dist/state/utils/atomic.js +157 -0
  376. package/dist/state/utils/atomic.js.map +1 -0
  377. package/dist/state/utils/index.d.ts +6 -0
  378. package/dist/state/utils/index.d.ts.map +1 -0
  379. package/dist/state/utils/index.js +6 -0
  380. package/dist/state/utils/index.js.map +1 -0
  381. package/dist/state/utils/reads.d.ts +196 -0
  382. package/dist/state/utils/reads.d.ts.map +1 -0
  383. package/dist/state/utils/reads.js +346 -0
  384. package/dist/state/utils/reads.js.map +1 -0
  385. package/dist/work-sources/__tests__/github.test.d.ts +2 -0
  386. package/dist/work-sources/__tests__/github.test.d.ts.map +1 -0
  387. package/dist/work-sources/__tests__/github.test.js +1334 -0
  388. package/dist/work-sources/__tests__/github.test.js.map +1 -0
  389. package/dist/work-sources/__tests__/manager.test.d.ts +2 -0
  390. package/dist/work-sources/__tests__/manager.test.d.ts.map +1 -0
  391. package/dist/work-sources/__tests__/manager.test.js +424 -0
  392. package/dist/work-sources/__tests__/manager.test.js.map +1 -0
  393. package/dist/work-sources/__tests__/registry.test.d.ts +2 -0
  394. package/dist/work-sources/__tests__/registry.test.d.ts.map +1 -0
  395. package/dist/work-sources/__tests__/registry.test.js +381 -0
  396. package/dist/work-sources/__tests__/registry.test.js.map +1 -0
  397. package/dist/work-sources/__tests__/types.test.d.ts +2 -0
  398. package/dist/work-sources/__tests__/types.test.d.ts.map +1 -0
  399. package/dist/work-sources/__tests__/types.test.js +406 -0
  400. package/dist/work-sources/__tests__/types.test.js.map +1 -0
  401. package/dist/work-sources/adapters/github.d.ts +290 -0
  402. package/dist/work-sources/adapters/github.d.ts.map +1 -0
  403. package/dist/work-sources/adapters/github.js +803 -0
  404. package/dist/work-sources/adapters/github.js.map +1 -0
  405. package/dist/work-sources/adapters/index.d.ts +10 -0
  406. package/dist/work-sources/adapters/index.d.ts.map +1 -0
  407. package/dist/work-sources/adapters/index.js +31 -0
  408. package/dist/work-sources/adapters/index.js.map +1 -0
  409. package/dist/work-sources/errors.d.ts +40 -0
  410. package/dist/work-sources/errors.d.ts.map +1 -0
  411. package/dist/work-sources/errors.js +54 -0
  412. package/dist/work-sources/errors.js.map +1 -0
  413. package/dist/work-sources/index.d.ts +105 -0
  414. package/dist/work-sources/index.d.ts.map +1 -0
  415. package/dist/work-sources/index.js +24 -0
  416. package/dist/work-sources/index.js.map +1 -0
  417. package/dist/work-sources/manager.d.ts +370 -0
  418. package/dist/work-sources/manager.d.ts.map +1 -0
  419. package/dist/work-sources/manager.js +61 -0
  420. package/dist/work-sources/manager.js.map +1 -0
  421. package/dist/work-sources/registry.d.ts +128 -0
  422. package/dist/work-sources/registry.d.ts.map +1 -0
  423. package/dist/work-sources/registry.js +132 -0
  424. package/dist/work-sources/registry.js.map +1 -0
  425. package/dist/work-sources/types.d.ts +127 -0
  426. package/dist/work-sources/types.d.ts.map +1 -0
  427. package/dist/work-sources/types.js +8 -0
  428. package/dist/work-sources/types.js.map +1 -0
  429. package/package.json +23 -0
  430. package/src/config/__tests__/agent.test.ts +864 -0
  431. package/src/config/__tests__/interpolate.test.ts +644 -0
  432. package/src/config/__tests__/loader.test.ts +784 -0
  433. package/src/config/__tests__/merge.test.ts +751 -0
  434. package/src/config/__tests__/parser.test.ts +533 -0
  435. package/src/config/__tests__/schema.test.ts +873 -0
  436. package/src/config/index.ts +119 -0
  437. package/src/config/interpolate.ts +189 -0
  438. package/src/config/loader.ts +472 -0
  439. package/src/config/merge.ts +246 -0
  440. package/src/config/parser.ts +376 -0
  441. package/src/config/schema.ts +346 -0
  442. package/src/fleet-manager/__tests__/coverage.test.ts +2869 -0
  443. package/src/fleet-manager/__tests__/errors.test.ts +660 -0
  444. package/src/fleet-manager/__tests__/event-helpers.test.ts +448 -0
  445. package/src/fleet-manager/__tests__/integration.test.ts +1209 -0
  446. package/src/fleet-manager/__tests__/job-control.test.ts +283 -0
  447. package/src/fleet-manager/__tests__/job-manager.test.ts +869 -0
  448. package/src/fleet-manager/__tests__/job-queue.test.ts +401 -0
  449. package/src/fleet-manager/__tests__/reload.test.ts +751 -0
  450. package/src/fleet-manager/__tests__/status-queries.test.ts +595 -0
  451. package/src/fleet-manager/__tests__/trigger.test.ts +601 -0
  452. package/src/fleet-manager/errors.ts +747 -0
  453. package/src/fleet-manager/event-types.ts +378 -0
  454. package/src/fleet-manager/fleet-manager.ts +2315 -0
  455. package/src/fleet-manager/index.ts +128 -0
  456. package/src/fleet-manager/job-manager.ts +663 -0
  457. package/src/fleet-manager/job-queue.ts +798 -0
  458. package/src/fleet-manager/types.ts +839 -0
  459. package/src/index.ts +32 -0
  460. package/src/runner/__tests__/errors.test.ts +382 -0
  461. package/src/runner/__tests__/job-executor.test.ts +1708 -0
  462. package/src/runner/__tests__/message-processor.test.ts +960 -0
  463. package/src/runner/__tests__/sdk-adapter.test.ts +626 -0
  464. package/src/runner/errors.ts +307 -0
  465. package/src/runner/index.ts +57 -0
  466. package/src/runner/job-executor.ts +448 -0
  467. package/src/runner/message-processor.ts +355 -0
  468. package/src/runner/sdk-adapter.ts +191 -0
  469. package/src/runner/types.ts +158 -0
  470. package/src/scheduler/__tests__/errors.test.ts +159 -0
  471. package/src/scheduler/__tests__/interval.test.ts +515 -0
  472. package/src/scheduler/__tests__/schedule-runner.test.ts +798 -0
  473. package/src/scheduler/__tests__/schedule-state.test.ts +671 -0
  474. package/src/scheduler/__tests__/scheduler.test.ts +1280 -0
  475. package/src/scheduler/errors.ts +101 -0
  476. package/src/scheduler/index.ts +53 -0
  477. package/src/scheduler/interval.ts +189 -0
  478. package/src/scheduler/schedule-runner.ts +442 -0
  479. package/src/scheduler/schedule-state.ts +211 -0
  480. package/src/scheduler/scheduler.ts +570 -0
  481. package/src/scheduler/types.ts +216 -0
  482. package/src/state/__tests__/directory.test.ts +595 -0
  483. package/src/state/__tests__/fleet-state.test.ts +868 -0
  484. package/src/state/__tests__/job-metadata-schema.test.ts +414 -0
  485. package/src/state/__tests__/job-metadata.test.ts +831 -0
  486. package/src/state/__tests__/job-output.test.ts +856 -0
  487. package/src/state/__tests__/session-schema.test.ts +378 -0
  488. package/src/state/__tests__/session.test.ts +604 -0
  489. package/src/state/directory.ts +217 -0
  490. package/src/state/errors.ts +97 -0
  491. package/src/state/fleet-state.ts +284 -0
  492. package/src/state/index.ts +79 -0
  493. package/src/state/job-metadata.ts +445 -0
  494. package/src/state/job-output.ts +316 -0
  495. package/src/state/schemas/__tests__/job-output.test.ts +338 -0
  496. package/src/state/schemas/fleet-state.ts +120 -0
  497. package/src/state/schemas/index.ts +67 -0
  498. package/src/state/schemas/job-metadata.ts +181 -0
  499. package/src/state/schemas/job-output.ts +177 -0
  500. package/src/state/schemas/session-info.ts +92 -0
  501. package/src/state/session.ts +253 -0
  502. package/src/state/types.ts +59 -0
  503. package/src/state/utils/__tests__/atomic.test.ts +723 -0
  504. package/src/state/utils/__tests__/reads.test.ts +1071 -0
  505. package/src/state/utils/atomic.ts +221 -0
  506. package/src/state/utils/index.ts +6 -0
  507. package/src/state/utils/reads.ts +512 -0
  508. package/src/work-sources/__tests__/github.test.ts +1800 -0
  509. package/src/work-sources/__tests__/manager.test.ts +529 -0
  510. package/src/work-sources/__tests__/registry.test.ts +477 -0
  511. package/src/work-sources/__tests__/types.test.ts +479 -0
  512. package/src/work-sources/adapters/github.ts +1166 -0
  513. package/src/work-sources/adapters/index.ts +64 -0
  514. package/src/work-sources/errors.ts +71 -0
  515. package/src/work-sources/index.ts +148 -0
  516. package/src/work-sources/manager.ts +413 -0
  517. package/src/work-sources/registry.ts +178 -0
  518. package/src/work-sources/types.ts +161 -0
  519. package/tsconfig.json +9 -0
  520. package/vitest.config.ts +19 -0
@@ -0,0 +1,784 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdir, writeFile, rm, realpath } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import {
6
+ loadConfig,
7
+ safeLoadConfig,
8
+ findConfigFile,
9
+ CONFIG_FILE_NAMES,
10
+ ConfigNotFoundError,
11
+ AgentLoadError,
12
+ } from "../loader.js";
13
+ import { FileReadError, SchemaValidationError } from "../parser.js";
14
+ import { UndefinedVariableError } from "../interpolate.js";
15
+
16
+ // Helper to create a temp directory structure
17
+ async function createTempDir(): Promise<string> {
18
+ const baseDir = join(tmpdir(), `herdctl-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
19
+ await mkdir(baseDir, { recursive: true });
20
+ // Resolve to real path to handle macOS /var -> /private/var symlink
21
+ return await realpath(baseDir);
22
+ }
23
+
24
+ // Helper to create a file
25
+ async function createFile(filePath: string, content: string): Promise<void> {
26
+ await mkdir(join(filePath, ".."), { recursive: true });
27
+ await writeFile(filePath, content, "utf-8");
28
+ }
29
+
30
+ describe("CONFIG_FILE_NAMES", () => {
31
+ it("includes herdctl.yaml and herdctl.yml", () => {
32
+ expect(CONFIG_FILE_NAMES).toContain("herdctl.yaml");
33
+ expect(CONFIG_FILE_NAMES).toContain("herdctl.yml");
34
+ expect(CONFIG_FILE_NAMES).toHaveLength(2);
35
+ });
36
+ });
37
+
38
+ describe("findConfigFile", () => {
39
+ let tempDir: string;
40
+
41
+ beforeEach(async () => {
42
+ tempDir = await createTempDir();
43
+ });
44
+
45
+ afterEach(async () => {
46
+ await rm(tempDir, { recursive: true, force: true });
47
+ });
48
+
49
+ it("finds herdctl.yaml in the current directory", async () => {
50
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
51
+
52
+ const result = await findConfigFile(tempDir);
53
+
54
+ expect(result).not.toBeNull();
55
+ expect(result!.path).toBe(join(tempDir, "herdctl.yaml"));
56
+ });
57
+
58
+ it("finds herdctl.yml in the current directory", async () => {
59
+ await createFile(join(tempDir, "herdctl.yml"), "version: 1");
60
+
61
+ const result = await findConfigFile(tempDir);
62
+
63
+ expect(result).not.toBeNull();
64
+ expect(result!.path).toBe(join(tempDir, "herdctl.yml"));
65
+ });
66
+
67
+ it("prefers herdctl.yaml over herdctl.yml", async () => {
68
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
69
+ await createFile(join(tempDir, "herdctl.yml"), "version: 1");
70
+
71
+ const result = await findConfigFile(tempDir);
72
+
73
+ expect(result).not.toBeNull();
74
+ expect(result!.path).toBe(join(tempDir, "herdctl.yaml"));
75
+ });
76
+
77
+ it("finds config file in parent directory", async () => {
78
+ const subDir = join(tempDir, "sub", "deep", "nested");
79
+ await mkdir(subDir, { recursive: true });
80
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
81
+
82
+ const result = await findConfigFile(subDir);
83
+
84
+ expect(result).not.toBeNull();
85
+ expect(result!.path).toBe(join(tempDir, "herdctl.yaml"));
86
+ });
87
+
88
+ it("finds config file in intermediate parent directory", async () => {
89
+ const subDir = join(tempDir, "sub", "deep", "nested");
90
+ const intermediateDir = join(tempDir, "sub");
91
+ await mkdir(subDir, { recursive: true });
92
+ await createFile(join(intermediateDir, "herdctl.yaml"), "version: 1");
93
+
94
+ const result = await findConfigFile(subDir);
95
+
96
+ expect(result).not.toBeNull();
97
+ expect(result!.path).toBe(join(intermediateDir, "herdctl.yaml"));
98
+ });
99
+
100
+ it("returns null when no config file is found", async () => {
101
+ const result = await findConfigFile(tempDir);
102
+
103
+ expect(result).toBeNull();
104
+ });
105
+
106
+ it("returns searched paths when config is found", async () => {
107
+ const subDir = join(tempDir, "sub");
108
+ await mkdir(subDir, { recursive: true });
109
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
110
+
111
+ const result = await findConfigFile(subDir);
112
+
113
+ expect(result).not.toBeNull();
114
+ expect(result!.searchedPaths).toContain(join(subDir, "herdctl.yaml"));
115
+ expect(result!.searchedPaths).toContain(join(subDir, "herdctl.yml"));
116
+ expect(result!.searchedPaths).toContain(join(tempDir, "herdctl.yaml"));
117
+ });
118
+ });
119
+
120
+ describe("ConfigNotFoundError", () => {
121
+ it("creates error with correct properties", () => {
122
+ const searchedPaths = ["/path/a", "/path/b"];
123
+ const error = new ConfigNotFoundError("/start/dir", searchedPaths);
124
+
125
+ expect(error.name).toBe("ConfigNotFoundError");
126
+ expect(error.startDirectory).toBe("/start/dir");
127
+ expect(error.searchedPaths).toEqual(searchedPaths);
128
+ expect(error.message).toContain("No herdctl configuration file found");
129
+ expect(error.message).toContain("/start/dir");
130
+ });
131
+ });
132
+
133
+ describe("AgentLoadError", () => {
134
+ it("creates error with correct properties", () => {
135
+ const cause = new Error("File not found");
136
+ const error = new AgentLoadError("./agents/test.yaml", cause, "test-agent");
137
+
138
+ expect(error.name).toBe("AgentLoadError");
139
+ expect(error.agentPath).toBe("./agents/test.yaml");
140
+ expect(error.agentName).toBe("test-agent");
141
+ expect(error.cause).toBe(cause);
142
+ expect(error.message).toContain("test.yaml");
143
+ expect(error.message).toContain("test-agent");
144
+ expect(error.message).toContain("File not found");
145
+ });
146
+
147
+ it("creates error without agent name", () => {
148
+ const cause = new Error("File not found");
149
+ const error = new AgentLoadError("./agents/test.yaml", cause);
150
+
151
+ expect(error.agentName).toBeUndefined();
152
+ expect(error.message).not.toContain("(undefined)");
153
+ });
154
+ });
155
+
156
+ describe("loadConfig", () => {
157
+ let tempDir: string;
158
+
159
+ beforeEach(async () => {
160
+ tempDir = await createTempDir();
161
+ });
162
+
163
+ afterEach(async () => {
164
+ await rm(tempDir, { recursive: true, force: true });
165
+ });
166
+
167
+ describe("file discovery", () => {
168
+ it("loads config from explicit yaml file path", async () => {
169
+ const configPath = join(tempDir, "herdctl.yaml");
170
+ await createFile(configPath, "version: 1");
171
+
172
+ const result = await loadConfig(configPath);
173
+
174
+ expect(result.fleet.version).toBe(1);
175
+ expect(result.configPath).toBe(configPath);
176
+ expect(result.configDir).toBe(tempDir);
177
+ });
178
+
179
+ it("loads config from explicit yml file path", async () => {
180
+ const configPath = join(tempDir, "herdctl.yml");
181
+ await createFile(configPath, "version: 1");
182
+
183
+ const result = await loadConfig(configPath);
184
+
185
+ expect(result.fleet.version).toBe(1);
186
+ expect(result.configPath).toBe(configPath);
187
+ });
188
+
189
+ it("searches from directory when path is a directory", async () => {
190
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
191
+
192
+ const result = await loadConfig(tempDir);
193
+
194
+ expect(result.fleet.version).toBe(1);
195
+ expect(result.configPath).toBe(join(tempDir, "herdctl.yaml"));
196
+ });
197
+
198
+ it("throws ConfigNotFoundError when no config found in directory", async () => {
199
+ await expect(loadConfig(tempDir)).rejects.toThrow(ConfigNotFoundError);
200
+ });
201
+
202
+ it("throws FileReadError when specified file does not exist", async () => {
203
+ const nonExistentPath = join(tempDir, "nonexistent.yaml");
204
+
205
+ await expect(loadConfig(nonExistentPath)).rejects.toThrow(FileReadError);
206
+ });
207
+ });
208
+
209
+ describe("fleet configuration parsing", () => {
210
+ it("parses empty config with defaults", async () => {
211
+ await createFile(join(tempDir, "herdctl.yaml"), "");
212
+
213
+ const result = await loadConfig(tempDir);
214
+
215
+ expect(result.fleet.version).toBe(1);
216
+ expect(result.fleet.agents).toEqual([]);
217
+ });
218
+
219
+ it("parses complete fleet configuration", async () => {
220
+ const config = `
221
+ version: 1
222
+ fleet:
223
+ name: test-fleet
224
+ description: A test fleet
225
+ defaults:
226
+ model: claude-sonnet-4-20250514
227
+ max_turns: 50
228
+ workspace:
229
+ root: ./workspace
230
+ `;
231
+ await createFile(join(tempDir, "herdctl.yaml"), config);
232
+
233
+ const result = await loadConfig(tempDir);
234
+
235
+ expect(result.fleet.fleet?.name).toBe("test-fleet");
236
+ expect(result.fleet.fleet?.description).toBe("A test fleet");
237
+ expect(result.fleet.defaults?.model).toBe("claude-sonnet-4-20250514");
238
+ expect(result.fleet.defaults?.max_turns).toBe(50);
239
+ expect(result.fleet.workspace?.root).toBe("./workspace");
240
+ });
241
+
242
+ it("throws on invalid YAML syntax", async () => {
243
+ await createFile(join(tempDir, "herdctl.yaml"), "invalid: yaml: syntax:");
244
+
245
+ await expect(loadConfig(tempDir)).rejects.toThrow("Invalid YAML syntax");
246
+ });
247
+
248
+ it("throws SchemaValidationError on invalid schema", async () => {
249
+ await createFile(join(tempDir, "herdctl.yaml"), "version: -1");
250
+
251
+ await expect(loadConfig(tempDir)).rejects.toThrow(SchemaValidationError);
252
+ });
253
+ });
254
+
255
+ describe("agent loading", () => {
256
+ it("loads agents referenced in fleet config", async () => {
257
+ const fleetConfig = `
258
+ version: 1
259
+ agents:
260
+ - path: ./agents/test-agent.yaml
261
+ `;
262
+ const agentConfig = `
263
+ name: test-agent
264
+ description: A test agent
265
+ `;
266
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
267
+ await createFile(join(tempDir, "agents", "test-agent.yaml"), agentConfig);
268
+
269
+ const result = await loadConfig(tempDir);
270
+
271
+ expect(result.agents).toHaveLength(1);
272
+ expect(result.agents[0].name).toBe("test-agent");
273
+ expect(result.agents[0].description).toBe("A test agent");
274
+ expect(result.agents[0].configPath).toBe(join(tempDir, "agents", "test-agent.yaml"));
275
+ });
276
+
277
+ it("loads multiple agents", async () => {
278
+ const fleetConfig = `
279
+ version: 1
280
+ agents:
281
+ - path: ./agents/agent1.yaml
282
+ - path: ./agents/agent2.yaml
283
+ `;
284
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
285
+ await createFile(join(tempDir, "agents", "agent1.yaml"), "name: agent-one");
286
+ await createFile(join(tempDir, "agents", "agent2.yaml"), "name: agent-two");
287
+
288
+ const result = await loadConfig(tempDir);
289
+
290
+ expect(result.agents).toHaveLength(2);
291
+ expect(result.agents[0].name).toBe("agent-one");
292
+ expect(result.agents[1].name).toBe("agent-two");
293
+ });
294
+
295
+ it("throws AgentLoadError when agent file not found", async () => {
296
+ const fleetConfig = `
297
+ version: 1
298
+ agents:
299
+ - path: ./agents/nonexistent.yaml
300
+ `;
301
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
302
+
303
+ await expect(loadConfig(tempDir)).rejects.toThrow(AgentLoadError);
304
+ });
305
+
306
+ it("throws AgentLoadError when agent YAML is invalid", async () => {
307
+ const fleetConfig = `
308
+ version: 1
309
+ agents:
310
+ - path: ./agents/invalid.yaml
311
+ `;
312
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
313
+ await createFile(join(tempDir, "agents", "invalid.yaml"), "invalid: yaml: syntax:");
314
+
315
+ await expect(loadConfig(tempDir)).rejects.toThrow(AgentLoadError);
316
+ });
317
+
318
+ it("throws AgentLoadError when agent schema is invalid", async () => {
319
+ const fleetConfig = `
320
+ version: 1
321
+ agents:
322
+ - path: ./agents/invalid.yaml
323
+ `;
324
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
325
+ // Missing required 'name' field
326
+ await createFile(join(tempDir, "agents", "invalid.yaml"), "description: no name");
327
+
328
+ await expect(loadConfig(tempDir)).rejects.toThrow(AgentLoadError);
329
+ });
330
+
331
+ it("resolves absolute agent paths correctly", async () => {
332
+ const agentPath = join(tempDir, "elsewhere", "agent.yaml");
333
+ const fleetConfig = `
334
+ version: 1
335
+ agents:
336
+ - path: ${agentPath}
337
+ `;
338
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
339
+ await createFile(agentPath, "name: elsewhere-agent");
340
+
341
+ const result = await loadConfig(tempDir);
342
+
343
+ expect(result.agents[0].name).toBe("elsewhere-agent");
344
+ expect(result.agents[0].configPath).toBe(agentPath);
345
+ });
346
+ });
347
+
348
+ describe("defaults merging", () => {
349
+ it("merges fleet defaults into agent config", async () => {
350
+ const fleetConfig = `
351
+ version: 1
352
+ defaults:
353
+ model: claude-sonnet-4-20250514
354
+ max_turns: 100
355
+ permission_mode: acceptEdits
356
+ agents:
357
+ - path: ./agents/test.yaml
358
+ `;
359
+ const agentConfig = `
360
+ name: test-agent
361
+ `;
362
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
363
+ await createFile(join(tempDir, "agents", "test.yaml"), agentConfig);
364
+
365
+ const result = await loadConfig(tempDir);
366
+
367
+ expect(result.agents[0].model).toBe("claude-sonnet-4-20250514");
368
+ expect(result.agents[0].max_turns).toBe(100);
369
+ expect(result.agents[0].permission_mode).toBe("acceptEdits");
370
+ });
371
+
372
+ it("agent values override fleet defaults", async () => {
373
+ const fleetConfig = `
374
+ version: 1
375
+ defaults:
376
+ model: claude-sonnet-4-20250514
377
+ max_turns: 100
378
+ agents:
379
+ - path: ./agents/test.yaml
380
+ `;
381
+ const agentConfig = `
382
+ name: test-agent
383
+ model: claude-opus-4-20250514
384
+ max_turns: 50
385
+ `;
386
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
387
+ await createFile(join(tempDir, "agents", "test.yaml"), agentConfig);
388
+
389
+ const result = await loadConfig(tempDir);
390
+
391
+ expect(result.agents[0].model).toBe("claude-opus-4-20250514");
392
+ expect(result.agents[0].max_turns).toBe(50);
393
+ });
394
+
395
+ it("deep merges nested objects", async () => {
396
+ const fleetConfig = `
397
+ version: 1
398
+ defaults:
399
+ permissions:
400
+ mode: acceptEdits
401
+ allowed_tools:
402
+ - Read
403
+ - Write
404
+ agents:
405
+ - path: ./agents/test.yaml
406
+ `;
407
+ const agentConfig = `
408
+ name: test-agent
409
+ permissions:
410
+ allowed_tools:
411
+ - Bash
412
+ `;
413
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
414
+ await createFile(join(tempDir, "agents", "test.yaml"), agentConfig);
415
+
416
+ const result = await loadConfig(tempDir);
417
+
418
+ // Mode should come from defaults
419
+ expect(result.agents[0].permissions?.mode).toBe("acceptEdits");
420
+ // Arrays are replaced, not merged
421
+ expect(result.agents[0].permissions?.allowed_tools).toEqual(["Bash"]);
422
+ });
423
+
424
+ it("skips merging when mergeDefaults is false", async () => {
425
+ const fleetConfig = `
426
+ version: 1
427
+ defaults:
428
+ model: claude-sonnet-4-20250514
429
+ agents:
430
+ - path: ./agents/test.yaml
431
+ `;
432
+ const agentConfig = `
433
+ name: test-agent
434
+ `;
435
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
436
+ await createFile(join(tempDir, "agents", "test.yaml"), agentConfig);
437
+
438
+ const result = await loadConfig(tempDir, { mergeDefaults: false });
439
+
440
+ expect(result.agents[0].model).toBeUndefined();
441
+ });
442
+ });
443
+
444
+ describe("environment interpolation", () => {
445
+ it("interpolates environment variables in fleet config", async () => {
446
+ const fleetConfig = `
447
+ version: 1
448
+ fleet:
449
+ name: \${FLEET_NAME}
450
+ `;
451
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
452
+
453
+ const result = await loadConfig(tempDir, {
454
+ env: { FLEET_NAME: "my-fleet" },
455
+ });
456
+
457
+ expect(result.fleet.fleet?.name).toBe("my-fleet");
458
+ });
459
+
460
+ it("interpolates environment variables in agent config", async () => {
461
+ const fleetConfig = `
462
+ version: 1
463
+ agents:
464
+ - path: ./agents/test.yaml
465
+ `;
466
+ const agentConfig = `
467
+ name: test-agent
468
+ model: \${MODEL_NAME}
469
+ `;
470
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
471
+ await createFile(join(tempDir, "agents", "test.yaml"), agentConfig);
472
+
473
+ const result = await loadConfig(tempDir, {
474
+ env: { MODEL_NAME: "claude-sonnet-4-20250514" },
475
+ });
476
+
477
+ expect(result.agents[0].model).toBe("claude-sonnet-4-20250514");
478
+ });
479
+
480
+ it("uses default values for undefined variables", async () => {
481
+ const fleetConfig = `
482
+ version: 1
483
+ fleet:
484
+ name: \${FLEET_NAME:-default-fleet}
485
+ `;
486
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
487
+
488
+ const result = await loadConfig(tempDir, { env: {} });
489
+
490
+ expect(result.fleet.fleet?.name).toBe("default-fleet");
491
+ });
492
+
493
+ it("throws UndefinedVariableError for undefined variables without default", async () => {
494
+ const fleetConfig = `
495
+ version: 1
496
+ fleet:
497
+ name: \${UNDEFINED_VAR}
498
+ `;
499
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
500
+
501
+ await expect(loadConfig(tempDir, { env: {} })).rejects.toThrow(UndefinedVariableError);
502
+ });
503
+
504
+ it("skips interpolation when interpolate is false", async () => {
505
+ const fleetConfig = `
506
+ version: 1
507
+ fleet:
508
+ name: \${FLEET_NAME}
509
+ `;
510
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
511
+
512
+ const result = await loadConfig(tempDir, {
513
+ interpolate: false,
514
+ env: { FLEET_NAME: "my-fleet" },
515
+ });
516
+
517
+ expect(result.fleet.fleet?.name).toBe("${FLEET_NAME}");
518
+ });
519
+ });
520
+
521
+ describe("result structure", () => {
522
+ it("returns correct structure with all fields", async () => {
523
+ const fleetConfig = `
524
+ version: 1
525
+ fleet:
526
+ name: test-fleet
527
+ agents:
528
+ - path: ./agents/test.yaml
529
+ `;
530
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
531
+ await createFile(join(tempDir, "agents", "test.yaml"), "name: test-agent");
532
+
533
+ const result = await loadConfig(tempDir);
534
+
535
+ expect(result).toHaveProperty("fleet");
536
+ expect(result).toHaveProperty("agents");
537
+ expect(result).toHaveProperty("configPath");
538
+ expect(result).toHaveProperty("configDir");
539
+ expect(result.configPath).toBe(join(tempDir, "herdctl.yaml"));
540
+ expect(result.configDir).toBe(tempDir);
541
+ });
542
+
543
+ it("returns empty agents array when no agents defined", async () => {
544
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
545
+
546
+ const result = await loadConfig(tempDir);
547
+
548
+ expect(result.agents).toEqual([]);
549
+ });
550
+ });
551
+ });
552
+
553
+ describe("safeLoadConfig", () => {
554
+ let tempDir: string;
555
+
556
+ beforeEach(async () => {
557
+ tempDir = await createTempDir();
558
+ });
559
+
560
+ afterEach(async () => {
561
+ await rm(tempDir, { recursive: true, force: true });
562
+ });
563
+
564
+ it("returns success result when config loads successfully", async () => {
565
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
566
+
567
+ const result = await safeLoadConfig(tempDir);
568
+
569
+ expect(result.success).toBe(true);
570
+ if (result.success) {
571
+ expect(result.data.fleet.version).toBe(1);
572
+ }
573
+ });
574
+
575
+ it("returns failure result with ConfigNotFoundError", async () => {
576
+ const result = await safeLoadConfig(tempDir);
577
+
578
+ expect(result.success).toBe(false);
579
+ if (!result.success) {
580
+ expect(result.error).toBeInstanceOf(ConfigNotFoundError);
581
+ }
582
+ });
583
+
584
+ it("returns failure result with FileReadError", async () => {
585
+ const result = await safeLoadConfig(join(tempDir, "nonexistent.yaml"));
586
+
587
+ expect(result.success).toBe(false);
588
+ if (!result.success) {
589
+ expect(result.error).toBeInstanceOf(FileReadError);
590
+ }
591
+ });
592
+
593
+ it("returns failure result with SchemaValidationError", async () => {
594
+ await createFile(join(tempDir, "herdctl.yaml"), "version: invalid");
595
+
596
+ const result = await safeLoadConfig(tempDir);
597
+
598
+ expect(result.success).toBe(false);
599
+ if (!result.success) {
600
+ expect(result.error).toBeInstanceOf(SchemaValidationError);
601
+ }
602
+ });
603
+
604
+ it("returns failure result with AgentLoadError", async () => {
605
+ const fleetConfig = `
606
+ version: 1
607
+ agents:
608
+ - path: ./nonexistent.yaml
609
+ `;
610
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
611
+
612
+ const result = await safeLoadConfig(tempDir);
613
+
614
+ expect(result.success).toBe(false);
615
+ if (!result.success) {
616
+ expect(result.error).toBeInstanceOf(AgentLoadError);
617
+ }
618
+ });
619
+
620
+ it("wraps non-ConfigError errors", async () => {
621
+ // Create a file that will cause an unexpected error
622
+ // This is difficult to trigger, so we'll test the general behavior
623
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
624
+
625
+ const result = await safeLoadConfig(tempDir);
626
+
627
+ expect(result.success).toBe(true);
628
+ });
629
+ });
630
+
631
+ describe("auto-discovery from cwd", () => {
632
+ let tempDir: string;
633
+ let originalCwd: string;
634
+
635
+ beforeEach(async () => {
636
+ tempDir = await createTempDir();
637
+ originalCwd = process.cwd();
638
+ });
639
+
640
+ afterEach(async () => {
641
+ process.chdir(originalCwd);
642
+ await rm(tempDir, { recursive: true, force: true });
643
+ });
644
+
645
+ it("finds config from current working directory when no path provided", async () => {
646
+ await createFile(join(tempDir, "herdctl.yaml"), "version: 1");
647
+ process.chdir(tempDir);
648
+
649
+ const result = await loadConfig();
650
+
651
+ expect(result.fleet.version).toBe(1);
652
+ expect(result.configPath).toBe(join(tempDir, "herdctl.yaml"));
653
+ });
654
+
655
+ it("throws ConfigNotFoundError when no config in cwd hierarchy", async () => {
656
+ process.chdir(tempDir);
657
+
658
+ await expect(loadConfig()).rejects.toThrow(ConfigNotFoundError);
659
+ });
660
+ });
661
+
662
+ describe("integration scenarios", () => {
663
+ let tempDir: string;
664
+
665
+ beforeEach(async () => {
666
+ tempDir = await createTempDir();
667
+ });
668
+
669
+ afterEach(async () => {
670
+ await rm(tempDir, { recursive: true, force: true });
671
+ });
672
+
673
+ it("handles a realistic fleet configuration", async () => {
674
+ const fleetConfig = `
675
+ version: 1
676
+
677
+ fleet:
678
+ name: example-fleet
679
+ description: An example herdctl fleet
680
+
681
+ defaults:
682
+ model: claude-sonnet-4-20250514
683
+ max_turns: 50
684
+ permission_mode: acceptEdits
685
+ permissions:
686
+ allowed_tools:
687
+ - Read
688
+ - Edit
689
+ - Write
690
+ - Bash
691
+ - Glob
692
+ - Grep
693
+
694
+ workspace:
695
+ root: ./workspace
696
+ auto_clone: true
697
+ clone_depth: 1
698
+
699
+ agents:
700
+ - path: ./agents/coder.yaml
701
+ - path: ./agents/reviewer.yaml
702
+
703
+ chat:
704
+ discord:
705
+ enabled: true
706
+ token_env: DISCORD_TOKEN
707
+ `;
708
+
709
+ const coderAgent = `
710
+ name: coder
711
+ description: Writes code based on issues
712
+ system_prompt: |
713
+ You are a coding assistant.
714
+ Write clean, maintainable code.
715
+ permissions:
716
+ allowed_tools:
717
+ - Read
718
+ - Edit
719
+ - Write
720
+ - Bash
721
+ `;
722
+
723
+ const reviewerAgent = `
724
+ name: reviewer
725
+ description: Reviews pull requests
726
+ model: claude-opus-4-20250514
727
+ max_turns: 25
728
+ system_prompt: |
729
+ You are a code reviewer.
730
+ Focus on code quality and security.
731
+ `;
732
+
733
+ await createFile(join(tempDir, "herdctl.yaml"), fleetConfig);
734
+ await createFile(join(tempDir, "agents", "coder.yaml"), coderAgent);
735
+ await createFile(join(tempDir, "agents", "reviewer.yaml"), reviewerAgent);
736
+
737
+ const result = await loadConfig(tempDir, {
738
+ env: { DISCORD_TOKEN: "test-token" },
739
+ });
740
+
741
+ // Fleet config
742
+ expect(result.fleet.fleet?.name).toBe("example-fleet");
743
+ expect(result.fleet.defaults?.model).toBe("claude-sonnet-4-20250514");
744
+ expect(result.fleet.workspace?.root).toBe("./workspace");
745
+ expect(result.fleet.chat?.discord?.enabled).toBe(true);
746
+
747
+ // Agents loaded
748
+ expect(result.agents).toHaveLength(2);
749
+
750
+ // Coder agent - inherits defaults
751
+ const coder = result.agents.find(a => a.name === "coder");
752
+ expect(coder).toBeDefined();
753
+ expect(coder!.model).toBe("claude-sonnet-4-20250514"); // from defaults
754
+ expect(coder!.max_turns).toBe(50); // from defaults
755
+ expect(coder!.permission_mode).toBe("acceptEdits"); // from defaults
756
+ expect(coder!.permissions?.allowed_tools).toEqual(["Read", "Edit", "Write", "Bash"]); // agent override
757
+
758
+ // Reviewer agent - overrides defaults
759
+ const reviewer = result.agents.find(a => a.name === "reviewer");
760
+ expect(reviewer).toBeDefined();
761
+ expect(reviewer!.model).toBe("claude-opus-4-20250514"); // agent override
762
+ expect(reviewer!.max_turns).toBe(25); // agent override
763
+ expect(reviewer!.permission_mode).toBe("acceptEdits"); // from defaults
764
+ });
765
+
766
+ it("handles nested directory structure", async () => {
767
+ const subDir = join(tempDir, "projects", "myproject", "src");
768
+ await mkdir(subDir, { recursive: true });
769
+
770
+ await createFile(join(tempDir, "projects", "myproject", "herdctl.yaml"), `
771
+ version: 1
772
+ agents:
773
+ - path: ./config/agent.yaml
774
+ `);
775
+ await createFile(join(tempDir, "projects", "myproject", "config", "agent.yaml"), `
776
+ name: nested-agent
777
+ `);
778
+
779
+ const result = await loadConfig(subDir);
780
+
781
+ expect(result.configPath).toBe(join(tempDir, "projects", "myproject", "herdctl.yaml"));
782
+ expect(result.agents[0].name).toBe("nested-agent");
783
+ });
784
+ });