@renseiai/agentfactory 0.8.7 → 0.8.9

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 (276) hide show
  1. package/dist/src/config/index.d.ts +1 -1
  2. package/dist/src/config/index.d.ts.map +1 -1
  3. package/dist/src/config/index.js +1 -1
  4. package/dist/src/config/repository-config.d.ts +37 -0
  5. package/dist/src/config/repository-config.d.ts.map +1 -1
  6. package/dist/src/config/repository-config.js +47 -0
  7. package/dist/src/config/repository-config.test.js +140 -1
  8. package/dist/src/governor/decision-engine.d.ts +3 -0
  9. package/dist/src/governor/decision-engine.d.ts.map +1 -1
  10. package/dist/src/governor/decision-engine.js +11 -0
  11. package/dist/src/governor/decision-engine.test.js +33 -0
  12. package/dist/src/governor/event-types.d.ts +18 -1
  13. package/dist/src/governor/event-types.d.ts.map +1 -1
  14. package/dist/src/governor/event-types.js +4 -0
  15. package/dist/src/governor/governor-types.d.ts +1 -1
  16. package/dist/src/governor/governor-types.d.ts.map +1 -1
  17. package/dist/src/governor/governor.d.ts +17 -1
  18. package/dist/src/governor/governor.d.ts.map +1 -1
  19. package/dist/src/governor/governor.js +112 -1
  20. package/dist/src/governor/governor.test.js +155 -0
  21. package/dist/src/index.d.ts +1 -0
  22. package/dist/src/index.d.ts.map +1 -1
  23. package/dist/src/index.js +1 -0
  24. package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
  25. package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
  26. package/dist/src/merge-queue/adapters/github-native.js +243 -0
  27. package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
  28. package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
  29. package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
  30. package/dist/src/merge-queue/index.d.ts +18 -0
  31. package/dist/src/merge-queue/index.d.ts.map +1 -0
  32. package/dist/src/merge-queue/index.js +28 -0
  33. package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
  34. package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
  35. package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
  36. package/dist/src/merge-queue/types.d.ts +48 -0
  37. package/dist/src/merge-queue/types.d.ts.map +1 -0
  38. package/dist/src/merge-queue/types.js +8 -0
  39. package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
  40. package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
  41. package/dist/src/orchestrator/artifact-tracker.js +235 -0
  42. package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
  43. package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
  44. package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
  45. package/dist/src/orchestrator/context-manager.d.ts +72 -0
  46. package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
  47. package/dist/src/orchestrator/context-manager.js +120 -0
  48. package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
  49. package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
  50. package/dist/src/orchestrator/context-manager.test.js +137 -0
  51. package/dist/src/orchestrator/index.d.ts +8 -2
  52. package/dist/src/orchestrator/index.d.ts.map +1 -1
  53. package/dist/src/orchestrator/index.js +8 -1
  54. package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
  55. package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
  56. package/dist/src/orchestrator/orchestrator.d.ts +12 -0
  57. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  58. package/dist/src/orchestrator/orchestrator.js +282 -2
  59. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
  60. package/dist/src/orchestrator/parse-work-result.js +6 -0
  61. package/dist/src/orchestrator/parse-work-result.test.js +19 -0
  62. package/dist/src/orchestrator/state-recovery.d.ts +21 -2
  63. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
  64. package/dist/src/orchestrator/state-recovery.js +54 -2
  65. package/dist/src/orchestrator/state-recovery.test.js +106 -2
  66. package/dist/src/orchestrator/state-types.d.ts +62 -0
  67. package/dist/src/orchestrator/state-types.d.ts.map +1 -1
  68. package/dist/src/orchestrator/state-types.js +5 -1
  69. package/dist/src/orchestrator/summary-builder.d.ts +47 -0
  70. package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
  71. package/dist/src/orchestrator/summary-builder.js +240 -0
  72. package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
  73. package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
  74. package/dist/src/orchestrator/summary-builder.test.js +236 -0
  75. package/dist/src/orchestrator/types.d.ts +2 -0
  76. package/dist/src/orchestrator/types.d.ts.map +1 -1
  77. package/dist/src/orchestrator/work-types.d.ts +1 -1
  78. package/dist/src/orchestrator/work-types.d.ts.map +1 -1
  79. package/dist/src/providers/index.d.ts +64 -1
  80. package/dist/src/providers/index.d.ts.map +1 -1
  81. package/dist/src/providers/index.js +132 -1
  82. package/dist/src/providers/index.test.js +340 -2
  83. package/dist/src/routing/index.d.ts +7 -0
  84. package/dist/src/routing/index.d.ts.map +1 -0
  85. package/dist/src/routing/index.js +6 -0
  86. package/dist/src/routing/observation-recorder.d.ts +19 -0
  87. package/dist/src/routing/observation-recorder.d.ts.map +1 -0
  88. package/dist/src/routing/observation-recorder.js +73 -0
  89. package/dist/src/routing/observation-recorder.test.d.ts +2 -0
  90. package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
  91. package/dist/src/routing/observation-recorder.test.js +322 -0
  92. package/dist/src/routing/observation-store.d.ts +40 -0
  93. package/dist/src/routing/observation-store.d.ts.map +1 -0
  94. package/dist/src/routing/observation-store.js +1 -0
  95. package/dist/src/routing/observation-store.test.d.ts +2 -0
  96. package/dist/src/routing/observation-store.test.d.ts.map +1 -0
  97. package/dist/src/routing/observation-store.test.js +138 -0
  98. package/dist/src/routing/posterior-store.d.ts +12 -0
  99. package/dist/src/routing/posterior-store.d.ts.map +1 -0
  100. package/dist/src/routing/posterior-store.js +13 -0
  101. package/dist/src/routing/posterior-store.test.d.ts +2 -0
  102. package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
  103. package/dist/src/routing/posterior-store.test.js +37 -0
  104. package/dist/src/routing/reward.d.ts +16 -0
  105. package/dist/src/routing/reward.d.ts.map +1 -0
  106. package/dist/src/routing/reward.js +29 -0
  107. package/dist/src/routing/reward.test.d.ts +2 -0
  108. package/dist/src/routing/reward.test.d.ts.map +1 -0
  109. package/dist/src/routing/reward.test.js +210 -0
  110. package/dist/src/routing/routing-engine.d.ts +20 -0
  111. package/dist/src/routing/routing-engine.d.ts.map +1 -0
  112. package/dist/src/routing/routing-engine.js +113 -0
  113. package/dist/src/routing/routing-engine.test.d.ts +2 -0
  114. package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
  115. package/dist/src/routing/routing-engine.test.js +310 -0
  116. package/dist/src/routing/types.d.ts +157 -0
  117. package/dist/src/routing/types.d.ts.map +1 -0
  118. package/dist/src/routing/types.js +68 -0
  119. package/dist/src/routing/types.test.d.ts +2 -0
  120. package/dist/src/routing/types.test.d.ts.map +1 -0
  121. package/dist/src/routing/types.test.js +184 -0
  122. package/dist/src/templates/registry.test.js +2 -2
  123. package/dist/src/templates/types.d.ts +5 -0
  124. package/dist/src/templates/types.d.ts.map +1 -1
  125. package/dist/src/templates/types.js +3 -0
  126. package/dist/src/workflow/agent-cancellation.d.ts +37 -0
  127. package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
  128. package/dist/src/workflow/agent-cancellation.js +41 -0
  129. package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
  130. package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
  131. package/dist/src/workflow/agent-cancellation.test.js +86 -0
  132. package/dist/src/workflow/branching-router.d.ts +38 -0
  133. package/dist/src/workflow/branching-router.d.ts.map +1 -0
  134. package/dist/src/workflow/branching-router.js +52 -0
  135. package/dist/src/workflow/branching-router.test.d.ts +2 -0
  136. package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
  137. package/dist/src/workflow/branching-router.test.js +209 -0
  138. package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
  139. package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
  140. package/dist/src/workflow/concurrency-semaphore.js +46 -0
  141. package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
  142. package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
  143. package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
  144. package/dist/src/workflow/duration.d.ts +28 -0
  145. package/dist/src/workflow/duration.d.ts.map +1 -0
  146. package/dist/src/workflow/duration.js +57 -0
  147. package/dist/src/workflow/duration.test.d.ts +2 -0
  148. package/dist/src/workflow/duration.test.d.ts.map +1 -0
  149. package/dist/src/workflow/duration.test.js +74 -0
  150. package/dist/src/workflow/expression/ast.d.ts +53 -0
  151. package/dist/src/workflow/expression/ast.d.ts.map +1 -0
  152. package/dist/src/workflow/expression/ast.js +8 -0
  153. package/dist/src/workflow/expression/context.d.ts +40 -0
  154. package/dist/src/workflow/expression/context.d.ts.map +1 -0
  155. package/dist/src/workflow/expression/context.js +37 -0
  156. package/dist/src/workflow/expression/evaluator.d.ts +28 -0
  157. package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
  158. package/dist/src/workflow/expression/evaluator.js +165 -0
  159. package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
  160. package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
  161. package/dist/src/workflow/expression/evaluator.test.js +792 -0
  162. package/dist/src/workflow/expression/expression.test.d.ts +2 -0
  163. package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
  164. package/dist/src/workflow/expression/expression.test.js +516 -0
  165. package/dist/src/workflow/expression/helpers.d.ts +21 -0
  166. package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
  167. package/dist/src/workflow/expression/helpers.js +56 -0
  168. package/dist/src/workflow/expression/index.d.ts +55 -0
  169. package/dist/src/workflow/expression/index.d.ts.map +1 -0
  170. package/dist/src/workflow/expression/index.js +71 -0
  171. package/dist/src/workflow/expression/lexer.d.ts +37 -0
  172. package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
  173. package/dist/src/workflow/expression/lexer.js +166 -0
  174. package/dist/src/workflow/expression/parser.d.ts +23 -0
  175. package/dist/src/workflow/expression/parser.d.ts.map +1 -0
  176. package/dist/src/workflow/expression/parser.js +181 -0
  177. package/dist/src/workflow/gate-state.d.ts +115 -0
  178. package/dist/src/workflow/gate-state.d.ts.map +1 -0
  179. package/dist/src/workflow/gate-state.js +185 -0
  180. package/dist/src/workflow/gate-state.test.d.ts +2 -0
  181. package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
  182. package/dist/src/workflow/gate-state.test.js +251 -0
  183. package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
  184. package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
  185. package/dist/src/workflow/gates/gate-evaluator.js +243 -0
  186. package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
  187. package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
  188. package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
  189. package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
  190. package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
  191. package/dist/src/workflow/gates/signal-gate.js +216 -0
  192. package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
  193. package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
  194. package/dist/src/workflow/gates/signal-gate.test.js +199 -0
  195. package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
  196. package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
  197. package/dist/src/workflow/gates/timeout-engine.js +162 -0
  198. package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
  199. package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
  200. package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
  201. package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
  202. package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
  203. package/dist/src/workflow/gates/timer-gate.js +381 -0
  204. package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
  205. package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
  206. package/dist/src/workflow/gates/timer-gate.test.js +211 -0
  207. package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
  208. package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
  209. package/dist/src/workflow/gates/webhook-gate.js +216 -0
  210. package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
  211. package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
  212. package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
  213. package/dist/src/workflow/index.d.ts +31 -3
  214. package/dist/src/workflow/index.d.ts.map +1 -1
  215. package/dist/src/workflow/index.js +20 -1
  216. package/dist/src/workflow/parallelism-executor.d.ts +25 -0
  217. package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
  218. package/dist/src/workflow/parallelism-executor.js +53 -0
  219. package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
  220. package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
  221. package/dist/src/workflow/parallelism-executor.test.js +191 -0
  222. package/dist/src/workflow/parallelism-types.d.ts +80 -0
  223. package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
  224. package/dist/src/workflow/parallelism-types.js +8 -0
  225. package/dist/src/workflow/phase-context-injector.d.ts +29 -0
  226. package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
  227. package/dist/src/workflow/phase-context-injector.js +43 -0
  228. package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
  229. package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
  230. package/dist/src/workflow/phase-context-injector.test.js +123 -0
  231. package/dist/src/workflow/phase-output-collector.d.ts +39 -0
  232. package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
  233. package/dist/src/workflow/phase-output-collector.js +141 -0
  234. package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
  235. package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
  236. package/dist/src/workflow/phase-output-collector.test.js +179 -0
  237. package/dist/src/workflow/retry-resolver.d.ts +51 -0
  238. package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
  239. package/dist/src/workflow/retry-resolver.js +70 -0
  240. package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
  241. package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
  242. package/dist/src/workflow/retry-resolver.test.js +149 -0
  243. package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
  244. package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
  245. package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
  246. package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
  247. package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
  248. package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
  249. package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
  250. package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
  251. package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
  252. package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
  253. package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
  254. package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
  255. package/dist/src/workflow/strategies/index.d.ts +4 -0
  256. package/dist/src/workflow/strategies/index.d.ts.map +1 -0
  257. package/dist/src/workflow/strategies/index.js +3 -0
  258. package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
  259. package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
  260. package/dist/src/workflow/strategies/race-strategy.js +92 -0
  261. package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
  262. package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
  263. package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
  264. package/dist/src/workflow/transition-engine.d.ts +3 -1
  265. package/dist/src/workflow/transition-engine.d.ts.map +1 -1
  266. package/dist/src/workflow/transition-engine.js +26 -7
  267. package/dist/src/workflow/transition-engine.test.js +215 -11
  268. package/dist/src/workflow/workflow-registry.d.ts +46 -1
  269. package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
  270. package/dist/src/workflow/workflow-registry.js +74 -0
  271. package/dist/src/workflow/workflow-registry.test.js +54 -0
  272. package/dist/src/workflow/workflow-types.d.ts +330 -12
  273. package/dist/src/workflow/workflow-types.d.ts.map +1 -1
  274. package/dist/src/workflow/workflow-types.js +100 -5
  275. package/dist/src/workflow/workflow-types.test.js +293 -2
  276. package/package.json +2 -2
@@ -25,6 +25,8 @@ function makeWorkflow(overrides) {
25
25
  { name: 'qa', template: 'qa' },
26
26
  { name: 'acceptance', template: 'acceptance' },
27
27
  { name: 'refinement', template: 'refinement' },
28
+ { name: 'research', template: 'research' },
29
+ { name: 'backlog-creation', template: 'backlog-creation' },
28
30
  ],
29
31
  transitions: [
30
32
  { from: 'Backlog', to: 'development' },
@@ -171,9 +173,9 @@ describe('evaluateTransitions', () => {
171
173
  expect(result.action).toBe('trigger-development');
172
174
  });
173
175
  });
174
- // --- Conditional transitions (Phase 2 behavior: skip conditions) ---
175
- describe('conditional transitions (Phase 2: conditions skipped)', () => {
176
- it('skips transitions with conditions in Phase 2', () => {
176
+ // --- Conditional transitions (conditions now evaluated) ---
177
+ describe('conditional transitions', () => {
178
+ it('evaluates condition and selects matching conditional transition', () => {
177
179
  const workflow = makeWorkflow({
178
180
  transitions: [
179
181
  { from: 'Backlog', to: 'qa', condition: '{{ isParentIssue }}', priority: 10 },
@@ -183,12 +185,29 @@ describe('evaluateTransitions', () => {
183
185
  const ctx = makeContext({
184
186
  issue: makeIssue({ status: 'Backlog' }),
185
187
  registry: registryWith(workflow),
188
+ isParentIssue: true,
186
189
  });
187
190
  const result = evaluateTransitions(ctx);
188
- // Conditional transition skipped, falls through to unconditional
191
+ // isParentIssue is true, so conditional transition matches
192
+ expect(result.action).toBe('trigger-qa');
193
+ });
194
+ it('skips conditional transition when condition is false and falls through to unconditional', () => {
195
+ const workflow = makeWorkflow({
196
+ transitions: [
197
+ { from: 'Backlog', to: 'qa', condition: '{{ isParentIssue }}', priority: 10 },
198
+ { from: 'Backlog', to: 'development' },
199
+ ],
200
+ });
201
+ const ctx = makeContext({
202
+ issue: makeIssue({ status: 'Backlog' }),
203
+ registry: registryWith(workflow),
204
+ isParentIssue: false,
205
+ });
206
+ const result = evaluateTransitions(ctx);
207
+ // isParentIssue is false, so conditional transition skipped, falls through to unconditional
189
208
  expect(result.action).toBe('trigger-development');
190
209
  });
191
- it('returns none when all transitions have conditions', () => {
210
+ it('selects first true condition when all transitions are conditional', () => {
192
211
  const workflow = makeWorkflow({
193
212
  transitions: [
194
213
  { from: 'Backlog', to: 'development', condition: '{{ true }}' },
@@ -200,9 +219,83 @@ describe('evaluateTransitions', () => {
200
219
  registry: registryWith(workflow),
201
220
  });
202
221
  const result = evaluateTransitions(ctx);
222
+ expect(result.action).toBe('trigger-development');
223
+ });
224
+ it('returns none when all conditional transitions evaluate to false', () => {
225
+ const workflow = makeWorkflow({
226
+ transitions: [
227
+ { from: 'Backlog', to: 'development', condition: '{{ false }}' },
228
+ { from: 'Backlog', to: 'qa', condition: '{{ false }}' },
229
+ ],
230
+ });
231
+ const ctx = makeContext({
232
+ issue: makeIssue({ status: 'Backlog' }),
233
+ registry: registryWith(workflow),
234
+ });
235
+ const result = evaluateTransitions(ctx);
203
236
  expect(result.action).toBe('none');
204
- expect(result.reason).toContain('conditions');
205
- expect(result.reason).toContain('Phase 3');
237
+ expect(result.reason).toContain('No transition conditions satisfied');
238
+ });
239
+ it('falls through conditional to next conditional when first is false', () => {
240
+ const workflow = makeWorkflow({
241
+ transitions: [
242
+ { from: 'Backlog', to: 'development', condition: '{{ false }}', priority: 10 },
243
+ { from: 'Backlog', to: 'qa', condition: '{{ true }}', priority: 5 },
244
+ ],
245
+ });
246
+ const ctx = makeContext({
247
+ issue: makeIssue({ status: 'Backlog' }),
248
+ registry: registryWith(workflow),
249
+ });
250
+ const result = evaluateTransitions(ctx);
251
+ // First condition false, second condition true
252
+ expect(result.action).toBe('trigger-qa');
253
+ });
254
+ it('mix of conditional and unconditional transitions — conditional wins when true', () => {
255
+ const workflow = makeWorkflow({
256
+ transitions: [
257
+ { from: 'Backlog', to: 'qa', condition: '{{ true }}', priority: 10 },
258
+ { from: 'Backlog', to: 'development', priority: 5 },
259
+ ],
260
+ });
261
+ const ctx = makeContext({
262
+ issue: makeIssue({ status: 'Backlog' }),
263
+ registry: registryWith(workflow),
264
+ });
265
+ const result = evaluateTransitions(ctx);
266
+ expect(result.action).toBe('trigger-qa');
267
+ });
268
+ it('phaseState variables affect condition evaluation', () => {
269
+ const workflow = makeWorkflow({
270
+ transitions: [
271
+ { from: 'Icebox', to: 'research', condition: '{{ not researchCompleted }}', priority: 10 },
272
+ { from: 'Icebox', to: 'backlog-creation', condition: '{{ researchCompleted and not backlogCreationCompleted }}', priority: 5 },
273
+ ],
274
+ });
275
+ // No research done yet
276
+ const ctx1 = makeContext({
277
+ issue: makeIssue({ status: 'Icebox' }),
278
+ registry: registryWith(workflow),
279
+ phaseState: { researchCompleted: false, backlogCreationCompleted: false },
280
+ });
281
+ const result1 = evaluateTransitions(ctx1);
282
+ expect(result1.action).toBe('trigger-research');
283
+ // Research done, backlog not done
284
+ const ctx2 = makeContext({
285
+ issue: makeIssue({ status: 'Icebox' }),
286
+ registry: registryWith(workflow),
287
+ phaseState: { researchCompleted: true, backlogCreationCompleted: false },
288
+ });
289
+ const result2 = evaluateTransitions(ctx2);
290
+ expect(result2.action).toBe('trigger-backlog-creation');
291
+ // Both done
292
+ const ctx3 = makeContext({
293
+ issue: makeIssue({ status: 'Icebox' }),
294
+ registry: registryWith(workflow),
295
+ phaseState: { researchCompleted: true, backlogCreationCompleted: true },
296
+ });
297
+ const result3 = evaluateTransitions(ctx3);
298
+ expect(result3.action).toBe('none');
206
299
  });
207
300
  });
208
301
  // --- Parent issue annotation ---
@@ -226,6 +319,98 @@ describe('evaluateTransitions', () => {
226
319
  expect(result.reason).not.toContain('coordination template');
227
320
  });
228
321
  });
322
+ // --- Parallelism group detection ---
323
+ describe('parallelism group detection', () => {
324
+ it('parent issue with phase in parallelism group returns trigger-parallel-group', () => {
325
+ const workflow = makeWorkflow({
326
+ parallelism: [
327
+ {
328
+ name: 'dev-parallel',
329
+ phases: ['development'],
330
+ strategy: 'fan-out',
331
+ },
332
+ ],
333
+ });
334
+ const ctx = makeContext({
335
+ issue: makeIssue({ status: 'Backlog' }),
336
+ registry: registryWith(workflow),
337
+ isParentIssue: true,
338
+ });
339
+ const result = evaluateTransitions(ctx);
340
+ expect(result.action).toBe('trigger-parallel-group');
341
+ expect(result.reason).toContain('parallel group');
342
+ expect(result.reason).toContain('dev-parallel');
343
+ expect(result.reason).toContain('fan-out');
344
+ });
345
+ it('non-parent issue with phase in parallelism group returns normal action', () => {
346
+ const workflow = makeWorkflow({
347
+ parallelism: [
348
+ {
349
+ name: 'dev-parallel',
350
+ phases: ['development'],
351
+ strategy: 'fan-out',
352
+ },
353
+ ],
354
+ });
355
+ const ctx = makeContext({
356
+ issue: makeIssue({ status: 'Backlog' }),
357
+ registry: registryWith(workflow),
358
+ isParentIssue: false,
359
+ });
360
+ const result = evaluateTransitions(ctx);
361
+ expect(result.action).toBe('trigger-development');
362
+ expect(result.reason).not.toContain('parallel group');
363
+ });
364
+ it('issue with no parallelism groups returns normal action', () => {
365
+ const ctx = makeContext({
366
+ issue: makeIssue({ status: 'Backlog' }),
367
+ isParentIssue: true,
368
+ });
369
+ const result = evaluateTransitions(ctx);
370
+ expect(result.action).toBe('trigger-development');
371
+ expect(result.reason).not.toContain('parallel group');
372
+ });
373
+ it('parent issue with phase NOT in any parallelism group returns normal action', () => {
374
+ const workflow = makeWorkflow({
375
+ parallelism: [
376
+ {
377
+ name: 'qa-parallel',
378
+ phases: ['qa'],
379
+ strategy: 'fan-in',
380
+ },
381
+ ],
382
+ });
383
+ const ctx = makeContext({
384
+ issue: makeIssue({ status: 'Backlog' }),
385
+ registry: registryWith(workflow),
386
+ isParentIssue: true,
387
+ });
388
+ const result = evaluateTransitions(ctx);
389
+ // 'development' is not in the parallelism group, so normal action
390
+ expect(result.action).toBe('trigger-development');
391
+ expect(result.reason).toContain('coordination template');
392
+ });
393
+ it('escalation strategy overrides parallelism group detection', () => {
394
+ const workflow = makeWorkflow({
395
+ parallelism: [
396
+ {
397
+ name: 'dev-parallel',
398
+ phases: ['development'],
399
+ strategy: 'fan-out',
400
+ },
401
+ ],
402
+ });
403
+ const ctx = makeContext({
404
+ issue: makeIssue({ status: 'Backlog' }),
405
+ registry: registryWith(workflow),
406
+ isParentIssue: true,
407
+ workflowStrategy: 'escalate-human',
408
+ });
409
+ const result = evaluateTransitions(ctx);
410
+ // Escalation takes priority over parallelism
411
+ expect(result.action).toBe('escalate-human');
412
+ });
413
+ });
229
414
  // --- Phase-to-action mapping ---
230
415
  describe('phase-to-action mapping', () => {
231
416
  it('returns none for unknown phase name', () => {
@@ -299,15 +484,34 @@ describe('evaluateTransitions', () => {
299
484
  });
300
485
  expect(result.action).toBe('decompose');
301
486
  });
302
- it('Icebox has only conditional transitions returns none in Phase 2', () => {
487
+ it('Icebox with phaseState routes to research when researchCompleted is false', () => {
303
488
  const result = evaluateTransitions({
304
489
  issue: makeIssue({ status: 'Icebox' }),
305
490
  registry: builtinRegistry,
306
491
  isParentIssue: false,
492
+ phaseState: { researchCompleted: false, backlogCreationCompleted: false },
307
493
  });
308
- // Icebox transitions all have conditions, which are skipped in Phase 2.
309
- // This is expected — Icebox routing is handled by decideIcebox() directly.
310
- expect(result.action).toBe('none');
494
+ // researchCompleted is false, so "not researchCompleted" is true research phase
495
+ expect(result.action).toBe('trigger-research');
496
+ });
497
+ it('Icebox with phaseState routes to backlog-creation when research done', () => {
498
+ const result = evaluateTransitions({
499
+ issue: makeIssue({ status: 'Icebox' }),
500
+ registry: builtinRegistry,
501
+ isParentIssue: false,
502
+ phaseState: { researchCompleted: true, backlogCreationCompleted: false },
503
+ });
504
+ // researchCompleted is true and backlogCreationCompleted is false → backlog-creation
505
+ expect(result.action).toBe('trigger-backlog-creation');
506
+ });
507
+ it('Icebox with no phaseState routes to research (undefined vars are falsy)', () => {
508
+ const result = evaluateTransitions({
509
+ issue: makeIssue({ status: 'Icebox' }),
510
+ registry: builtinRegistry,
511
+ isParentIssue: false,
512
+ });
513
+ // No phaseState → researchCompleted is undefined → falsy → "not researchCompleted" is true
514
+ expect(result.action).toBe('trigger-research');
311
515
  });
312
516
  });
313
517
  });
@@ -9,7 +9,19 @@
9
9
  * 2. Project-level override (e.g., .agentfactory/workflow.yaml)
10
10
  * 3. Inline config override (programmatic)
11
11
  */
12
- import type { WorkflowDefinition, EscalationConfig } from './workflow-types.js';
12
+ import type { WorkflowDefinition, EscalationConfig, ParallelismGroupDefinition } from './workflow-types.js';
13
+ /**
14
+ * Interface for an external workflow store (e.g., Redis-backed).
15
+ * WorkflowRegistry can load definitions from this store as an additional layer.
16
+ */
17
+ export interface WorkflowStoreSource {
18
+ get(workflowId: string): Promise<{
19
+ definition: Record<string, unknown>;
20
+ } | null>;
21
+ list(): Promise<Array<{
22
+ id: string;
23
+ }>>;
24
+ }
13
25
  export interface WorkflowRegistryConfig {
14
26
  /** Path to a project-level workflow definition YAML override */
15
27
  workflowPath?: string;
@@ -17,19 +29,48 @@ export interface WorkflowRegistryConfig {
17
29
  workflow?: WorkflowDefinition;
18
30
  /** Whether to load the built-in default workflow (default: true) */
19
31
  useBuiltinDefault?: boolean;
32
+ /** External store source (e.g., Redis). Loaded between filesystem and inline layers. */
33
+ store?: WorkflowStoreSource;
34
+ /** Workflow ID to load from the store (default: 'default') */
35
+ storeWorkflowId?: string;
20
36
  }
21
37
  export declare class WorkflowRegistry {
22
38
  private workflow;
39
+ private _onReload?;
23
40
  constructor();
24
41
  /**
25
42
  * Create and initialize a registry from configuration.
43
+ * For synchronous initialization (no store). Use createAsync() when a store is configured.
26
44
  */
27
45
  static create(config?: WorkflowRegistryConfig): WorkflowRegistry;
46
+ /**
47
+ * Create and initialize a registry with async store support.
48
+ */
49
+ static createAsync(config?: WorkflowRegistryConfig): Promise<WorkflowRegistry>;
28
50
  /**
29
51
  * Initialize the registry by loading workflow definition from
30
52
  * configured sources. Later sources override earlier ones.
53
+ * Note: This method does NOT load from the store (use initializeAsync for that).
31
54
  */
32
55
  initialize(config?: WorkflowRegistryConfig): void;
56
+ /**
57
+ * Initialize with async store loading.
58
+ * Resolution order (later sources override earlier):
59
+ * 1. Built-in default (workflow/defaults/workflow.yaml)
60
+ * 2. Project-level override (e.g., .agentfactory/workflow.yaml)
61
+ * 3. Store override (Redis-backed, takes precedence over filesystem)
62
+ * 4. Inline config override (programmatic, highest priority)
63
+ */
64
+ initializeAsync(config?: WorkflowRegistryConfig): Promise<void>;
65
+ /**
66
+ * Replace the current workflow definition (used by hot-reload).
67
+ * Notifies the onReload callback if registered.
68
+ */
69
+ setWorkflow(workflow: WorkflowDefinition): void;
70
+ /**
71
+ * Register a callback to be invoked when the workflow is hot-reloaded.
72
+ */
73
+ onReload(callback: (workflow: WorkflowDefinition) => void): void;
33
74
  /**
34
75
  * Get the currently loaded workflow definition.
35
76
  */
@@ -45,6 +86,10 @@ export declare class WorkflowRegistry {
45
86
  * Falls back to 'normal' if no match or no escalation config.
46
87
  */
47
88
  getEscalationStrategy(cycleCount: number): string;
89
+ /**
90
+ * Get the parallelism group that contains the given phase, if any.
91
+ */
92
+ getParallelismGroup(phaseName: string): ParallelismGroupDefinition | undefined;
48
93
  /**
49
94
  * Get circuit breaker limits from the workflow definition.
50
95
  */
@@ -1 +1 @@
1
- {"version":3,"file":"workflow-registry.d.ts","sourceRoot":"","sources":["../../../src/workflow/workflow-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAO/E,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAcD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAkC;;IAIlD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;;OAGG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IAsBrD;;OAEG;IACH,WAAW,IAAI,kBAAkB,GAAG,IAAI;IAIxC;;OAEG;IACH,aAAa,IAAI,gBAAgB,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAcjD;;OAEG;IACH,uBAAuB,IAAI;QAAE,mBAAmB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;CAOxF"}
1
+ {"version":3,"file":"workflow-registry.d.ts","sourceRoot":"","sources":["../../../src/workflow/workflow-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAA;AAO3G;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAChF,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;CACvC;AAED,MAAM,WAAW,sBAAsB;IACrC,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IAC7B,oEAAoE;IACpE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,wFAAwF;IACxF,KAAK,CAAC,EAAE,mBAAmB,CAAA;IAC3B,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAcD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,SAAS,CAAC,CAAwC;;IAI1D;;;OAGG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;OAEG;WACU,WAAW,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAMxF;;;;OAIG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IAsBrD;;;;;;;OAOG;IACG,eAAe,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAqCzE;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI;IAK/C;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,GAAG,IAAI;IAIhE;;OAEG;IACH,WAAW,IAAI,kBAAkB,GAAG,IAAI;IAIxC;;OAEG;IACH,aAAa,IAAI,gBAAgB,GAAG,IAAI;IAIxC;;;;;OAKG;IACH,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAcjD;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,0BAA0B,GAAG,SAAS;IAK9E;;OAEG;IACH,uBAAuB,IAAI;QAAE,mBAAmB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,MAAM,CAAA;KAAE;CAOxF"}
@@ -22,18 +22,29 @@ const DEFAULT_MAX_SESSIONS_PER_PHASE = 3;
22
22
  // ---------------------------------------------------------------------------
23
23
  export class WorkflowRegistry {
24
24
  workflow = null;
25
+ _onReload;
25
26
  constructor() { }
26
27
  /**
27
28
  * Create and initialize a registry from configuration.
29
+ * For synchronous initialization (no store). Use createAsync() when a store is configured.
28
30
  */
29
31
  static create(config = {}) {
30
32
  const registry = new WorkflowRegistry();
31
33
  registry.initialize(config);
32
34
  return registry;
33
35
  }
36
+ /**
37
+ * Create and initialize a registry with async store support.
38
+ */
39
+ static async createAsync(config = {}) {
40
+ const registry = new WorkflowRegistry();
41
+ await registry.initializeAsync(config);
42
+ return registry;
43
+ }
34
44
  /**
35
45
  * Initialize the registry by loading workflow definition from
36
46
  * configured sources. Later sources override earlier ones.
47
+ * Note: This method does NOT load from the store (use initializeAsync for that).
37
48
  */
38
49
  initialize(config = {}) {
39
50
  const { workflowPath, workflow, useBuiltinDefault = true } = config;
@@ -53,6 +64,61 @@ export class WorkflowRegistry {
53
64
  this.workflow = workflow;
54
65
  }
55
66
  }
67
+ /**
68
+ * Initialize with async store loading.
69
+ * Resolution order (later sources override earlier):
70
+ * 1. Built-in default (workflow/defaults/workflow.yaml)
71
+ * 2. Project-level override (e.g., .agentfactory/workflow.yaml)
72
+ * 3. Store override (Redis-backed, takes precedence over filesystem)
73
+ * 4. Inline config override (programmatic, highest priority)
74
+ */
75
+ async initializeAsync(config = {}) {
76
+ const { workflowPath, workflow, useBuiltinDefault = true, store, storeWorkflowId } = config;
77
+ // Layer 1: Built-in default
78
+ if (useBuiltinDefault) {
79
+ const builtinPath = getBuiltinWorkflowPath();
80
+ if (fs.existsSync(builtinPath)) {
81
+ this.workflow = loadWorkflowDefinitionFile(builtinPath);
82
+ }
83
+ }
84
+ // Layer 2: Project-level override
85
+ if (workflowPath && fs.existsSync(workflowPath)) {
86
+ this.workflow = loadWorkflowDefinitionFile(workflowPath);
87
+ }
88
+ // Layer 3: Store override (Redis-backed)
89
+ if (store) {
90
+ try {
91
+ const id = storeWorkflowId ?? 'default';
92
+ const stored = await store.get(id);
93
+ if (stored) {
94
+ const { validateWorkflowDefinition } = await import('./workflow-types.js');
95
+ this.workflow = validateWorkflowDefinition(stored.definition);
96
+ }
97
+ }
98
+ catch (err) {
99
+ // Log but don't fail — fall back to filesystem layers
100
+ console.warn('[WorkflowRegistry] Failed to load from store, using filesystem layers:', err);
101
+ }
102
+ }
103
+ // Layer 4: Inline override (highest priority)
104
+ if (workflow) {
105
+ this.workflow = workflow;
106
+ }
107
+ }
108
+ /**
109
+ * Replace the current workflow definition (used by hot-reload).
110
+ * Notifies the onReload callback if registered.
111
+ */
112
+ setWorkflow(workflow) {
113
+ this.workflow = workflow;
114
+ this._onReload?.(workflow);
115
+ }
116
+ /**
117
+ * Register a callback to be invoked when the workflow is hot-reloaded.
118
+ */
119
+ onReload(callback) {
120
+ this._onReload = callback;
121
+ }
56
122
  /**
57
123
  * Get the currently loaded workflow definition.
58
124
  */
@@ -82,6 +148,14 @@ export class WorkflowRegistry {
82
148
  const match = sorted.find(rung => cycleCount >= rung.cycle);
83
149
  return match?.strategy ?? 'normal';
84
150
  }
151
+ /**
152
+ * Get the parallelism group that contains the given phase, if any.
153
+ */
154
+ getParallelismGroup(phaseName) {
155
+ if (!this.workflow?.parallelism)
156
+ return undefined;
157
+ return this.workflow.parallelism.find(g => g.phases.includes(phaseName));
158
+ }
85
159
  /**
86
160
  * Get circuit breaker limits from the workflow definition.
87
161
  */
@@ -185,6 +185,60 @@ describe('WorkflowRegistry', () => {
185
185
  expect(limits.maxSessionsPerPhase).toBe(3);
186
186
  });
187
187
  });
188
+ describe('getParallelismGroup()', () => {
189
+ it('returns the group containing the given phase', () => {
190
+ const custom = makeWorkflow({
191
+ parallelism: [
192
+ {
193
+ name: 'dev-parallel',
194
+ phases: ['development'],
195
+ strategy: 'fan-out',
196
+ },
197
+ {
198
+ name: 'qa-parallel',
199
+ phases: ['qa'],
200
+ strategy: 'fan-in',
201
+ },
202
+ ],
203
+ });
204
+ const registry = WorkflowRegistry.create({ workflow: custom });
205
+ const group = registry.getParallelismGroup('development');
206
+ expect(group).toBeDefined();
207
+ expect(group.name).toBe('dev-parallel');
208
+ expect(group.strategy).toBe('fan-out');
209
+ });
210
+ it('returns undefined for phase not in any group', () => {
211
+ const custom = makeWorkflow({
212
+ parallelism: [
213
+ {
214
+ name: 'dev-parallel',
215
+ phases: ['development'],
216
+ strategy: 'fan-out',
217
+ },
218
+ ],
219
+ });
220
+ const registry = WorkflowRegistry.create({ workflow: custom });
221
+ expect(registry.getParallelismGroup('qa')).toBeUndefined();
222
+ });
223
+ it('returns undefined when no parallelism defined', () => {
224
+ const custom = makeWorkflow(); // No parallelism
225
+ const registry = WorkflowRegistry.create({ workflow: custom });
226
+ expect(registry.getParallelismGroup('development')).toBeUndefined();
227
+ });
228
+ it('returns undefined for a completely unknown phase name', () => {
229
+ const custom = makeWorkflow({
230
+ parallelism: [
231
+ {
232
+ name: 'dev-parallel',
233
+ phases: ['development'],
234
+ strategy: 'fan-out',
235
+ },
236
+ ],
237
+ });
238
+ const registry = WorkflowRegistry.create({ workflow: custom });
239
+ expect(registry.getParallelismGroup('nonexistent-phase')).toBeUndefined();
240
+ });
241
+ });
188
242
  describe('getEscalation()', () => {
189
243
  it('returns escalation config from workflow', () => {
190
244
  const registry = WorkflowRegistry.create();