@renseiai/agentfactory 0.8.18 → 0.8.20
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/repository-config.d.ts +7 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +15 -1
- package/dist/src/config/repository-config.test.js +1 -1
- package/dist/src/governor/decision-engine-adapter.d.ts +43 -0
- package/dist/src/governor/decision-engine-adapter.d.ts.map +1 -0
- package/dist/src/governor/decision-engine-adapter.js +417 -0
- package/dist/src/governor/decision-engine-adapter.test.d.ts +2 -0
- package/dist/src/governor/decision-engine-adapter.test.d.ts.map +1 -0
- package/dist/src/governor/decision-engine-adapter.test.js +362 -0
- package/dist/src/governor/decision-engine.js +3 -7
- package/dist/src/governor/decision-engine.test.js +5 -5
- package/dist/src/governor/index.d.ts +1 -0
- package/dist/src/governor/index.d.ts.map +1 -1
- package/dist/src/governor/index.js +1 -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/manifest/route-manifest.d.ts.map +1 -1
- package/dist/src/manifest/route-manifest.js +4 -0
- package/dist/src/merge-queue/adapters/local.d.ts +68 -0
- package/dist/src/merge-queue/adapters/local.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.js +136 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.test.js +176 -0
- package/dist/src/merge-queue/index.d.ts +13 -5
- package/dist/src/merge-queue/index.d.ts.map +1 -1
- package/dist/src/merge-queue/index.js +13 -6
- package/dist/src/merge-queue/merge-queue.integration.test.js +19 -0
- package/dist/src/merge-queue/merge-worker.d.ts.map +1 -1
- package/dist/src/merge-queue/merge-worker.js +29 -0
- package/dist/src/merge-queue/types.d.ts +1 -1
- package/dist/src/merge-queue/types.d.ts.map +1 -1
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +58 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +552 -97
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +3 -1
- package/dist/src/orchestrator/parse-work-result.test.js +6 -0
- package/dist/src/orchestrator/quality-baseline.d.ts +83 -0
- package/dist/src/orchestrator/quality-baseline.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.js +313 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.test.js +448 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts +70 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.js +162 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.test.js +335 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.d.ts.map +1 -1
- package/dist/src/providers/claude-provider.js +11 -0
- package/dist/src/providers/codex-app-server-provider.d.ts +237 -0
- package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -0
- package/dist/src/providers/codex-app-server-provider.js +1041 -0
- package/dist/src/providers/codex-app-server-provider.test.d.ts +2 -0
- package/dist/src/providers/codex-app-server-provider.test.d.ts.map +1 -0
- package/dist/src/providers/codex-app-server-provider.test.js +589 -0
- package/dist/src/providers/codex-approval-bridge.d.ts +49 -0
- package/dist/src/providers/codex-approval-bridge.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.js +117 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts +2 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.test.js +188 -0
- package/dist/src/providers/codex-provider.d.ts +24 -4
- package/dist/src/providers/codex-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-provider.js +58 -6
- package/dist/src/providers/index.d.ts +1 -0
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +1 -0
- package/dist/src/providers/types.d.ts +25 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/routing/observation-recorder.test.js +1 -1
- package/dist/src/routing/observation-store.d.ts +15 -1
- package/dist/src/routing/observation-store.d.ts.map +1 -1
- package/dist/src/routing/observation-store.test.js +17 -11
- package/dist/src/routing/types.d.ts +1 -1
- package/dist/src/templates/adapters.d.ts +25 -0
- package/dist/src/templates/adapters.d.ts.map +1 -1
- package/dist/src/templates/adapters.js +70 -0
- package/dist/src/templates/adapters.test.js +49 -0
- package/dist/src/templates/index.d.ts +3 -1
- package/dist/src/templates/index.d.ts.map +1 -1
- package/dist/src/templates/index.js +1 -0
- package/dist/src/templates/registry.d.ts +31 -0
- package/dist/src/templates/registry.d.ts.map +1 -1
- package/dist/src/templates/registry.js +91 -0
- package/dist/src/templates/schema.d.ts +31 -0
- package/dist/src/templates/schema.d.ts.map +1 -0
- package/dist/src/templates/schema.js +139 -0
- package/dist/src/templates/schema.test.d.ts +2 -0
- package/dist/src/templates/schema.test.d.ts.map +1 -0
- package/dist/src/templates/schema.test.js +215 -0
- package/dist/src/templates/types.d.ts +22 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +12 -0
- package/dist/src/tools/index.d.ts +2 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +1 -0
- package/dist/src/tools/registry.d.ts +9 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +13 -1
- package/dist/src/tools/stdio-server-entry.d.ts +25 -0
- package/dist/src/tools/stdio-server-entry.d.ts.map +1 -0
- package/dist/src/tools/stdio-server-entry.js +205 -0
- package/dist/src/tools/stdio-server.d.ts +87 -0
- package/dist/src/tools/stdio-server.d.ts.map +1 -0
- package/dist/src/tools/stdio-server.js +138 -0
- package/dist/src/workflow/workflow-types.d.ts +3 -3
- package/package.json +3 -2
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DecisionEngineAdapter } from './decision-engine-adapter.js';
|
|
3
|
+
import { decideAction, MAX_SESSION_ATTEMPTS } from './decision-engine.js';
|
|
4
|
+
import { DEFAULT_GOVERNOR_CONFIG } from './governor-types.js';
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Helpers
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function makeIssue(overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
id: 'issue-1',
|
|
11
|
+
identifier: 'SUP-100',
|
|
12
|
+
title: 'Test Issue',
|
|
13
|
+
description: undefined,
|
|
14
|
+
status: 'Backlog',
|
|
15
|
+
labels: [],
|
|
16
|
+
createdAt: Date.now() - 2 * 60 * 60 * 1000,
|
|
17
|
+
...overrides,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function makeContext(overrides = {}) {
|
|
21
|
+
return {
|
|
22
|
+
issue: makeIssue(),
|
|
23
|
+
config: { ...DEFAULT_GOVERNOR_CONFIG, projects: ['TestProject'] },
|
|
24
|
+
hasActiveSession: false,
|
|
25
|
+
isHeld: false,
|
|
26
|
+
isWithinCooldown: false,
|
|
27
|
+
isParentIssue: false,
|
|
28
|
+
workflowStrategy: undefined,
|
|
29
|
+
researchCompleted: false,
|
|
30
|
+
backlogCreationCompleted: false,
|
|
31
|
+
completedSessionCount: 0,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function findNode(workflow, name) {
|
|
36
|
+
return workflow.nodes?.find(n => n.name === name);
|
|
37
|
+
}
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Basic structure tests
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
describe('DecisionEngineAdapter', () => {
|
|
42
|
+
describe('toWorkflowDefinition', () => {
|
|
43
|
+
it('generates a valid WorkflowDefinition v2', () => {
|
|
44
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
45
|
+
expect(workflow.apiVersion).toBe('v2');
|
|
46
|
+
expect(workflow.kind).toBe('WorkflowDefinition');
|
|
47
|
+
expect(workflow.metadata.name).toBe('governor-decision-engine');
|
|
48
|
+
});
|
|
49
|
+
it('uses custom workflow name when provided', () => {
|
|
50
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition({
|
|
51
|
+
workflowName: 'custom-workflow',
|
|
52
|
+
});
|
|
53
|
+
expect(workflow.metadata.name).toBe('custom-workflow');
|
|
54
|
+
});
|
|
55
|
+
it('includes triggers for Linear events and governor scan', () => {
|
|
56
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
57
|
+
expect(workflow.triggers).toHaveLength(2);
|
|
58
|
+
expect(workflow.triggers?.[0].name).toBe('issue-status-change');
|
|
59
|
+
expect(workflow.triggers?.[0].source).toBe('linear');
|
|
60
|
+
expect(workflow.triggers?.[1].name).toBe('governor-scan');
|
|
61
|
+
expect(workflow.triggers?.[1].type).toBe('schedule');
|
|
62
|
+
});
|
|
63
|
+
it('includes agent and linear providers', () => {
|
|
64
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
65
|
+
expect(workflow.providers).toHaveLength(2);
|
|
66
|
+
const names = workflow.providers?.map(p => p.name);
|
|
67
|
+
expect(names).toContain('linear');
|
|
68
|
+
expect(names).toContain('agent');
|
|
69
|
+
});
|
|
70
|
+
it('stores governor config values in workflow config', () => {
|
|
71
|
+
const config = {
|
|
72
|
+
...DEFAULT_GOVERNOR_CONFIG,
|
|
73
|
+
enableAutoDevelopment: false,
|
|
74
|
+
enableAutoQA: false,
|
|
75
|
+
};
|
|
76
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition({ governorConfig: config });
|
|
77
|
+
expect(workflow.config?.enableAutoDevelopment).toBe(false);
|
|
78
|
+
expect(workflow.config?.enableAutoQA).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Guard nodes
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
describe('guard nodes', () => {
|
|
85
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
86
|
+
it('includes active-session guard', () => {
|
|
87
|
+
const node = findNode(workflow, 'guard-active-session');
|
|
88
|
+
expect(node).toBeDefined();
|
|
89
|
+
expect(node?.when).toContain('hasActiveSession');
|
|
90
|
+
});
|
|
91
|
+
it('includes cooldown guard', () => {
|
|
92
|
+
const node = findNode(workflow, 'guard-cooldown');
|
|
93
|
+
expect(node).toBeDefined();
|
|
94
|
+
expect(node?.when).toContain('isWithinCooldown');
|
|
95
|
+
});
|
|
96
|
+
it('includes hold guard', () => {
|
|
97
|
+
const node = findNode(workflow, 'guard-hold');
|
|
98
|
+
expect(node).toBeDefined();
|
|
99
|
+
expect(node?.when).toContain('isHeld');
|
|
100
|
+
});
|
|
101
|
+
it('includes gate guard', () => {
|
|
102
|
+
const node = findNode(workflow, 'guard-gates');
|
|
103
|
+
expect(node).toBeDefined();
|
|
104
|
+
expect(node?.when).toContain('gateEvaluation');
|
|
105
|
+
});
|
|
106
|
+
it('includes circuit breaker guard', () => {
|
|
107
|
+
const node = findNode(workflow, 'guard-circuit-breaker');
|
|
108
|
+
expect(node).toBeDefined();
|
|
109
|
+
expect(node?.when).toContain(`${MAX_SESSION_ATTEMPTS}`);
|
|
110
|
+
});
|
|
111
|
+
it('includes terminal status guard', () => {
|
|
112
|
+
const node = findNode(workflow, 'guard-terminal-status');
|
|
113
|
+
expect(node).toBeDefined();
|
|
114
|
+
expect(node?.when).toContain('Accepted');
|
|
115
|
+
expect(node?.when).toContain('Canceled');
|
|
116
|
+
expect(node?.when).toContain('Duplicate');
|
|
117
|
+
});
|
|
118
|
+
it('includes sub-issue guard', () => {
|
|
119
|
+
const node = findNode(workflow, 'guard-sub-issue');
|
|
120
|
+
expect(node).toBeDefined();
|
|
121
|
+
expect(node?.when).toContain('parentId');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Routing nodes
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
describe('routing nodes', () => {
|
|
128
|
+
it('includes Backlog routing node', () => {
|
|
129
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
130
|
+
const node = findNode(workflow, 'route-backlog');
|
|
131
|
+
expect(node).toBeDefined();
|
|
132
|
+
expect(node?.when).toContain('Backlog');
|
|
133
|
+
const actions = node?.steps?.map(s => s.action);
|
|
134
|
+
expect(actions).toContain('trigger-development');
|
|
135
|
+
});
|
|
136
|
+
it('includes Started routing node', () => {
|
|
137
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
138
|
+
const node = findNode(workflow, 'route-started');
|
|
139
|
+
expect(node).toBeDefined();
|
|
140
|
+
expect(node?.when).toContain('Started');
|
|
141
|
+
});
|
|
142
|
+
it('includes Finished routing node with QA', () => {
|
|
143
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
144
|
+
const node = findNode(workflow, 'route-finished');
|
|
145
|
+
expect(node).toBeDefined();
|
|
146
|
+
expect(node?.when).toContain('Finished');
|
|
147
|
+
const actions = node?.steps?.map(s => s.action);
|
|
148
|
+
expect(actions).toContain('trigger-qa');
|
|
149
|
+
expect(actions).toContain('escalate-human');
|
|
150
|
+
expect(actions).toContain('decompose');
|
|
151
|
+
});
|
|
152
|
+
it('never includes merge queue step in Finished (merge queue does not bypass QA)', () => {
|
|
153
|
+
const workflowEnabled = DecisionEngineAdapter.toWorkflowDefinition({
|
|
154
|
+
includeMergeQueue: true,
|
|
155
|
+
});
|
|
156
|
+
const nodeEnabled = findNode(workflowEnabled, 'route-finished');
|
|
157
|
+
const actionsEnabled = nodeEnabled?.steps?.map(s => s.action);
|
|
158
|
+
expect(actionsEnabled).not.toContain('trigger-merge');
|
|
159
|
+
const workflowDisabled = DecisionEngineAdapter.toWorkflowDefinition({
|
|
160
|
+
includeMergeQueue: false,
|
|
161
|
+
});
|
|
162
|
+
const nodeDisabled = findNode(workflowDisabled, 'route-finished');
|
|
163
|
+
const actionsDisabled = nodeDisabled?.steps?.map(s => s.action);
|
|
164
|
+
expect(actionsDisabled).not.toContain('trigger-merge');
|
|
165
|
+
});
|
|
166
|
+
it('includes Delivered routing node', () => {
|
|
167
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
168
|
+
const node = findNode(workflow, 'route-delivered');
|
|
169
|
+
expect(node).toBeDefined();
|
|
170
|
+
expect(node?.when).toContain('Delivered');
|
|
171
|
+
const actions = node?.steps?.map(s => s.action);
|
|
172
|
+
expect(actions).toContain('trigger-acceptance');
|
|
173
|
+
});
|
|
174
|
+
it('includes Rejected routing node with escalation', () => {
|
|
175
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
176
|
+
const node = findNode(workflow, 'route-rejected');
|
|
177
|
+
expect(node).toBeDefined();
|
|
178
|
+
expect(node?.when).toContain('Rejected');
|
|
179
|
+
const actions = node?.steps?.map(s => s.action);
|
|
180
|
+
expect(actions).toContain('trigger-refinement');
|
|
181
|
+
expect(actions).toContain('escalate-human');
|
|
182
|
+
expect(actions).toContain('decompose');
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Top-of-funnel (Icebox) nodes
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
describe('top-of-funnel nodes', () => {
|
|
189
|
+
it('includes Icebox research node by default', () => {
|
|
190
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
191
|
+
const node = findNode(workflow, 'icebox-research');
|
|
192
|
+
expect(node).toBeDefined();
|
|
193
|
+
expect(node?.when).toContain('Icebox');
|
|
194
|
+
expect(node?.when).toContain('enableAutoResearch');
|
|
195
|
+
});
|
|
196
|
+
it('includes Icebox backlog-creation node by default', () => {
|
|
197
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition();
|
|
198
|
+
const node = findNode(workflow, 'icebox-backlog-creation');
|
|
199
|
+
expect(node).toBeDefined();
|
|
200
|
+
expect(node?.when).toContain('Icebox');
|
|
201
|
+
expect(node?.when).toContain('enableAutoBacklogCreation');
|
|
202
|
+
});
|
|
203
|
+
it('excludes top-of-funnel nodes when disabled', () => {
|
|
204
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition({
|
|
205
|
+
includeTopOfFunnel: false,
|
|
206
|
+
});
|
|
207
|
+
expect(findNode(workflow, 'icebox-research')).toBeUndefined();
|
|
208
|
+
expect(findNode(workflow, 'icebox-backlog-creation')).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// Decision parity: verify the adapter covers all decision paths
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
describe('decision parity with decideAction()', () => {
|
|
215
|
+
const workflow = DecisionEngineAdapter.toWorkflowDefinition({
|
|
216
|
+
includeMergeQueue: true,
|
|
217
|
+
includeTopOfFunnel: true,
|
|
218
|
+
});
|
|
219
|
+
it('covers active session skip → guard-active-session node exists', () => {
|
|
220
|
+
const ctx = makeContext({ hasActiveSession: true });
|
|
221
|
+
const result = decideAction(ctx);
|
|
222
|
+
expect(result.action).toBe('none');
|
|
223
|
+
expect(findNode(workflow, 'guard-active-session')).toBeDefined();
|
|
224
|
+
});
|
|
225
|
+
it('covers cooldown skip → guard-cooldown node exists', () => {
|
|
226
|
+
const ctx = makeContext({ isWithinCooldown: true });
|
|
227
|
+
const result = decideAction(ctx);
|
|
228
|
+
expect(result.action).toBe('none');
|
|
229
|
+
expect(findNode(workflow, 'guard-cooldown')).toBeDefined();
|
|
230
|
+
});
|
|
231
|
+
it('covers hold skip → guard-hold node exists', () => {
|
|
232
|
+
const ctx = makeContext({ isHeld: true });
|
|
233
|
+
const result = decideAction(ctx);
|
|
234
|
+
expect(result.action).toBe('none');
|
|
235
|
+
expect(findNode(workflow, 'guard-hold')).toBeDefined();
|
|
236
|
+
});
|
|
237
|
+
it('covers circuit breaker → guard-circuit-breaker node exists', () => {
|
|
238
|
+
const ctx = makeContext({ completedSessionCount: MAX_SESSION_ATTEMPTS });
|
|
239
|
+
const result = decideAction(ctx);
|
|
240
|
+
expect(result.action).toBe('none');
|
|
241
|
+
expect(findNode(workflow, 'guard-circuit-breaker')).toBeDefined();
|
|
242
|
+
});
|
|
243
|
+
it('covers terminal status → guard-terminal-status node exists', () => {
|
|
244
|
+
for (const status of ['Accepted', 'Canceled', 'Duplicate']) {
|
|
245
|
+
const ctx = makeContext({ issue: makeIssue({ status }) });
|
|
246
|
+
const result = decideAction(ctx);
|
|
247
|
+
expect(result.action).toBe('none');
|
|
248
|
+
}
|
|
249
|
+
expect(findNode(workflow, 'guard-terminal-status')).toBeDefined();
|
|
250
|
+
});
|
|
251
|
+
it('covers sub-issue guard → guard-sub-issue node exists', () => {
|
|
252
|
+
const ctx = makeContext({ issue: makeIssue({ parentId: 'parent-1' }) });
|
|
253
|
+
const result = decideAction(ctx);
|
|
254
|
+
expect(result.action).toBe('none');
|
|
255
|
+
expect(findNode(workflow, 'guard-sub-issue')).toBeDefined();
|
|
256
|
+
});
|
|
257
|
+
it('covers Backlog → trigger-development', () => {
|
|
258
|
+
const ctx = makeContext({ issue: makeIssue({ status: 'Backlog' }) });
|
|
259
|
+
const result = decideAction(ctx);
|
|
260
|
+
expect(result.action).toBe('trigger-development');
|
|
261
|
+
const node = findNode(workflow, 'route-backlog');
|
|
262
|
+
const dispatchStep = node?.steps?.find(s => s.action === 'trigger-development');
|
|
263
|
+
expect(dispatchStep).toBeDefined();
|
|
264
|
+
});
|
|
265
|
+
it('covers Finished → trigger-qa', () => {
|
|
266
|
+
const ctx = makeContext({ issue: makeIssue({ status: 'Finished' }) });
|
|
267
|
+
const result = decideAction(ctx);
|
|
268
|
+
expect(result.action).toBe('trigger-qa');
|
|
269
|
+
const node = findNode(workflow, 'route-finished');
|
|
270
|
+
const qaStep = node?.steps?.find(s => s.action === 'trigger-qa');
|
|
271
|
+
expect(qaStep).toBeDefined();
|
|
272
|
+
});
|
|
273
|
+
it('covers Finished + escalate-human → escalate-human', () => {
|
|
274
|
+
const ctx = makeContext({
|
|
275
|
+
issue: makeIssue({ status: 'Finished' }),
|
|
276
|
+
workflowStrategy: 'escalate-human',
|
|
277
|
+
});
|
|
278
|
+
const result = decideAction(ctx);
|
|
279
|
+
expect(result.action).toBe('escalate-human');
|
|
280
|
+
const node = findNode(workflow, 'route-finished');
|
|
281
|
+
const escalateStep = node?.steps?.find(s => s.action === 'escalate-human');
|
|
282
|
+
expect(escalateStep).toBeDefined();
|
|
283
|
+
});
|
|
284
|
+
it('covers Finished + decompose → decompose', () => {
|
|
285
|
+
const ctx = makeContext({
|
|
286
|
+
issue: makeIssue({ status: 'Finished' }),
|
|
287
|
+
workflowStrategy: 'decompose',
|
|
288
|
+
});
|
|
289
|
+
const result = decideAction(ctx);
|
|
290
|
+
expect(result.action).toBe('decompose');
|
|
291
|
+
const node = findNode(workflow, 'route-finished');
|
|
292
|
+
const decomposeStep = node?.steps?.find(s => s.action === 'decompose');
|
|
293
|
+
expect(decomposeStep).toBeDefined();
|
|
294
|
+
});
|
|
295
|
+
it('Finished always triggers QA even with merge queue enabled (merge queue does not bypass QA)', () => {
|
|
296
|
+
const ctx = makeContext({
|
|
297
|
+
issue: makeIssue({ status: 'Finished' }),
|
|
298
|
+
mergeQueueEnabled: true,
|
|
299
|
+
});
|
|
300
|
+
const result = decideAction(ctx);
|
|
301
|
+
expect(result.action).toBe('trigger-qa');
|
|
302
|
+
const node = findNode(workflow, 'route-finished');
|
|
303
|
+
// No trigger-merge step should exist in the Finished node
|
|
304
|
+
const mergeStep = node?.steps?.find(s => s.action === 'trigger-merge');
|
|
305
|
+
expect(mergeStep).toBeUndefined();
|
|
306
|
+
});
|
|
307
|
+
it('covers Delivered → trigger-acceptance', () => {
|
|
308
|
+
const ctx = makeContext({ issue: makeIssue({ status: 'Delivered' }) });
|
|
309
|
+
const result = decideAction(ctx);
|
|
310
|
+
expect(result.action).toBe('trigger-acceptance');
|
|
311
|
+
const node = findNode(workflow, 'route-delivered');
|
|
312
|
+
const acceptStep = node?.steps?.find(s => s.action === 'trigger-acceptance');
|
|
313
|
+
expect(acceptStep).toBeDefined();
|
|
314
|
+
});
|
|
315
|
+
it('covers Rejected → trigger-refinement', () => {
|
|
316
|
+
const ctx = makeContext({ issue: makeIssue({ status: 'Rejected' }) });
|
|
317
|
+
const result = decideAction(ctx);
|
|
318
|
+
expect(result.action).toBe('trigger-refinement');
|
|
319
|
+
const node = findNode(workflow, 'route-rejected');
|
|
320
|
+
const refineStep = node?.steps?.find(s => s.action === 'trigger-refinement');
|
|
321
|
+
expect(refineStep).toBeDefined();
|
|
322
|
+
});
|
|
323
|
+
it('covers Rejected + escalate-human → escalate-human', () => {
|
|
324
|
+
const ctx = makeContext({
|
|
325
|
+
issue: makeIssue({ status: 'Rejected' }),
|
|
326
|
+
workflowStrategy: 'escalate-human',
|
|
327
|
+
});
|
|
328
|
+
const result = decideAction(ctx);
|
|
329
|
+
expect(result.action).toBe('escalate-human');
|
|
330
|
+
const node = findNode(workflow, 'route-rejected');
|
|
331
|
+
const escalateStep = node?.steps?.find(s => s.action === 'escalate-human');
|
|
332
|
+
expect(escalateStep).toBeDefined();
|
|
333
|
+
});
|
|
334
|
+
it('covers Started → none', () => {
|
|
335
|
+
const ctx = makeContext({ issue: makeIssue({ status: 'Started' }) });
|
|
336
|
+
const result = decideAction(ctx);
|
|
337
|
+
expect(result.action).toBe('none');
|
|
338
|
+
expect(findNode(workflow, 'route-started')).toBeDefined();
|
|
339
|
+
});
|
|
340
|
+
it('all decideAction decision points have corresponding workflow nodes', () => {
|
|
341
|
+
// Comprehensive check: every decision path in decideAction has a matching node
|
|
342
|
+
const nodeNames = workflow.nodes?.map(n => n.name) ?? [];
|
|
343
|
+
// Guard nodes
|
|
344
|
+
expect(nodeNames).toContain('guard-active-session');
|
|
345
|
+
expect(nodeNames).toContain('guard-cooldown');
|
|
346
|
+
expect(nodeNames).toContain('guard-hold');
|
|
347
|
+
expect(nodeNames).toContain('guard-gates');
|
|
348
|
+
expect(nodeNames).toContain('guard-circuit-breaker');
|
|
349
|
+
expect(nodeNames).toContain('guard-terminal-status');
|
|
350
|
+
expect(nodeNames).toContain('guard-sub-issue');
|
|
351
|
+
// Routing nodes
|
|
352
|
+
expect(nodeNames).toContain('route-backlog');
|
|
353
|
+
expect(nodeNames).toContain('route-started');
|
|
354
|
+
expect(nodeNames).toContain('route-finished');
|
|
355
|
+
expect(nodeNames).toContain('route-delivered');
|
|
356
|
+
expect(nodeNames).toContain('route-rejected');
|
|
357
|
+
// Top-of-funnel
|
|
358
|
+
expect(nodeNames).toContain('icebox-research');
|
|
359
|
+
expect(nodeNames).toContain('icebox-backlog-creation');
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
@@ -244,13 +244,9 @@ function decideFinished(ctx) {
|
|
|
244
244
|
reason: `Issue ${issue.identifier} is in Finished with decompose strategy — triggering decomposition`,
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
action: 'trigger-merge',
|
|
251
|
-
reason: `Issue ${issue.identifier} is in Finished — enqueuing to merge queue`,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
247
|
+
// Always run QA to validate functional correctness, even when merge queue is
|
|
248
|
+
// enabled. The merge queue handles git mechanics (rebase, conflict resolution)
|
|
249
|
+
// at merge time — it should not bypass implementation validation.
|
|
254
250
|
return {
|
|
255
251
|
action: 'trigger-qa',
|
|
256
252
|
reason: `Issue ${issue.identifier} is in Finished — triggering QA`,
|
|
@@ -408,15 +408,15 @@ describe('decideAction — Finished (QA)', () => {
|
|
|
408
408
|
// ---------------------------------------------------------------------------
|
|
409
409
|
// Finished (merge queue)
|
|
410
410
|
// ---------------------------------------------------------------------------
|
|
411
|
-
describe('decideAction — Finished (merge queue)', () => {
|
|
412
|
-
it('triggers
|
|
411
|
+
describe('decideAction — Finished (merge queue does not bypass QA)', () => {
|
|
412
|
+
it('triggers QA even when mergeQueueEnabled is true', () => {
|
|
413
413
|
const ctx = makeContext({
|
|
414
414
|
issue: makeIssue({ status: 'Finished' }),
|
|
415
415
|
mergeQueueEnabled: true,
|
|
416
416
|
});
|
|
417
417
|
const result = decideAction(ctx);
|
|
418
|
-
expect(result.action).toBe('trigger-
|
|
419
|
-
expect(result.reason).toContain('
|
|
418
|
+
expect(result.action).toBe('trigger-qa');
|
|
419
|
+
expect(result.reason).toContain('triggering QA');
|
|
420
420
|
});
|
|
421
421
|
it('triggers QA when mergeQueueEnabled is false', () => {
|
|
422
422
|
const ctx = makeContext({
|
|
@@ -453,7 +453,7 @@ describe('decideAction — Finished (merge queue)', () => {
|
|
|
453
453
|
const result = decideAction(ctx);
|
|
454
454
|
expect(result.action).toBe('decompose');
|
|
455
455
|
});
|
|
456
|
-
it('auto-QA disabled takes precedence
|
|
456
|
+
it('auto-QA disabled takes precedence even with merge queue', () => {
|
|
457
457
|
const ctx = makeContext({
|
|
458
458
|
issue: makeIssue({ status: 'Finished' }),
|
|
459
459
|
mergeQueueEnabled: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/governor/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/governor/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAA;AACpC,cAAc,wBAAwB,CAAA;AACtC,cAAc,oBAAoB,CAAA;AAClC,cAAc,uBAAuB,CAAA;AACrC,cAAc,qBAAqB,CAAA;AACnC,cAAc,sBAAsB,CAAA;AACpC,cAAc,eAAe,CAAA;AAC7B,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,yBAAyB,CAAA;AACvC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,uBAAuB,CAAA;AACrC,cAAc,8BAA8B,CAAA"}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,sBAAsB,CAAA;AACpC,cAAc,aAAa,CAAA;AAC3B,cAAc,uBAAuB,CAAA;AACrC,cAAc,sBAAsB,CAAA;AACpC,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,qBAAqB,CAAA;AACnC,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,oBAAoB,CAAA;AAClC,cAAc,qBAAqB,CAAA;AACnC,cAAc,wBAAwB,CAAA"}
|
package/dist/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest/route-manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA;AAElD,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAA;IACZ,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAA;IACjB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"route-manifest.d.ts","sourceRoot":"","sources":["../../../src/manifest/route-manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,CAAA;AAElD,MAAM,WAAW,UAAU;IACzB,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAA;IACZ,0FAA0F;IAC1F,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAA;CAC7C;AAED,MAAM,WAAW,SAAS;IACxB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAA;IACjB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAA;IAClB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,UAAU,EAAE,CAAA;IACpB,KAAK,EAAE,SAAS,EAAE,CAAA;CACnB;AAED,eAAO,MAAM,cAAc,EAAE,aAuJ5B,CAAA"}
|
|
@@ -99,6 +99,10 @@ export const ROUTE_MANIFEST = {
|
|
|
99
99
|
path: 'src/app/api/public/sessions/[id]/route.ts',
|
|
100
100
|
methods: { GET: 'routes.public.sessionDetail.GET' },
|
|
101
101
|
},
|
|
102
|
+
{
|
|
103
|
+
path: 'src/app/api/public/phase-metrics/route.ts',
|
|
104
|
+
methods: { GET: 'routes.public.phaseMetrics.GET' },
|
|
105
|
+
},
|
|
102
106
|
// Config route
|
|
103
107
|
{
|
|
104
108
|
path: 'src/app/api/config/route.ts',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Merge Queue Adapter
|
|
3
|
+
*
|
|
4
|
+
* Self-hosted merge queue that uses the built-in merge worker + Redis storage
|
|
5
|
+
* instead of an external service like GitHub's merge queue. This is the default
|
|
6
|
+
* provider — it works with any GitHub repository without requiring GitHub's
|
|
7
|
+
* paid merge queue feature.
|
|
8
|
+
*
|
|
9
|
+
* The adapter handles queue management (enqueue/dequeue/status). The merge worker
|
|
10
|
+
* (merge-worker.ts) handles the actual rebase → resolve → test → merge pipeline.
|
|
11
|
+
*
|
|
12
|
+
* PR eligibility is checked via `gh pr view` CLI (no GraphQL needed).
|
|
13
|
+
*/
|
|
14
|
+
import type { MergeQueueAdapter, MergeQueueStatus } from '../types.js';
|
|
15
|
+
export interface LocalMergeQueueStorage {
|
|
16
|
+
enqueue(entry: {
|
|
17
|
+
repoId: string;
|
|
18
|
+
prNumber: number;
|
|
19
|
+
prUrl: string;
|
|
20
|
+
issueIdentifier: string;
|
|
21
|
+
priority: number;
|
|
22
|
+
sourceBranch: string;
|
|
23
|
+
targetBranch: string;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
dequeue(repoId: string): Promise<{
|
|
26
|
+
prNumber: number;
|
|
27
|
+
} | null>;
|
|
28
|
+
/** Get queue depth for a repo */
|
|
29
|
+
getQueueDepth(repoId: string): Promise<number>;
|
|
30
|
+
/** Check if a PR is already in the queue */
|
|
31
|
+
isEnqueued(repoId: string, prNumber: number): Promise<boolean>;
|
|
32
|
+
/** Get position of a PR in the queue (1-based), or null if not queued */
|
|
33
|
+
getPosition(repoId: string, prNumber: number): Promise<number | null>;
|
|
34
|
+
/** Remove a specific PR from the queue */
|
|
35
|
+
remove(repoId: string, prNumber: number): Promise<void>;
|
|
36
|
+
/** Get failed/blocked status for a PR */
|
|
37
|
+
getFailedReason(repoId: string, prNumber: number): Promise<string | null>;
|
|
38
|
+
getBlockedReason(repoId: string, prNumber: number): Promise<string | null>;
|
|
39
|
+
}
|
|
40
|
+
export declare class LocalMergeQueueAdapter implements MergeQueueAdapter {
|
|
41
|
+
private storage;
|
|
42
|
+
readonly name: "local";
|
|
43
|
+
constructor(storage: LocalMergeQueueStorage);
|
|
44
|
+
/**
|
|
45
|
+
* Check if a PR is eligible for the local merge queue.
|
|
46
|
+
* Uses `gh pr view` to verify PR is open. Does NOT require the PR to be
|
|
47
|
+
* conflict-free — the merge worker handles rebasing.
|
|
48
|
+
*/
|
|
49
|
+
canEnqueue(owner: string, repo: string, prNumber: number): Promise<boolean>;
|
|
50
|
+
/**
|
|
51
|
+
* Add a PR to the local merge queue.
|
|
52
|
+
* The merge worker will pick it up and process it (rebase, test, merge).
|
|
53
|
+
*/
|
|
54
|
+
enqueue(owner: string, repo: string, prNumber: number): Promise<MergeQueueStatus>;
|
|
55
|
+
/**
|
|
56
|
+
* Get the status of a PR in the local merge queue.
|
|
57
|
+
*/
|
|
58
|
+
getStatus(owner: string, repo: string, prNumber: number): Promise<MergeQueueStatus>;
|
|
59
|
+
/**
|
|
60
|
+
* Remove a PR from the local merge queue.
|
|
61
|
+
*/
|
|
62
|
+
dequeue(owner: string, repo: string, prNumber: number): Promise<void>;
|
|
63
|
+
/**
|
|
64
|
+
* Local merge queue is always available (no external service dependency).
|
|
65
|
+
*/
|
|
66
|
+
isEnabled(_owner: string, _repo: string): Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../../src/merge-queue/adapters/local.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAWtE,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,KAAK,EAAE;QACb,MAAM,EAAE,MAAM,CAAA;QACd,QAAQ,EAAE,MAAM,CAAA;QAChB,KAAK,EAAE,MAAM,CAAA;QACb,eAAe,EAAE,MAAM,CAAA;QACvB,QAAQ,EAAE,MAAM,CAAA;QAChB,YAAY,EAAE,MAAM,CAAA;QACpB,YAAY,EAAE,MAAM,CAAA;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IAE7D,iCAAiC;IACjC,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE9C,4CAA4C;IAC5C,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE9D,yEAAyE;IACzE,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAErE,0CAA0C;IAC1C,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvD,yCAAyC;IACzC,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACzE,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC3E;AAMD,qBAAa,sBAAuB,YAAW,iBAAiB;IAGlD,OAAO,CAAC,OAAO;IAF3B,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAEnD;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAcjF;;;OAGG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAqCvF;;OAEG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkDzF;;OAEG;IACG,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK3E;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGjE"}
|