@fractary/faber 2.4.40 → 2.4.42

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 (353) hide show
  1. package/dist/__tests__/config.test.d.ts +0 -0
  2. package/dist/__tests__/config.test.d.ts.map +0 -0
  3. package/dist/__tests__/config.test.js +0 -0
  4. package/dist/__tests__/config.test.js.map +0 -0
  5. package/dist/__tests__/integration/forge-integration.test.d.ts +0 -0
  6. package/dist/__tests__/integration/forge-integration.test.d.ts.map +0 -0
  7. package/dist/__tests__/integration/forge-integration.test.js +2 -2
  8. package/dist/__tests__/integration/forge-integration.test.js.map +1 -1
  9. package/dist/__tests__/integration/init-workflow.test.d.ts +0 -0
  10. package/dist/__tests__/integration/init-workflow.test.d.ts.map +0 -0
  11. package/dist/__tests__/integration/init-workflow.test.js +0 -0
  12. package/dist/__tests__/integration/init-workflow.test.js.map +0 -0
  13. package/dist/agents/index.d.ts +0 -0
  14. package/dist/agents/index.d.ts.map +0 -0
  15. package/dist/agents/index.js +0 -0
  16. package/dist/agents/index.js.map +0 -0
  17. package/dist/agents/selector.d.ts +0 -0
  18. package/dist/agents/selector.d.ts.map +0 -0
  19. package/dist/agents/selector.js +0 -0
  20. package/dist/agents/selector.js.map +0 -0
  21. package/dist/agents/type-registry.d.ts +0 -0
  22. package/dist/agents/type-registry.d.ts.map +0 -0
  23. package/dist/agents/type-registry.js +0 -0
  24. package/dist/agents/type-registry.js.map +0 -0
  25. package/dist/agents/types.d.ts +0 -0
  26. package/dist/agents/types.d.ts.map +0 -0
  27. package/dist/agents/types.js +0 -0
  28. package/dist/agents/types.js.map +0 -0
  29. package/dist/auth/github-app.d.ts +0 -0
  30. package/dist/auth/github-app.d.ts.map +0 -0
  31. package/dist/auth/github-app.js +0 -0
  32. package/dist/auth/github-app.js.map +0 -0
  33. package/dist/auth/index.d.ts +0 -0
  34. package/dist/auth/index.d.ts.map +0 -0
  35. package/dist/auth/index.js +0 -0
  36. package/dist/auth/index.js.map +0 -0
  37. package/dist/changelog/index.d.ts +0 -0
  38. package/dist/changelog/index.d.ts.map +0 -0
  39. package/dist/changelog/index.js +0 -0
  40. package/dist/changelog/index.js.map +0 -0
  41. package/dist/changelog/manager.d.ts +0 -0
  42. package/dist/changelog/manager.d.ts.map +0 -0
  43. package/dist/changelog/manager.js +0 -0
  44. package/dist/changelog/manager.js.map +0 -0
  45. package/dist/changelog/types.d.ts +0 -0
  46. package/dist/changelog/types.d.ts.map +0 -0
  47. package/dist/changelog/types.js +0 -0
  48. package/dist/changelog/types.js.map +0 -0
  49. package/dist/cloud/__tests__/resource-manager.test.d.ts +7 -0
  50. package/dist/cloud/__tests__/resource-manager.test.d.ts.map +1 -0
  51. package/dist/cloud/__tests__/resource-manager.test.js +741 -0
  52. package/dist/cloud/__tests__/resource-manager.test.js.map +1 -0
  53. package/dist/cloud/__tests__/terraform-manager.test.d.ts +7 -0
  54. package/dist/cloud/__tests__/terraform-manager.test.d.ts.map +1 -0
  55. package/dist/cloud/__tests__/terraform-manager.test.js +493 -0
  56. package/dist/cloud/__tests__/terraform-manager.test.js.map +1 -0
  57. package/dist/cloud/index.d.ts +9 -0
  58. package/dist/cloud/index.d.ts.map +1 -0
  59. package/dist/cloud/index.js +11 -0
  60. package/dist/cloud/index.js.map +1 -0
  61. package/dist/cloud/resource-manager.d.ts +81 -0
  62. package/dist/cloud/resource-manager.d.ts.map +1 -0
  63. package/dist/cloud/resource-manager.js +288 -0
  64. package/dist/cloud/resource-manager.js.map +1 -0
  65. package/dist/cloud/terraform-manager.d.ts +108 -0
  66. package/dist/cloud/terraform-manager.d.ts.map +1 -0
  67. package/dist/cloud/terraform-manager.js +552 -0
  68. package/dist/cloud/terraform-manager.js.map +1 -0
  69. package/dist/cloud/types.d.ts +281 -0
  70. package/dist/cloud/types.d.ts.map +1 -0
  71. package/dist/cloud/types.js +7 -0
  72. package/dist/cloud/types.js.map +1 -0
  73. package/dist/config/__tests__/initializer.test.d.ts +0 -0
  74. package/dist/config/__tests__/initializer.test.d.ts.map +0 -0
  75. package/dist/config/__tests__/initializer.test.js +0 -0
  76. package/dist/config/__tests__/initializer.test.js.map +0 -0
  77. package/dist/config/initializer.d.ts +0 -0
  78. package/dist/config/initializer.d.ts.map +0 -0
  79. package/dist/config/initializer.js +0 -0
  80. package/dist/config/initializer.js.map +0 -0
  81. package/dist/config/updater.d.ts +0 -0
  82. package/dist/config/updater.d.ts.map +0 -0
  83. package/dist/config/updater.js +0 -0
  84. package/dist/config/updater.js.map +0 -0
  85. package/dist/config/validator.d.ts +0 -0
  86. package/dist/config/validator.d.ts.map +0 -0
  87. package/dist/config/validator.js +0 -0
  88. package/dist/config/validator.js.map +0 -0
  89. package/dist/config.d.ts +0 -0
  90. package/dist/config.d.ts.map +0 -0
  91. package/dist/config.js +0 -0
  92. package/dist/config.js.map +0 -0
  93. package/dist/defaults.d.ts +0 -0
  94. package/dist/defaults.d.ts.map +0 -0
  95. package/dist/defaults.js +0 -0
  96. package/dist/defaults.js.map +0 -0
  97. package/dist/errors.d.ts +0 -0
  98. package/dist/errors.d.ts.map +0 -0
  99. package/dist/errors.js +0 -0
  100. package/dist/errors.js.map +0 -0
  101. package/dist/executors/cli-entry.d.ts +0 -0
  102. package/dist/executors/cli-entry.d.ts.map +0 -0
  103. package/dist/executors/cli-entry.js +0 -0
  104. package/dist/executors/cli-entry.js.map +0 -0
  105. package/dist/executors/index.d.ts +0 -0
  106. package/dist/executors/index.d.ts.map +0 -0
  107. package/dist/executors/index.js +0 -0
  108. package/dist/executors/index.js.map +0 -0
  109. package/dist/executors/providers/claude.d.ts +0 -0
  110. package/dist/executors/providers/claude.d.ts.map +0 -0
  111. package/dist/executors/providers/claude.js +0 -0
  112. package/dist/executors/providers/claude.js.map +0 -0
  113. package/dist/executors/providers/http.d.ts +0 -0
  114. package/dist/executors/providers/http.d.ts.map +0 -0
  115. package/dist/executors/providers/http.js +0 -0
  116. package/dist/executors/providers/http.js.map +0 -0
  117. package/dist/executors/providers/index.d.ts +0 -0
  118. package/dist/executors/providers/index.d.ts.map +0 -0
  119. package/dist/executors/providers/index.js +0 -0
  120. package/dist/executors/providers/index.js.map +0 -0
  121. package/dist/executors/providers/openai-compatible.d.ts +0 -0
  122. package/dist/executors/providers/openai-compatible.d.ts.map +0 -0
  123. package/dist/executors/providers/openai-compatible.js +0 -0
  124. package/dist/executors/providers/openai-compatible.js.map +0 -0
  125. package/dist/executors/providers/openai.d.ts +0 -0
  126. package/dist/executors/providers/openai.d.ts.map +0 -0
  127. package/dist/executors/providers/openai.js +0 -0
  128. package/dist/executors/providers/openai.js.map +0 -0
  129. package/dist/executors/registry.d.ts +0 -0
  130. package/dist/executors/registry.d.ts.map +0 -0
  131. package/dist/executors/registry.js +0 -0
  132. package/dist/executors/registry.js.map +0 -0
  133. package/dist/executors/types.d.ts +0 -0
  134. package/dist/executors/types.d.ts.map +0 -0
  135. package/dist/executors/types.js +0 -0
  136. package/dist/executors/types.js.map +0 -0
  137. package/dist/executors/workflow-executor.d.ts +0 -0
  138. package/dist/executors/workflow-executor.d.ts.map +0 -0
  139. package/dist/executors/workflow-executor.js +0 -0
  140. package/dist/executors/workflow-executor.js.map +0 -0
  141. package/dist/faber/__tests__/config-manager.test.d.ts +7 -0
  142. package/dist/faber/__tests__/config-manager.test.d.ts.map +1 -0
  143. package/dist/faber/__tests__/config-manager.test.js +637 -0
  144. package/dist/faber/__tests__/config-manager.test.js.map +1 -0
  145. package/dist/faber/__tests__/state-manager.test.d.ts +7 -0
  146. package/dist/faber/__tests__/state-manager.test.d.ts.map +1 -0
  147. package/dist/faber/__tests__/state-manager.test.js +541 -0
  148. package/dist/faber/__tests__/state-manager.test.js.map +1 -0
  149. package/dist/faber/config-manager.d.ts +123 -0
  150. package/dist/faber/config-manager.d.ts.map +1 -0
  151. package/dist/faber/config-manager.js +449 -0
  152. package/dist/faber/config-manager.js.map +1 -0
  153. package/dist/faber/index.d.ts +17 -0
  154. package/dist/faber/index.d.ts.map +1 -0
  155. package/dist/faber/index.js +15 -0
  156. package/dist/faber/index.js.map +1 -0
  157. package/dist/faber/state-manager.d.ts +124 -0
  158. package/dist/faber/state-manager.d.ts.map +1 -0
  159. package/dist/faber/state-manager.js +558 -0
  160. package/dist/faber/state-manager.js.map +1 -0
  161. package/dist/faber/types.d.ts +383 -0
  162. package/dist/faber/types.d.ts.map +1 -0
  163. package/dist/faber/types.js +60 -0
  164. package/dist/faber/types.js.map +1 -0
  165. package/dist/index.d.ts +0 -0
  166. package/dist/index.d.ts.map +0 -0
  167. package/dist/index.js +0 -0
  168. package/dist/index.js.map +0 -0
  169. package/dist/logs/index.d.ts +0 -0
  170. package/dist/logs/index.d.ts.map +0 -0
  171. package/dist/logs/index.js +0 -0
  172. package/dist/logs/index.js.map +0 -0
  173. package/dist/logs/manager.d.ts +0 -0
  174. package/dist/logs/manager.d.ts.map +0 -0
  175. package/dist/logs/manager.js +0 -0
  176. package/dist/logs/manager.js.map +0 -0
  177. package/dist/logs/types.d.ts +0 -0
  178. package/dist/logs/types.d.ts.map +0 -0
  179. package/dist/logs/types.js +0 -0
  180. package/dist/logs/types.js.map +0 -0
  181. package/dist/paths.d.ts +0 -0
  182. package/dist/paths.d.ts.map +0 -0
  183. package/dist/paths.js +0 -0
  184. package/dist/paths.js.map +0 -0
  185. package/dist/repo/git.d.ts +0 -0
  186. package/dist/repo/git.d.ts.map +0 -0
  187. package/dist/repo/git.js +0 -0
  188. package/dist/repo/git.js.map +0 -0
  189. package/dist/repo/index.d.ts +0 -0
  190. package/dist/repo/index.d.ts.map +0 -0
  191. package/dist/repo/index.js +0 -0
  192. package/dist/repo/index.js.map +0 -0
  193. package/dist/repo/manager.d.ts +0 -0
  194. package/dist/repo/manager.d.ts.map +0 -0
  195. package/dist/repo/manager.js +0 -0
  196. package/dist/repo/manager.js.map +0 -0
  197. package/dist/repo/providers/bitbucket.d.ts +0 -0
  198. package/dist/repo/providers/bitbucket.d.ts.map +0 -0
  199. package/dist/repo/providers/bitbucket.js +0 -0
  200. package/dist/repo/providers/bitbucket.js.map +0 -0
  201. package/dist/repo/providers/github.d.ts +0 -0
  202. package/dist/repo/providers/github.d.ts.map +0 -0
  203. package/dist/repo/providers/github.js +0 -0
  204. package/dist/repo/providers/github.js.map +0 -0
  205. package/dist/repo/providers/gitlab.d.ts +0 -0
  206. package/dist/repo/providers/gitlab.d.ts.map +0 -0
  207. package/dist/repo/providers/gitlab.js +0 -0
  208. package/dist/repo/providers/gitlab.js.map +0 -0
  209. package/dist/repo/providers/index.d.ts +0 -0
  210. package/dist/repo/providers/index.d.ts.map +0 -0
  211. package/dist/repo/providers/index.js +0 -0
  212. package/dist/repo/providers/index.js.map +0 -0
  213. package/dist/repo/types.d.ts +0 -0
  214. package/dist/repo/types.d.ts.map +0 -0
  215. package/dist/repo/types.js +0 -0
  216. package/dist/repo/types.js.map +0 -0
  217. package/dist/spec/__tests__/manager.test.d.ts +0 -0
  218. package/dist/spec/__tests__/manager.test.d.ts.map +0 -0
  219. package/dist/spec/__tests__/manager.test.js +2 -2
  220. package/dist/spec/__tests__/manager.test.js.map +1 -1
  221. package/dist/spec/index.d.ts +3 -3
  222. package/dist/spec/index.d.ts.map +1 -1
  223. package/dist/spec/index.js +3 -3
  224. package/dist/spec/index.js.map +1 -1
  225. package/dist/spec/manager.d.ts +1 -1
  226. package/dist/spec/manager.d.ts.map +1 -1
  227. package/dist/spec/manager.js +3 -3
  228. package/dist/spec/manager.js.map +1 -1
  229. package/dist/spec/templates.d.ts +1 -1
  230. package/dist/spec/templates.d.ts.map +1 -1
  231. package/dist/spec/templates.js +0 -0
  232. package/dist/spec/templates.js.map +0 -0
  233. package/dist/spec/types.d.ts +2 -2
  234. package/dist/spec/types.d.ts.map +1 -1
  235. package/dist/spec/types.js +0 -0
  236. package/dist/spec/types.js.map +0 -0
  237. package/dist/state/index.d.ts +0 -0
  238. package/dist/state/index.d.ts.map +0 -0
  239. package/dist/state/index.js +0 -0
  240. package/dist/state/index.js.map +0 -0
  241. package/dist/state/manager.d.ts +0 -0
  242. package/dist/state/manager.d.ts.map +0 -0
  243. package/dist/state/manager.js +0 -0
  244. package/dist/state/manager.js.map +0 -0
  245. package/dist/state/session.d.ts +0 -0
  246. package/dist/state/session.d.ts.map +0 -0
  247. package/dist/state/session.js +0 -0
  248. package/dist/state/session.js.map +0 -0
  249. package/dist/state/types.d.ts +0 -0
  250. package/dist/state/types.d.ts.map +0 -0
  251. package/dist/state/types.js +0 -0
  252. package/dist/state/types.js.map +0 -0
  253. package/dist/storage/codex-adapter.d.ts +0 -0
  254. package/dist/storage/codex-adapter.d.ts.map +0 -0
  255. package/dist/storage/codex-adapter.js +0 -0
  256. package/dist/storage/codex-adapter.js.map +0 -0
  257. package/dist/storage/index.d.ts +0 -0
  258. package/dist/storage/index.d.ts.map +0 -0
  259. package/dist/storage/index.js +0 -0
  260. package/dist/storage/index.js.map +0 -0
  261. package/dist/storage/local.d.ts +0 -0
  262. package/dist/storage/local.d.ts.map +0 -0
  263. package/dist/storage/local.js +0 -0
  264. package/dist/storage/local.js.map +0 -0
  265. package/dist/types.d.ts +0 -0
  266. package/dist/types.d.ts.map +0 -0
  267. package/dist/types.js +0 -0
  268. package/dist/types.js.map +0 -0
  269. package/dist/work/index.d.ts +0 -0
  270. package/dist/work/index.d.ts.map +0 -0
  271. package/dist/work/index.js +0 -0
  272. package/dist/work/index.js.map +0 -0
  273. package/dist/work/manager.d.ts +0 -0
  274. package/dist/work/manager.d.ts.map +0 -0
  275. package/dist/work/manager.js +0 -0
  276. package/dist/work/manager.js.map +0 -0
  277. package/dist/work/providers/github.d.ts +0 -0
  278. package/dist/work/providers/github.d.ts.map +0 -0
  279. package/dist/work/providers/github.js +0 -0
  280. package/dist/work/providers/github.js.map +0 -0
  281. package/dist/work/providers/jira.d.ts +0 -0
  282. package/dist/work/providers/jira.d.ts.map +0 -0
  283. package/dist/work/providers/jira.js +0 -0
  284. package/dist/work/providers/jira.js.map +0 -0
  285. package/dist/work/providers/linear.d.ts +0 -0
  286. package/dist/work/providers/linear.d.ts.map +0 -0
  287. package/dist/work/providers/linear.js +0 -0
  288. package/dist/work/providers/linear.js.map +0 -0
  289. package/dist/work/types.d.ts +0 -0
  290. package/dist/work/types.d.ts.map +0 -0
  291. package/dist/work/types.js +0 -0
  292. package/dist/work/types.js.map +0 -0
  293. package/dist/workflow/__tests__/agent-executor.test.d.ts +0 -0
  294. package/dist/workflow/__tests__/agent-executor.test.d.ts.map +0 -0
  295. package/dist/workflow/__tests__/agent-executor.test.js +2 -2
  296. package/dist/workflow/__tests__/agent-executor.test.js.map +1 -1
  297. package/dist/workflow/__tests__/resolver.test.d.ts +0 -0
  298. package/dist/workflow/__tests__/resolver.test.d.ts.map +0 -0
  299. package/dist/workflow/__tests__/resolver.test.js +96 -0
  300. package/dist/workflow/__tests__/resolver.test.js.map +1 -1
  301. package/dist/workflow/agent-executor.d.ts +1 -1
  302. package/dist/workflow/agent-executor.d.ts.map +1 -1
  303. package/dist/workflow/agent-executor.js +3 -3
  304. package/dist/workflow/agent-executor.js.map +1 -1
  305. package/dist/workflow/faber.d.ts +0 -0
  306. package/dist/workflow/faber.d.ts.map +0 -0
  307. package/dist/workflow/faber.js +0 -0
  308. package/dist/workflow/faber.js.map +0 -0
  309. package/dist/workflow/index.d.ts +0 -0
  310. package/dist/workflow/index.d.ts.map +0 -0
  311. package/dist/workflow/index.js +0 -0
  312. package/dist/workflow/index.js.map +0 -0
  313. package/dist/workflow/resolver.d.ts +6 -0
  314. package/dist/workflow/resolver.d.ts.map +1 -1
  315. package/dist/workflow/resolver.js +16 -1
  316. package/dist/workflow/resolver.js.map +1 -1
  317. package/dist/workflow/types.d.ts +0 -0
  318. package/dist/workflow/types.d.ts.map +0 -0
  319. package/dist/workflow/types.js +0 -0
  320. package/dist/workflow/types.js.map +0 -0
  321. package/dist/workflows/__tests__/registry.test.d.ts +0 -0
  322. package/dist/workflows/__tests__/registry.test.d.ts.map +0 -0
  323. package/dist/workflows/__tests__/registry.test.js +0 -0
  324. package/dist/workflows/__tests__/registry.test.js.map +0 -0
  325. package/dist/workflows/registry.d.ts +0 -0
  326. package/dist/workflows/registry.d.ts.map +0 -0
  327. package/dist/workflows/registry.js +0 -0
  328. package/dist/workflows/registry.js.map +0 -0
  329. package/package.json +2 -2
  330. package/dist/cli/commands/logs.d.ts +0 -6
  331. package/dist/cli/commands/logs.d.ts.map +0 -1
  332. package/dist/cli/commands/logs.js +0 -215
  333. package/dist/cli/commands/logs.js.map +0 -1
  334. package/dist/cli/commands/repo.d.ts +0 -6
  335. package/dist/cli/commands/repo.d.ts.map +0 -1
  336. package/dist/cli/commands/repo.js +0 -260
  337. package/dist/cli/commands/repo.js.map +0 -1
  338. package/dist/cli/commands/spec.d.ts +0 -6
  339. package/dist/cli/commands/spec.d.ts.map +0 -1
  340. package/dist/cli/commands/spec.js +0 -184
  341. package/dist/cli/commands/spec.js.map +0 -1
  342. package/dist/cli/commands/work.d.ts +0 -6
  343. package/dist/cli/commands/work.d.ts.map +0 -1
  344. package/dist/cli/commands/work.js +0 -113
  345. package/dist/cli/commands/work.js.map +0 -1
  346. package/dist/cli/commands/workflow.d.ts +0 -6
  347. package/dist/cli/commands/workflow.d.ts.map +0 -1
  348. package/dist/cli/commands/workflow.js +0 -214
  349. package/dist/cli/commands/workflow.js.map +0 -1
  350. package/dist/cli/index.d.ts +0 -8
  351. package/dist/cli/index.d.ts.map +0 -1
  352. package/dist/cli/index.js +0 -27
  353. package/dist/cli/index.js.map +0 -1
@@ -0,0 +1,637 @@
1
+ /**
2
+ * @fractary/faber - FaberConfigManager Tests
3
+ *
4
+ * Unit tests for FaberConfigManager with workflow resolution
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { FaberConfigManager } from '../config-manager';
9
+ describe('FaberConfigManager', () => {
10
+ const testDir = path.join(__dirname, '__test-config-manager__');
11
+ const configDir = path.join(testDir, '.fractary', 'plugins', 'faber');
12
+ const configPath = path.join(configDir, 'config.json');
13
+ const workflowsDir = path.join(configDir, 'workflows');
14
+ let manager;
15
+ /**
16
+ * Create a minimal valid configuration
17
+ */
18
+ function createMinimalConfig() {
19
+ return {
20
+ schema_version: '2.0',
21
+ default_workflow: 'default',
22
+ integrations: {
23
+ work_plugin: 'fractary-work',
24
+ repo_plugin: 'fractary-repo',
25
+ },
26
+ workflows: [
27
+ {
28
+ id: 'default',
29
+ description: 'Default workflow',
30
+ phases: createDefaultPhases(),
31
+ autonomy: {
32
+ level: 'guarded',
33
+ },
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ /**
39
+ * Create default phase definitions
40
+ */
41
+ function createDefaultPhases() {
42
+ const defaultPhase = {
43
+ enabled: true,
44
+ description: 'Default phase',
45
+ steps: [],
46
+ };
47
+ return {
48
+ frame: { ...defaultPhase, description: 'Frame phase' },
49
+ architect: { ...defaultPhase, description: 'Architect phase' },
50
+ build: { ...defaultPhase, description: 'Build phase' },
51
+ evaluate: { ...defaultPhase, description: 'Evaluate phase' },
52
+ release: { ...defaultPhase, description: 'Release phase' },
53
+ };
54
+ }
55
+ /**
56
+ * Write config file to disk
57
+ */
58
+ function writeConfig(config) {
59
+ fs.mkdirSync(configDir, { recursive: true });
60
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
61
+ }
62
+ /**
63
+ * Write workflow file to disk
64
+ */
65
+ function writeWorkflowFile(filename, workflow) {
66
+ fs.mkdirSync(workflowsDir, { recursive: true });
67
+ const filePath = path.join(workflowsDir, filename);
68
+ fs.writeFileSync(filePath, JSON.stringify(workflow, null, 2), 'utf-8');
69
+ }
70
+ beforeEach(() => {
71
+ // Clean up test directory before each test
72
+ if (fs.existsSync(testDir)) {
73
+ fs.rmSync(testDir, { recursive: true, force: true });
74
+ }
75
+ fs.mkdirSync(testDir, { recursive: true });
76
+ // Mock process.cwd() to return testDir
77
+ jest.spyOn(process, 'cwd').mockReturnValue(testDir);
78
+ // Create manager with default config
79
+ manager = new FaberConfigManager();
80
+ });
81
+ afterEach(() => {
82
+ // Clean up test directory after each test
83
+ if (fs.existsSync(testDir)) {
84
+ fs.rmSync(testDir, { recursive: true, force: true });
85
+ }
86
+ // Restore mocks
87
+ jest.restoreAllMocks();
88
+ });
89
+ describe('Constructor', () => {
90
+ it('should construct with default config', () => {
91
+ const mgr = new FaberConfigManager();
92
+ expect(mgr).toBeDefined();
93
+ });
94
+ it('should construct with custom config', () => {
95
+ const mgr = new FaberConfigManager({
96
+ configDir: '.custom/faber',
97
+ configFile: 'faber.json',
98
+ workflowsDir: 'defs',
99
+ });
100
+ expect(mgr).toBeDefined();
101
+ });
102
+ it('should construct with partial config', () => {
103
+ const mgr = new FaberConfigManager({
104
+ configDir: '.custom/faber',
105
+ });
106
+ expect(mgr).toBeDefined();
107
+ });
108
+ });
109
+ describe('Config Loading', () => {
110
+ describe('configExists', () => {
111
+ it('should return false when config does not exist', () => {
112
+ expect(manager.configExists()).toBe(false);
113
+ });
114
+ it('should return true when config exists', () => {
115
+ writeConfig(createMinimalConfig());
116
+ expect(manager.configExists()).toBe(true);
117
+ });
118
+ });
119
+ describe('load', () => {
120
+ it('should throw error when config not found', async () => {
121
+ await expect(manager.load())
122
+ .rejects.toThrow('Configuration not found');
123
+ });
124
+ it('should load valid configuration', async () => {
125
+ const config = createMinimalConfig();
126
+ writeConfig(config);
127
+ const loaded = await manager.load();
128
+ expect(loaded.schema_version).toBe('2.0');
129
+ expect(loaded.integrations.work_plugin).toBe('fractary-work');
130
+ });
131
+ it('should throw error on invalid JSON', async () => {
132
+ fs.mkdirSync(configDir, { recursive: true });
133
+ fs.writeFileSync(configPath, '{ invalid json }', 'utf-8');
134
+ await expect(manager.load())
135
+ .rejects.toThrow('Failed to load configuration');
136
+ });
137
+ });
138
+ describe('getConfig', () => {
139
+ it('should load config if not already loaded', async () => {
140
+ writeConfig(createMinimalConfig());
141
+ const config = await manager.getConfig();
142
+ expect(config.schema_version).toBe('2.0');
143
+ });
144
+ it('should return cached config on subsequent calls', async () => {
145
+ writeConfig(createMinimalConfig());
146
+ const config1 = await manager.getConfig();
147
+ const config2 = await manager.getConfig();
148
+ expect(config1).toBe(config2); // Same reference
149
+ });
150
+ });
151
+ describe('reload', () => {
152
+ it('should clear cache and reload', async () => {
153
+ const config1 = createMinimalConfig();
154
+ writeConfig(config1);
155
+ await manager.load();
156
+ // Modify config on disk
157
+ const config2 = { ...config1, schema_version: '3.0' };
158
+ writeConfig(config2);
159
+ const reloaded = await manager.reload();
160
+ expect(reloaded.schema_version).toBe('3.0');
161
+ });
162
+ });
163
+ });
164
+ describe('Validation', () => {
165
+ describe('validate', () => {
166
+ it('should report missing schema_version', async () => {
167
+ const config = createMinimalConfig();
168
+ delete config.schema_version;
169
+ writeConfig(config);
170
+ const result = await manager.validate();
171
+ expect(result.valid).toBe(false);
172
+ expect(result.errors).toContain('Missing required field: schema_version');
173
+ });
174
+ it('should report missing integrations', async () => {
175
+ const config = createMinimalConfig();
176
+ delete config.integrations;
177
+ writeConfig(config);
178
+ const result = await manager.validate();
179
+ expect(result.valid).toBe(false);
180
+ expect(result.errors).toContain('Missing required field: integrations');
181
+ });
182
+ it('should report missing work_plugin', async () => {
183
+ const config = createMinimalConfig();
184
+ delete config.integrations.work_plugin;
185
+ writeConfig(config);
186
+ const result = await manager.validate();
187
+ expect(result.valid).toBe(false);
188
+ expect(result.errors).toContain('Missing required integration: work_plugin');
189
+ });
190
+ it('should report missing repo_plugin', async () => {
191
+ const config = createMinimalConfig();
192
+ delete config.integrations.repo_plugin;
193
+ writeConfig(config);
194
+ const result = await manager.validate();
195
+ expect(result.valid).toBe(false);
196
+ expect(result.errors).toContain('Missing required integration: repo_plugin');
197
+ });
198
+ it('should report missing workflow file', async () => {
199
+ const config = createMinimalConfig();
200
+ config.workflows = [
201
+ { id: 'test', file: 'workflows/nonexistent.json' },
202
+ ];
203
+ writeConfig(config);
204
+ const result = await manager.validate();
205
+ expect(result.warnings).toContain('Workflow file not found: workflows/nonexistent.json');
206
+ });
207
+ it('should pass validation for valid config', async () => {
208
+ writeConfig(createMinimalConfig());
209
+ const result = await manager.validate();
210
+ expect(result.valid).toBe(true);
211
+ expect(result.errors).toHaveLength(0);
212
+ });
213
+ });
214
+ });
215
+ describe('Workflow Operations', () => {
216
+ describe('resolveWorkflow', () => {
217
+ it('should resolve default workflow', async () => {
218
+ writeConfig(createMinimalConfig());
219
+ const workflow = await manager.resolveWorkflow();
220
+ expect(workflow.id).toBe('default');
221
+ });
222
+ it('should resolve workflow by ID', async () => {
223
+ const config = createMinimalConfig();
224
+ config.workflows.push({
225
+ id: 'feature',
226
+ description: 'Feature workflow',
227
+ phases: createDefaultPhases(),
228
+ autonomy: { level: 'autonomous' },
229
+ });
230
+ writeConfig(config);
231
+ const workflow = await manager.resolveWorkflow('feature');
232
+ expect(workflow.id).toBe('feature');
233
+ expect(workflow.autonomy.level).toBe('autonomous');
234
+ });
235
+ it('should throw error for unknown workflow', async () => {
236
+ writeConfig(createMinimalConfig());
237
+ await expect(manager.resolveWorkflow('unknown'))
238
+ .rejects.toThrow('Workflow not found: unknown');
239
+ });
240
+ it('should resolve workflow from file reference', async () => {
241
+ const config = createMinimalConfig();
242
+ config.workflows = [
243
+ { id: 'file-workflow', file: 'workflows/custom.json' },
244
+ ];
245
+ writeConfig(config);
246
+ const fileWorkflow = {
247
+ id: 'file-workflow',
248
+ description: 'From file',
249
+ phases: createDefaultPhases(),
250
+ autonomy: { level: 'assisted' },
251
+ };
252
+ writeWorkflowFile('custom.json', fileWorkflow);
253
+ const workflow = await manager.resolveWorkflow('file-workflow');
254
+ expect(workflow.description).toBe('From file');
255
+ });
256
+ it('should cache resolved workflows', async () => {
257
+ writeConfig(createMinimalConfig());
258
+ const workflow1 = await manager.resolveWorkflow('default');
259
+ const workflow2 = await manager.resolveWorkflow('default');
260
+ expect(workflow1).toBe(workflow2); // Same reference from cache
261
+ });
262
+ });
263
+ describe('listWorkflows', () => {
264
+ it('should list all workflows', async () => {
265
+ const config = createMinimalConfig();
266
+ config.workflows.push({
267
+ id: 'feature',
268
+ description: 'Feature workflow',
269
+ phases: createDefaultPhases(),
270
+ autonomy: { level: 'autonomous' },
271
+ });
272
+ writeConfig(config);
273
+ const workflows = await manager.listWorkflows();
274
+ expect(workflows).toHaveLength(2);
275
+ expect(workflows.map(w => w.id)).toContain('default');
276
+ expect(workflows.map(w => w.id)).toContain('feature');
277
+ });
278
+ it('should identify file-based workflows', async () => {
279
+ const config = createMinimalConfig();
280
+ config.workflows = [
281
+ { id: 'file-workflow', file: 'workflows/custom.json' },
282
+ ];
283
+ writeConfig(config);
284
+ const workflows = await manager.listWorkflows();
285
+ expect(workflows[0].isFile).toBe(true);
286
+ expect(workflows[0].filePath).toBe('workflows/custom.json');
287
+ });
288
+ it('should identify inline workflows', async () => {
289
+ writeConfig(createMinimalConfig());
290
+ const workflows = await manager.listWorkflows();
291
+ expect(workflows[0].isFile).toBe(false);
292
+ expect(workflows[0].filePath).toBeUndefined();
293
+ });
294
+ });
295
+ describe('getWorkflowForLabels', () => {
296
+ it('should return null when no label mapping', async () => {
297
+ writeConfig(createMinimalConfig());
298
+ const workflowId = await manager.getWorkflowForLabels(['bug', 'urgent']);
299
+ expect(workflowId).toBeNull();
300
+ });
301
+ it('should return workflow ID for matching label', async () => {
302
+ const config = createMinimalConfig();
303
+ config.workflow_inference = {
304
+ label_mapping: {
305
+ bug: 'bugfix',
306
+ feature: 'feature-workflow',
307
+ },
308
+ };
309
+ writeConfig(config);
310
+ const workflowId = await manager.getWorkflowForLabels(['bug', 'urgent']);
311
+ expect(workflowId).toBe('bugfix');
312
+ });
313
+ it('should return first matching label', async () => {
314
+ const config = createMinimalConfig();
315
+ config.workflow_inference = {
316
+ label_mapping: {
317
+ bug: 'bugfix',
318
+ feature: 'feature-workflow',
319
+ },
320
+ };
321
+ writeConfig(config);
322
+ const workflowId = await manager.getWorkflowForLabels(['feature', 'bug']);
323
+ expect(workflowId).toBe('feature-workflow');
324
+ });
325
+ });
326
+ });
327
+ describe('Workflow Inheritance', () => {
328
+ it('should resolve workflow with extends', async () => {
329
+ const config = createMinimalConfig();
330
+ config.workflows.push({
331
+ id: 'child',
332
+ extends: 'default',
333
+ description: 'Child workflow',
334
+ phases: createDefaultPhases(),
335
+ autonomy: { level: 'autonomous' },
336
+ });
337
+ writeConfig(config);
338
+ const workflow = await manager.resolveWorkflow('child');
339
+ expect(workflow.id).toBe('child');
340
+ expect(workflow.description).toBe('Child workflow');
341
+ expect(workflow.autonomy.level).toBe('autonomous');
342
+ });
343
+ it('should merge phases from parent', async () => {
344
+ const config = createMinimalConfig();
345
+ const parentWorkflow = config.workflows[0];
346
+ parentWorkflow.phases.frame.steps = [
347
+ { id: 'parent-step', name: 'Parent Step' },
348
+ ];
349
+ config.workflows.push({
350
+ id: 'child',
351
+ extends: 'default',
352
+ phases: {
353
+ ...createDefaultPhases(),
354
+ frame: {
355
+ enabled: true,
356
+ steps: [{ id: 'child-step', name: 'Child Step' }],
357
+ },
358
+ },
359
+ autonomy: { level: 'guarded' },
360
+ });
361
+ writeConfig(config);
362
+ const workflow = await manager.resolveWorkflow('child');
363
+ // Child steps should come after parent steps
364
+ expect(workflow.phases.frame.steps).toHaveLength(2);
365
+ expect(workflow.phases.frame.steps[0].id).toBe('parent-step');
366
+ expect(workflow.phases.frame.steps[1].id).toBe('child-step');
367
+ });
368
+ it('should merge pre_steps and post_steps correctly', async () => {
369
+ const config = createMinimalConfig();
370
+ const parentWorkflow = config.workflows[0];
371
+ parentWorkflow.phases.frame.pre_steps = [
372
+ { id: 'parent-pre', name: 'Parent Pre' },
373
+ ];
374
+ parentWorkflow.phases.frame.post_steps = [
375
+ { id: 'parent-post', name: 'Parent Post' },
376
+ ];
377
+ config.workflows.push({
378
+ id: 'child',
379
+ extends: 'default',
380
+ phases: {
381
+ ...createDefaultPhases(),
382
+ frame: {
383
+ enabled: true,
384
+ pre_steps: [{ id: 'child-pre', name: 'Child Pre' }],
385
+ post_steps: [{ id: 'child-post', name: 'Child Post' }],
386
+ },
387
+ },
388
+ autonomy: { level: 'guarded' },
389
+ });
390
+ writeConfig(config);
391
+ const workflow = await manager.resolveWorkflow('child');
392
+ // Pre-steps: parent first, then child
393
+ expect(workflow.phases.frame.pre_steps).toHaveLength(2);
394
+ expect(workflow.phases.frame.pre_steps[0].id).toBe('parent-pre');
395
+ expect(workflow.phases.frame.pre_steps[1].id).toBe('child-pre');
396
+ // Post-steps: child first, then parent
397
+ expect(workflow.phases.frame.post_steps).toHaveLength(2);
398
+ expect(workflow.phases.frame.post_steps[0].id).toBe('child-post');
399
+ expect(workflow.phases.frame.post_steps[1].id).toBe('parent-post');
400
+ });
401
+ it('should apply skip_steps filter', async () => {
402
+ const config = createMinimalConfig();
403
+ const parentWorkflow = config.workflows[0];
404
+ parentWorkflow.phases.frame.steps = [
405
+ { id: 'step1', name: 'Step 1' },
406
+ { id: 'step2', name: 'Step 2' },
407
+ { id: 'step3', name: 'Step 3' },
408
+ ];
409
+ config.workflows.push({
410
+ id: 'child',
411
+ extends: 'default',
412
+ skip_steps: ['step2'],
413
+ phases: createDefaultPhases(),
414
+ autonomy: { level: 'guarded' },
415
+ });
416
+ writeConfig(config);
417
+ const workflow = await manager.resolveWorkflow('child');
418
+ expect(workflow.phases.frame.steps).toHaveLength(2);
419
+ expect(workflow.phases.frame.steps.map(s => s.id)).not.toContain('step2');
420
+ });
421
+ it('should inherit description from parent if not specified', async () => {
422
+ const config = createMinimalConfig();
423
+ config.workflows[0].description = 'Parent description';
424
+ config.workflows.push({
425
+ id: 'child',
426
+ extends: 'default',
427
+ phases: createDefaultPhases(),
428
+ autonomy: { level: 'guarded' },
429
+ // No description
430
+ });
431
+ writeConfig(config);
432
+ const workflow = await manager.resolveWorkflow('child');
433
+ expect(workflow.description).toBe('Parent description');
434
+ });
435
+ });
436
+ describe('Helper Methods', () => {
437
+ describe('getIntegrations', () => {
438
+ it('should return integrations config', async () => {
439
+ writeConfig(createMinimalConfig());
440
+ const integrations = await manager.getIntegrations();
441
+ expect(integrations.work_plugin).toBe('fractary-work');
442
+ expect(integrations.repo_plugin).toBe('fractary-repo');
443
+ });
444
+ });
445
+ describe('getLogging', () => {
446
+ it('should return undefined when not configured', async () => {
447
+ writeConfig(createMinimalConfig());
448
+ const logging = await manager.getLogging();
449
+ expect(logging).toBeUndefined();
450
+ });
451
+ it('should return logging config when present', async () => {
452
+ const config = createMinimalConfig();
453
+ config.logging = {
454
+ use_logs_plugin: true,
455
+ log_level: 'debug',
456
+ };
457
+ writeConfig(config);
458
+ const logging = await manager.getLogging();
459
+ expect(logging?.use_logs_plugin).toBe(true);
460
+ expect(logging?.log_level).toBe('debug');
461
+ });
462
+ });
463
+ describe('getSafety', () => {
464
+ it('should return undefined when not configured', async () => {
465
+ writeConfig(createMinimalConfig());
466
+ const safety = await manager.getSafety();
467
+ expect(safety).toBeUndefined();
468
+ });
469
+ it('should return safety config when present', async () => {
470
+ const config = createMinimalConfig();
471
+ config.safety = {
472
+ protected_paths: ['*.env', 'secrets/**'],
473
+ require_confirm_for: ['delete', 'force-push'],
474
+ };
475
+ writeConfig(config);
476
+ const safety = await manager.getSafety();
477
+ expect(safety?.protected_paths).toContain('*.env');
478
+ expect(safety?.require_confirm_for).toContain('delete');
479
+ });
480
+ });
481
+ describe('getDefaultWorkflowId', () => {
482
+ it('should return configured default', async () => {
483
+ const config = createMinimalConfig();
484
+ config.default_workflow = 'my-default';
485
+ writeConfig(config);
486
+ const defaultId = await manager.getDefaultWorkflowId();
487
+ expect(defaultId).toBe('my-default');
488
+ });
489
+ it('should return "default" when not configured', async () => {
490
+ const config = createMinimalConfig();
491
+ delete config.default_workflow;
492
+ writeConfig(config);
493
+ const defaultId = await manager.getDefaultWorkflowId();
494
+ expect(defaultId).toBe('default');
495
+ });
496
+ });
497
+ describe('getSchemaVersion', () => {
498
+ it('should return schema version', async () => {
499
+ writeConfig(createMinimalConfig());
500
+ const version = await manager.getSchemaVersion();
501
+ expect(version).toBe('2.0');
502
+ });
503
+ });
504
+ describe('isPathProtected', () => {
505
+ it('should return false when no safety config', async () => {
506
+ writeConfig(createMinimalConfig());
507
+ const isProtected = await manager.isPathProtected('.env');
508
+ expect(isProtected).toBe(false);
509
+ });
510
+ it('should match exact paths', async () => {
511
+ const config = createMinimalConfig();
512
+ config.safety = {
513
+ protected_paths: ['.env'],
514
+ };
515
+ writeConfig(config);
516
+ expect(await manager.isPathProtected('.env')).toBe(true);
517
+ expect(await manager.isPathProtected('.env.local')).toBe(false);
518
+ });
519
+ it('should match glob patterns with *', async () => {
520
+ const config = createMinimalConfig();
521
+ config.safety = {
522
+ protected_paths: ['*.env'],
523
+ };
524
+ writeConfig(config);
525
+ expect(await manager.isPathProtected('.env')).toBe(true);
526
+ expect(await manager.isPathProtected('local.env')).toBe(true);
527
+ expect(await manager.isPathProtected('.env.local')).toBe(false);
528
+ });
529
+ it('should match glob patterns with **', async () => {
530
+ const config = createMinimalConfig();
531
+ config.safety = {
532
+ protected_paths: ['secrets/**'],
533
+ };
534
+ writeConfig(config);
535
+ // The simple glob implementation converts ** to .* regex
536
+ expect(await manager.isPathProtected('secrets/api-key')).toBe(true);
537
+ expect(await manager.isPathProtected('other/secrets/key')).toBe(false);
538
+ });
539
+ });
540
+ describe('requiresConfirmation', () => {
541
+ it('should return false when no safety config', async () => {
542
+ writeConfig(createMinimalConfig());
543
+ const requires = await manager.requiresConfirmation('delete');
544
+ expect(requires).toBe(false);
545
+ });
546
+ it('should return true for configured actions', async () => {
547
+ const config = createMinimalConfig();
548
+ config.safety = {
549
+ require_confirm_for: ['delete', 'force-push'],
550
+ };
551
+ writeConfig(config);
552
+ expect(await manager.requiresConfirmation('delete')).toBe(true);
553
+ expect(await manager.requiresConfirmation('force-push')).toBe(true);
554
+ expect(await manager.requiresConfirmation('commit')).toBe(false);
555
+ });
556
+ });
557
+ describe('getPhaseSteps', () => {
558
+ it('should return flattened steps for a phase', async () => {
559
+ const config = createMinimalConfig();
560
+ const workflow = config.workflows[0];
561
+ workflow.phases.frame = {
562
+ enabled: true,
563
+ pre_steps: [{ id: 'pre1' }],
564
+ steps: [{ id: 'main1' }, { id: 'main2' }],
565
+ post_steps: [{ id: 'post1' }],
566
+ };
567
+ writeConfig(config);
568
+ const steps = await manager.getPhaseSteps('default', 'frame');
569
+ expect(steps).toHaveLength(4);
570
+ expect(steps.map(s => s.id)).toEqual(['pre1', 'main1', 'main2', 'post1']);
571
+ });
572
+ it('should handle phases with no steps', async () => {
573
+ writeConfig(createMinimalConfig());
574
+ const steps = await manager.getPhaseSteps('default', 'frame');
575
+ expect(steps).toHaveLength(0);
576
+ });
577
+ });
578
+ describe('getStep', () => {
579
+ it('should find step by ID across phases', async () => {
580
+ const config = createMinimalConfig();
581
+ const workflow = config.workflows[0];
582
+ workflow.phases.build.steps = [
583
+ { id: 'implement', name: 'Implement feature' },
584
+ ];
585
+ writeConfig(config);
586
+ const result = await manager.getStep('default', 'implement');
587
+ expect(result).not.toBeNull();
588
+ expect(result?.phase).toBe('build');
589
+ expect(result?.step.name).toBe('Implement feature');
590
+ });
591
+ it('should return null for unknown step', async () => {
592
+ writeConfig(createMinimalConfig());
593
+ const result = await manager.getStep('default', 'nonexistent');
594
+ expect(result).toBeNull();
595
+ });
596
+ });
597
+ });
598
+ describe('Edge Cases', () => {
599
+ it('should handle empty workflows array', async () => {
600
+ const config = createMinimalConfig();
601
+ config.workflows = [];
602
+ writeConfig(config);
603
+ const workflows = await manager.listWorkflows();
604
+ expect(workflows).toHaveLength(0);
605
+ });
606
+ it('should handle special characters in workflow IDs', async () => {
607
+ const config = createMinimalConfig();
608
+ config.workflows = [
609
+ {
610
+ id: 'workflow-with-special_chars.v2',
611
+ description: 'Special chars',
612
+ phases: createDefaultPhases(),
613
+ autonomy: { level: 'guarded' },
614
+ },
615
+ ];
616
+ writeConfig(config);
617
+ const workflow = await manager.resolveWorkflow('workflow-with-special_chars.v2');
618
+ expect(workflow.id).toBe('workflow-with-special_chars.v2');
619
+ });
620
+ it('should handle namespaced workflow IDs', async () => {
621
+ const config = createMinimalConfig();
622
+ config.workflows = [
623
+ {
624
+ id: 'default',
625
+ description: 'Default in project namespace',
626
+ phases: createDefaultPhases(),
627
+ autonomy: { level: 'guarded' },
628
+ },
629
+ ];
630
+ writeConfig(config);
631
+ // When no colon, defaults to 'project' namespace
632
+ const workflow = await manager.resolveWorkflow('default');
633
+ expect(workflow.id).toBe('default');
634
+ });
635
+ });
636
+ });
637
+ //# sourceMappingURL=config-manager.test.js.map