@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.
- package/dist/src/config/index.d.ts +1 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/repository-config.d.ts +37 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +47 -0
- package/dist/src/config/repository-config.test.js +140 -1
- package/dist/src/governor/decision-engine.d.ts +3 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +11 -0
- package/dist/src/governor/decision-engine.test.js +33 -0
- package/dist/src/governor/event-types.d.ts +18 -1
- package/dist/src/governor/event-types.d.ts.map +1 -1
- package/dist/src/governor/event-types.js +4 -0
- package/dist/src/governor/governor-types.d.ts +1 -1
- package/dist/src/governor/governor-types.d.ts.map +1 -1
- package/dist/src/governor/governor.d.ts +17 -1
- package/dist/src/governor/governor.d.ts.map +1 -1
- package/dist/src/governor/governor.js +112 -1
- package/dist/src/governor/governor.test.js +155 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.js +243 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
- package/dist/src/merge-queue/index.d.ts +18 -0
- package/dist/src/merge-queue/index.d.ts.map +1 -0
- package/dist/src/merge-queue/index.js +28 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
- package/dist/src/merge-queue/types.d.ts +48 -0
- package/dist/src/merge-queue/types.d.ts.map +1 -0
- package/dist/src/merge-queue/types.js +8 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.js +235 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
- package/dist/src/orchestrator/context-manager.d.ts +72 -0
- package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.js +120 -0
- package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
- package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.test.js +137 -0
- package/dist/src/orchestrator/index.d.ts +8 -2
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +8 -1
- package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.d.ts +12 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +282 -2
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +6 -0
- package/dist/src/orchestrator/parse-work-result.test.js +19 -0
- package/dist/src/orchestrator/state-recovery.d.ts +21 -2
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.js +54 -2
- package/dist/src/orchestrator/state-recovery.test.js +106 -2
- package/dist/src/orchestrator/state-types.d.ts +62 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/orchestrator/state-types.js +5 -1
- package/dist/src/orchestrator/summary-builder.d.ts +47 -0
- package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.js +240 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.test.js +236 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/orchestrator/work-types.d.ts +1 -1
- package/dist/src/orchestrator/work-types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +64 -1
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +132 -1
- package/dist/src/providers/index.test.js +340 -2
- package/dist/src/routing/index.d.ts +7 -0
- package/dist/src/routing/index.d.ts.map +1 -0
- package/dist/src/routing/index.js +6 -0
- package/dist/src/routing/observation-recorder.d.ts +19 -0
- package/dist/src/routing/observation-recorder.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.js +73 -0
- package/dist/src/routing/observation-recorder.test.d.ts +2 -0
- package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.test.js +322 -0
- package/dist/src/routing/observation-store.d.ts +40 -0
- package/dist/src/routing/observation-store.d.ts.map +1 -0
- package/dist/src/routing/observation-store.js +1 -0
- package/dist/src/routing/observation-store.test.d.ts +2 -0
- package/dist/src/routing/observation-store.test.d.ts.map +1 -0
- package/dist/src/routing/observation-store.test.js +138 -0
- package/dist/src/routing/posterior-store.d.ts +12 -0
- package/dist/src/routing/posterior-store.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.js +13 -0
- package/dist/src/routing/posterior-store.test.d.ts +2 -0
- package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.test.js +37 -0
- package/dist/src/routing/reward.d.ts +16 -0
- package/dist/src/routing/reward.d.ts.map +1 -0
- package/dist/src/routing/reward.js +29 -0
- package/dist/src/routing/reward.test.d.ts +2 -0
- package/dist/src/routing/reward.test.d.ts.map +1 -0
- package/dist/src/routing/reward.test.js +210 -0
- package/dist/src/routing/routing-engine.d.ts +20 -0
- package/dist/src/routing/routing-engine.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.js +113 -0
- package/dist/src/routing/routing-engine.test.d.ts +2 -0
- package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.test.js +310 -0
- package/dist/src/routing/types.d.ts +157 -0
- package/dist/src/routing/types.d.ts.map +1 -0
- package/dist/src/routing/types.js +68 -0
- package/dist/src/routing/types.test.d.ts +2 -0
- package/dist/src/routing/types.test.d.ts.map +1 -0
- package/dist/src/routing/types.test.js +184 -0
- package/dist/src/templates/registry.test.js +2 -2
- package/dist/src/templates/types.d.ts +5 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +3 -0
- package/dist/src/workflow/agent-cancellation.d.ts +37 -0
- package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.js +41 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.test.js +86 -0
- package/dist/src/workflow/branching-router.d.ts +38 -0
- package/dist/src/workflow/branching-router.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.js +52 -0
- package/dist/src/workflow/branching-router.test.d.ts +2 -0
- package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.test.js +209 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.js +46 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
- package/dist/src/workflow/duration.d.ts +28 -0
- package/dist/src/workflow/duration.d.ts.map +1 -0
- package/dist/src/workflow/duration.js +57 -0
- package/dist/src/workflow/duration.test.d.ts +2 -0
- package/dist/src/workflow/duration.test.d.ts.map +1 -0
- package/dist/src/workflow/duration.test.js +74 -0
- package/dist/src/workflow/expression/ast.d.ts +53 -0
- package/dist/src/workflow/expression/ast.d.ts.map +1 -0
- package/dist/src/workflow/expression/ast.js +8 -0
- package/dist/src/workflow/expression/context.d.ts +40 -0
- package/dist/src/workflow/expression/context.d.ts.map +1 -0
- package/dist/src/workflow/expression/context.js +37 -0
- package/dist/src/workflow/expression/evaluator.d.ts +28 -0
- package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.js +165 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.test.js +792 -0
- package/dist/src/workflow/expression/expression.test.d.ts +2 -0
- package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/expression.test.js +516 -0
- package/dist/src/workflow/expression/helpers.d.ts +21 -0
- package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
- package/dist/src/workflow/expression/helpers.js +56 -0
- package/dist/src/workflow/expression/index.d.ts +55 -0
- package/dist/src/workflow/expression/index.d.ts.map +1 -0
- package/dist/src/workflow/expression/index.js +71 -0
- package/dist/src/workflow/expression/lexer.d.ts +37 -0
- package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
- package/dist/src/workflow/expression/lexer.js +166 -0
- package/dist/src/workflow/expression/parser.d.ts +23 -0
- package/dist/src/workflow/expression/parser.d.ts.map +1 -0
- package/dist/src/workflow/expression/parser.js +181 -0
- package/dist/src/workflow/gate-state.d.ts +115 -0
- package/dist/src/workflow/gate-state.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.js +185 -0
- package/dist/src/workflow/gate-state.test.d.ts +2 -0
- package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.test.js +251 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.js +243 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
- package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
- package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.js +216 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.test.js +199 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.js +162 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
- package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
- package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.js +381 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.test.js +211 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.js +216 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
- package/dist/src/workflow/index.d.ts +31 -3
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +20 -1
- package/dist/src/workflow/parallelism-executor.d.ts +25 -0
- package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.js +53 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.test.js +191 -0
- package/dist/src/workflow/parallelism-types.d.ts +80 -0
- package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-types.js +8 -0
- package/dist/src/workflow/phase-context-injector.d.ts +29 -0
- package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.js +43 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.test.js +123 -0
- package/dist/src/workflow/phase-output-collector.d.ts +39 -0
- package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.js +141 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.test.js +179 -0
- package/dist/src/workflow/retry-resolver.d.ts +51 -0
- package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.js +70 -0
- package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
- package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.test.js +149 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
- package/dist/src/workflow/strategies/index.d.ts +4 -0
- package/dist/src/workflow/strategies/index.d.ts.map +1 -0
- package/dist/src/workflow/strategies/index.js +3 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.js +92 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
- package/dist/src/workflow/transition-engine.d.ts +3 -1
- package/dist/src/workflow/transition-engine.d.ts.map +1 -1
- package/dist/src/workflow/transition-engine.js +26 -7
- package/dist/src/workflow/transition-engine.test.js +215 -11
- package/dist/src/workflow/workflow-registry.d.ts +46 -1
- package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
- package/dist/src/workflow/workflow-registry.js +74 -0
- package/dist/src/workflow/workflow-registry.test.js +54 -0
- package/dist/src/workflow/workflow-types.d.ts +330 -12
- package/dist/src/workflow/workflow-types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.js +100 -5
- package/dist/src/workflow/workflow-types.test.js +293 -2
- 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 (
|
|
175
|
-
describe('conditional transitions
|
|
176
|
-
it('
|
|
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
|
-
//
|
|
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('
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
309
|
-
|
|
310
|
-
|
|
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;
|
|
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();
|