@zibby/core 0.1.48 ā 0.2.0
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/index.js +100 -100
- package/dist/package.json +2 -2
- package/dist/register-built-in-strategies.js +52 -52
- package/dist/strategies/assistant-strategy.js +1 -1
- package/dist/strategies/claude-strategy.js +3 -3
- package/dist/strategies/codex-strategy.js +3 -3
- package/dist/strategies/cursor-strategy.js +30 -30
- package/dist/strategies/gemini-strategy.js +13 -13
- package/dist/strategies/index.js +57 -57
- package/dist/templates/browser-test-automation/README.md +136 -0
- package/dist/templates/browser-test-automation/chat.mjs +36 -0
- package/dist/templates/browser-test-automation/graph.mjs +54 -0
- package/dist/templates/browser-test-automation/nodes/execute-live.mjs +222 -0
- package/dist/templates/browser-test-automation/nodes/generate-script.mjs +97 -0
- package/dist/templates/browser-test-automation/nodes/index.mjs +3 -0
- package/dist/templates/browser-test-automation/nodes/preflight.mjs +59 -0
- package/dist/templates/browser-test-automation/nodes/utils.mjs +297 -0
- package/dist/templates/browser-test-automation/pipeline-ids.js +12 -0
- package/dist/templates/browser-test-automation/result-handler.mjs +327 -0
- package/dist/templates/browser-test-automation/run-index.mjs +418 -0
- package/dist/templates/browser-test-automation/run_test.json +358 -0
- package/dist/templates/code-analysis/graph.js +72 -0
- package/dist/templates/code-analysis/index.js +18 -0
- package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +204 -0
- package/dist/templates/code-analysis/nodes/create-pr-node.js +175 -0
- package/dist/templates/code-analysis/nodes/finalize-node.js +118 -0
- package/dist/templates/code-analysis/nodes/generate-code-node.js +425 -0
- package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +376 -0
- package/dist/templates/code-analysis/nodes/services/prMetaService.js +86 -0
- package/dist/templates/code-analysis/nodes/setup-node.js +142 -0
- package/dist/templates/code-analysis/prompts/analyze-ticket.md +181 -0
- package/dist/templates/code-analysis/prompts/generate-code.md +33 -0
- package/dist/templates/code-analysis/prompts/generate-test-cases.md +110 -0
- package/dist/templates/code-analysis/state.js +40 -0
- package/dist/templates/code-implementation/graph.js +35 -0
- package/dist/templates/code-implementation/index.js +7 -0
- package/dist/templates/code-implementation/state.js +14 -0
- package/dist/templates/global-setup.js +56 -0
- package/dist/templates/index.js +94 -0
- package/dist/templates/register-nodes.js +24 -0
- package/package.json +2 -2
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": [
|
|
3
|
+
{
|
|
4
|
+
"id": "preflight",
|
|
5
|
+
"type": "preflight",
|
|
6
|
+
"data": {
|
|
7
|
+
"nodeType": "preflight",
|
|
8
|
+
"label": "preflight"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "execute_live",
|
|
13
|
+
"type": "execute_live",
|
|
14
|
+
"data": {
|
|
15
|
+
"nodeType": "execute_live",
|
|
16
|
+
"label": "execute_live"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "generate_script",
|
|
21
|
+
"type": "generate_script",
|
|
22
|
+
"data": {
|
|
23
|
+
"nodeType": "generate_script",
|
|
24
|
+
"label": "generate_script"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"edges": [
|
|
29
|
+
{
|
|
30
|
+
"source": "preflight",
|
|
31
|
+
"target": "execute_live"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"source": "generate_script",
|
|
35
|
+
"target": "END"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"nodeConfigs": {
|
|
39
|
+
"preflight": {
|
|
40
|
+
"outputSchema": {
|
|
41
|
+
"jsonSchema": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"title": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Concise test title (5-10 words, action-oriented). Prefix with ticket ID if found."
|
|
47
|
+
},
|
|
48
|
+
"assertions": {
|
|
49
|
+
"type": "array",
|
|
50
|
+
"items": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"description": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "What to verify (e.g., \"User is redirected to dashboard\")"
|
|
56
|
+
},
|
|
57
|
+
"expected": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "What the expected outcome looks like (e.g., \"URL contains /dashboard\")"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"required": [
|
|
63
|
+
"description",
|
|
64
|
+
"expected"
|
|
65
|
+
],
|
|
66
|
+
"additionalProperties": false
|
|
67
|
+
},
|
|
68
|
+
"description": "Every expected result from the spec as a verifiable assertion"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"required": [
|
|
72
|
+
"title",
|
|
73
|
+
"assertions"
|
|
74
|
+
],
|
|
75
|
+
"additionalProperties": false
|
|
76
|
+
},
|
|
77
|
+
"variables": [
|
|
78
|
+
{
|
|
79
|
+
"path": "title",
|
|
80
|
+
"type": "string",
|
|
81
|
+
"label": "Concise test title (5-10 words, action-oriented). Prefix with ticket ID if found.",
|
|
82
|
+
"optional": false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"path": "assertions",
|
|
86
|
+
"type": "array",
|
|
87
|
+
"label": "Every expected result from the spec as a verifiable assertion",
|
|
88
|
+
"optional": false
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"path": "assertions[].description",
|
|
92
|
+
"type": "string",
|
|
93
|
+
"label": "What to verify (e.g., \"User is redirected to dashboard\")",
|
|
94
|
+
"optional": false
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"path": "assertions[].expected",
|
|
98
|
+
"type": "string",
|
|
99
|
+
"label": "What the expected outcome looks like (e.g., \"URL contains /dashboard\")",
|
|
100
|
+
"optional": false
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"execute_live": {
|
|
106
|
+
"outputSchema": {
|
|
107
|
+
"jsonSchema": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"properties": {
|
|
110
|
+
"success": {
|
|
111
|
+
"type": "boolean",
|
|
112
|
+
"description": "Whether the test execution completed successfully"
|
|
113
|
+
},
|
|
114
|
+
"steps": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": {
|
|
117
|
+
"type": "string"
|
|
118
|
+
},
|
|
119
|
+
"description": "Array of test steps executed"
|
|
120
|
+
},
|
|
121
|
+
"finalUrl": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "Final URL after test execution"
|
|
124
|
+
},
|
|
125
|
+
"actions": {
|
|
126
|
+
"type": "array",
|
|
127
|
+
"description": "Detailed array of actions performed with descriptions and reasoning"
|
|
128
|
+
},
|
|
129
|
+
"assertions": {
|
|
130
|
+
"type": "array",
|
|
131
|
+
"items": {
|
|
132
|
+
"type": "object",
|
|
133
|
+
"properties": {
|
|
134
|
+
"description": {
|
|
135
|
+
"type": "string",
|
|
136
|
+
"description": "What was verified"
|
|
137
|
+
},
|
|
138
|
+
"passed": {
|
|
139
|
+
"type": "boolean",
|
|
140
|
+
"description": "Whether the assertion passed"
|
|
141
|
+
},
|
|
142
|
+
"verifiedAfterAction": {
|
|
143
|
+
"type": "number",
|
|
144
|
+
"description": "Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED"
|
|
145
|
+
},
|
|
146
|
+
"evidence": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"description": "Brief evidence of what was observed"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"required": [
|
|
152
|
+
"description",
|
|
153
|
+
"passed",
|
|
154
|
+
"verifiedAfterAction"
|
|
155
|
+
],
|
|
156
|
+
"additionalProperties": false
|
|
157
|
+
},
|
|
158
|
+
"description": "Array of assertions made during test"
|
|
159
|
+
},
|
|
160
|
+
"waits": {
|
|
161
|
+
"type": "array",
|
|
162
|
+
"description": "Array of waits needed for proper test execution"
|
|
163
|
+
},
|
|
164
|
+
"evidenceScreenshots": {
|
|
165
|
+
"type": "array",
|
|
166
|
+
"items": {
|
|
167
|
+
"type": "object",
|
|
168
|
+
"properties": {
|
|
169
|
+
"filename": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"description": "Descriptive filename pattern: {step-number}-{action-or-state}.png"
|
|
172
|
+
},
|
|
173
|
+
"description": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"description": "What the screenshot shows and why it is evidence"
|
|
176
|
+
},
|
|
177
|
+
"verdict": {
|
|
178
|
+
"type": "string",
|
|
179
|
+
"enum": [
|
|
180
|
+
"pass",
|
|
181
|
+
"fail",
|
|
182
|
+
"info"
|
|
183
|
+
],
|
|
184
|
+
"description": "Test verdict: pass/fail for validation points, info for checkpoints"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
"required": [
|
|
188
|
+
"filename",
|
|
189
|
+
"description",
|
|
190
|
+
"verdict"
|
|
191
|
+
],
|
|
192
|
+
"additionalProperties": false
|
|
193
|
+
},
|
|
194
|
+
"description": "Array of screenshots taken at key validation points throughout the test"
|
|
195
|
+
},
|
|
196
|
+
"browserClosed": {
|
|
197
|
+
"type": "boolean",
|
|
198
|
+
"description": "Whether the browser was properly closed (should always be true)"
|
|
199
|
+
},
|
|
200
|
+
"notes": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"description": "Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute"
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"required": [
|
|
206
|
+
"success",
|
|
207
|
+
"steps",
|
|
208
|
+
"browserClosed"
|
|
209
|
+
],
|
|
210
|
+
"additionalProperties": false
|
|
211
|
+
},
|
|
212
|
+
"variables": [
|
|
213
|
+
{
|
|
214
|
+
"path": "success",
|
|
215
|
+
"type": "boolean",
|
|
216
|
+
"label": "Whether the test execution completed successfully",
|
|
217
|
+
"optional": false
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"path": "steps",
|
|
221
|
+
"type": "array",
|
|
222
|
+
"label": "Array of test steps executed",
|
|
223
|
+
"optional": false
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"path": "finalUrl",
|
|
227
|
+
"type": "string",
|
|
228
|
+
"label": "Final URL after test execution",
|
|
229
|
+
"optional": true
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"path": "actions",
|
|
233
|
+
"type": "array",
|
|
234
|
+
"label": "Detailed array of actions performed with descriptions and reasoning",
|
|
235
|
+
"optional": true
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
"path": "assertions",
|
|
239
|
+
"type": "array",
|
|
240
|
+
"label": "Array of assertions made during test",
|
|
241
|
+
"optional": true
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"path": "assertions[].description",
|
|
245
|
+
"type": "string",
|
|
246
|
+
"label": "What was verified",
|
|
247
|
+
"optional": false
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"path": "assertions[].passed",
|
|
251
|
+
"type": "boolean",
|
|
252
|
+
"label": "Whether the assertion passed",
|
|
253
|
+
"optional": false
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"path": "assertions[].verifiedAfterAction",
|
|
257
|
+
"type": "number",
|
|
258
|
+
"label": "Index of the action after which this was verified (0-based, matches actions array index) - REQUIRED",
|
|
259
|
+
"optional": false
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"path": "assertions[].evidence",
|
|
263
|
+
"type": "string",
|
|
264
|
+
"label": "Brief evidence of what was observed",
|
|
265
|
+
"optional": true
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"path": "waits",
|
|
269
|
+
"type": "array",
|
|
270
|
+
"label": "Array of waits needed for proper test execution",
|
|
271
|
+
"optional": true
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"path": "evidenceScreenshots",
|
|
275
|
+
"type": "array",
|
|
276
|
+
"label": "Array of screenshots taken at key validation points throughout the test",
|
|
277
|
+
"optional": true
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"path": "evidenceScreenshots[].filename",
|
|
281
|
+
"type": "string",
|
|
282
|
+
"label": "Descriptive filename pattern: {step-number}-{action-or-state}.png",
|
|
283
|
+
"optional": false
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
"path": "evidenceScreenshots[].description",
|
|
287
|
+
"type": "string",
|
|
288
|
+
"label": "What the screenshot shows and why it is evidence",
|
|
289
|
+
"optional": false
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"path": "evidenceScreenshots[].verdict",
|
|
293
|
+
"type": "string",
|
|
294
|
+
"label": "Test verdict: pass/fail for validation points, info for checkpoints",
|
|
295
|
+
"optional": false
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"path": "browserClosed",
|
|
299
|
+
"type": "boolean",
|
|
300
|
+
"label": "Whether the browser was properly closed (should always be true)",
|
|
301
|
+
"optional": false
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
"path": "notes",
|
|
305
|
+
"type": "string",
|
|
306
|
+
"label": "Additional notes or observations. REQUIRED when success=false to explain why test failed or could not execute",
|
|
307
|
+
"optional": true
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
"generate_script": {
|
|
313
|
+
"outputSchema": {
|
|
314
|
+
"jsonSchema": {
|
|
315
|
+
"type": "object",
|
|
316
|
+
"properties": {
|
|
317
|
+
"success": {
|
|
318
|
+
"type": "boolean"
|
|
319
|
+
},
|
|
320
|
+
"scriptPath": {
|
|
321
|
+
"type": "string"
|
|
322
|
+
},
|
|
323
|
+
"method": {
|
|
324
|
+
"type": "string"
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
"required": [
|
|
328
|
+
"success",
|
|
329
|
+
"scriptPath",
|
|
330
|
+
"method"
|
|
331
|
+
],
|
|
332
|
+
"additionalProperties": false
|
|
333
|
+
},
|
|
334
|
+
"variables": [
|
|
335
|
+
{
|
|
336
|
+
"path": "success",
|
|
337
|
+
"type": "boolean",
|
|
338
|
+
"label": "Success",
|
|
339
|
+
"optional": false
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"path": "scriptPath",
|
|
343
|
+
"type": "string",
|
|
344
|
+
"label": "Script Path",
|
|
345
|
+
"optional": false
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"path": "method",
|
|
349
|
+
"type": "string",
|
|
350
|
+
"label": "Method",
|
|
351
|
+
"optional": false
|
|
352
|
+
}
|
|
353
|
+
]
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
"stateSchema": null
|
|
358
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { setupNode } from './nodes/setup-node.js';
|
|
5
|
+
import { analyzeTicketNode } from './nodes/analyze-ticket-node.js';
|
|
6
|
+
import { generateCodeNode } from './nodes/generate-code-node.js';
|
|
7
|
+
import { generateTestCasesNode } from './nodes/generate-test-cases-node.js';
|
|
8
|
+
import { finalizeNode } from './nodes/finalize-node.js';
|
|
9
|
+
import { analysisStateSchema } from './state.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const promptsDir = join(__dirname, 'prompts');
|
|
13
|
+
|
|
14
|
+
// Helper to load prompt if file exists
|
|
15
|
+
function loadPrompt(filename) {
|
|
16
|
+
const path = join(promptsDir, filename);
|
|
17
|
+
if (existsSync(path)) {
|
|
18
|
+
return readFileSync(path, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Load prompt templates at graph definition time
|
|
24
|
+
const analyzeTicketPrompt = loadPrompt('analyze-ticket.md');
|
|
25
|
+
const generateCodePrompt = loadPrompt('generate-code.md');
|
|
26
|
+
const generateTestCasesPrompt = loadPrompt('generate-test-cases.md');
|
|
27
|
+
|
|
28
|
+
export function buildAnalysisGraph(graph) {
|
|
29
|
+
graph.setStateSchema(analysisStateSchema);
|
|
30
|
+
|
|
31
|
+
graph
|
|
32
|
+
.addNode('setup', setupNode)
|
|
33
|
+
.addNode('analyze_ticket', analyzeTicketNode, {
|
|
34
|
+
prompt: analyzeTicketPrompt
|
|
35
|
+
})
|
|
36
|
+
.addConditionalNode('validation_check', {
|
|
37
|
+
condition: (state) => {
|
|
38
|
+
const validation = state.analyze_ticket?.validation;
|
|
39
|
+
if (!validation?.canProceed) {
|
|
40
|
+
return 'finalize';
|
|
41
|
+
}
|
|
42
|
+
return 'generate_code';
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
.addNode('generate_code', generateCodeNode, {
|
|
46
|
+
prompt: generateCodePrompt
|
|
47
|
+
})
|
|
48
|
+
.addNode('generate_test_cases', generateTestCasesNode, {
|
|
49
|
+
prompt: generateTestCasesPrompt
|
|
50
|
+
})
|
|
51
|
+
.addNode('finalize', finalizeNode)
|
|
52
|
+
.setNodeType('validation_check', 'decision')
|
|
53
|
+
.setEntryPoint('setup')
|
|
54
|
+
.addEdge('setup', 'analyze_ticket')
|
|
55
|
+
.addEdge('analyze_ticket', 'validation_check')
|
|
56
|
+
.addConditionalEdges('validation_check', (state) => {
|
|
57
|
+
const validation = state.analyze_ticket?.validation || state.analyze_ticket_output?.validation;
|
|
58
|
+
if (validation?.canProceed) {
|
|
59
|
+
return 'generate_code';
|
|
60
|
+
}
|
|
61
|
+
return 'finalize';
|
|
62
|
+
}, {
|
|
63
|
+
labels: {
|
|
64
|
+
generate_code: 'if valid',
|
|
65
|
+
finalize: 'if invalid'
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
.addEdge('generate_code', 'generate_test_cases')
|
|
69
|
+
.addEdge('generate_test_cases', 'finalize');
|
|
70
|
+
|
|
71
|
+
return graph;
|
|
72
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Analysis Module
|
|
3
|
+
* Nodes for analyzing Jira tickets and generating code changes
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// State Schema
|
|
7
|
+
export { analysisStateSchema } from './state.js';
|
|
8
|
+
|
|
9
|
+
// Nodes
|
|
10
|
+
export { setupNode } from './nodes/setup-node.js';
|
|
11
|
+
export { analyzeTicketNode } from './nodes/analyze-ticket-node.js';
|
|
12
|
+
export { generateCodeNode, implementCodeNode } from './nodes/generate-code-node.js';
|
|
13
|
+
export { generateTestCasesNode } from './nodes/generate-test-cases-node.js';
|
|
14
|
+
export { createPRNode } from './nodes/create-pr-node.js';
|
|
15
|
+
export { finalizeNode } from './nodes/finalize-node.js';
|
|
16
|
+
|
|
17
|
+
// Graph Builder
|
|
18
|
+
export { buildAnalysisGraph } from './graph.js';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { randomBytes } from 'crypto';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { adfToText } from '../../../src/utils/adf-converter.js';
|
|
6
|
+
|
|
7
|
+
const generateId = () => randomBytes(16).toString('hex');
|
|
8
|
+
|
|
9
|
+
const SuggestedChangeSchema = z.object({
|
|
10
|
+
field: z.enum(['summary', 'description', 'labels'])
|
|
11
|
+
.describe('Which ticket field this change applies to'),
|
|
12
|
+
|
|
13
|
+
original: z.string()
|
|
14
|
+
.describe('The original value of the field'),
|
|
15
|
+
|
|
16
|
+
suggested: z.string()
|
|
17
|
+
.describe('The suggested new value for the field'),
|
|
18
|
+
|
|
19
|
+
reasoning: z.string()
|
|
20
|
+
.describe('Explanation of why this change improves the ticket')
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Shared validation schema (used by both AI agent output and node output)
|
|
24
|
+
const ValidationSchema = z.object({
|
|
25
|
+
canProceed: z.boolean()
|
|
26
|
+
.describe('Whether the ticket has enough information to proceed with implementation'),
|
|
27
|
+
|
|
28
|
+
status: z.enum(['valid', 'insufficient_context', 'unclear_requirements', 'invalid_ticket', 'needs_clarification'])
|
|
29
|
+
.describe('Status indicating why we can or cannot proceed'),
|
|
30
|
+
|
|
31
|
+
reasoning: z.string()
|
|
32
|
+
.describe('Explanation of the validation decision - why the ticket is valid or what is missing'),
|
|
33
|
+
|
|
34
|
+
blockers: z.array(z.string())
|
|
35
|
+
.nullable()
|
|
36
|
+
.describe('Specific blockers preventing implementation (if canProceed is false), null if none')
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const AnalysisOutputSchema = z.object({
|
|
40
|
+
validation: ValidationSchema
|
|
41
|
+
.describe('Validation of whether ticket is ready for implementation'),
|
|
42
|
+
|
|
43
|
+
suggestedChanges: z.array(SuggestedChangeSchema)
|
|
44
|
+
.describe('Array of suggested changes to ticket fields'),
|
|
45
|
+
|
|
46
|
+
technicalAnalysis: z.object({
|
|
47
|
+
requirements: z.array(z.string())
|
|
48
|
+
.describe('Key requirements extracted from the ticket'),
|
|
49
|
+
|
|
50
|
+
affectedFiles: z.array(z.string())
|
|
51
|
+
.nullable()
|
|
52
|
+
.describe('Files that will likely need changes (null if unknown). Each path MUST start with a top-level repo directory name (e.g. "my-api/src/...")'),
|
|
53
|
+
|
|
54
|
+
complexity: z.enum(['simple', 'medium', 'complex', 'unknown'])
|
|
55
|
+
.describe('Estimated complexity of the implementation'),
|
|
56
|
+
|
|
57
|
+
risks: z.array(z.string())
|
|
58
|
+
.nullable()
|
|
59
|
+
.describe('Potential risks or challenges, null if none')
|
|
60
|
+
}).describe('Technical analysis of the implementation'),
|
|
61
|
+
|
|
62
|
+
overallSummary: z.string()
|
|
63
|
+
.describe('Brief summary of the analysis findings'),
|
|
64
|
+
|
|
65
|
+
implementationPlan: z.string()
|
|
66
|
+
.describe('Markdown implementation plan for the code generation agent. Include: 1) A brief summary of what to build, 2) Which top-level repositories need changes ā MUST be the exact directory names from Repository Information, NEVER internal sub-modules or Maven modules, 3) An ordered TODO checklist where every file path starts with a top-level repo name (e.g. "In my-api/src/.../Foo.java" NOT "src/.../Foo.java"), 4) Key technical details like function signatures, patterns to follow, and gotchas. This will be passed directly to the coding agent as its instructions.')
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Node output schema (what this node returns to state)
|
|
70
|
+
const AnalyzeTicketNodeOutputSchema = z.object({
|
|
71
|
+
success: z.boolean(),
|
|
72
|
+
analysis: z.object({
|
|
73
|
+
raw: z.string(),
|
|
74
|
+
structured: z.object({
|
|
75
|
+
validation: ValidationSchema,
|
|
76
|
+
suggestedChanges: z.array(z.any()),
|
|
77
|
+
technicalAnalysis: z.any(),
|
|
78
|
+
overallSummary: z.string(),
|
|
79
|
+
implementationPlan: z.string()
|
|
80
|
+
}),
|
|
81
|
+
timestamp: z.string()
|
|
82
|
+
}),
|
|
83
|
+
validation: ValidationSchema // Exposed at root for easy access by conditional logic
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
export const analyzeTicketNode = {
|
|
87
|
+
name: 'analyze_ticket',
|
|
88
|
+
outputSchema: AnalyzeTicketNodeOutputSchema,
|
|
89
|
+
|
|
90
|
+
execute: async (context) => {
|
|
91
|
+
console.log('\nš Analyzing ticket with Cursor Agent...');
|
|
92
|
+
|
|
93
|
+
// Extract from context (new unified signature)
|
|
94
|
+
const { state, invokeAgent } = context;
|
|
95
|
+
const { workspace, ticketContext, repos, model } = state.getAll();
|
|
96
|
+
const aiModel = model || ticketContext.model || 'auto';
|
|
97
|
+
|
|
98
|
+
// Prepare description text
|
|
99
|
+
let descriptionText = ticketContext.description || 'No description provided';
|
|
100
|
+
if (typeof descriptionText === 'object') {
|
|
101
|
+
descriptionText = adfToText(descriptionText);
|
|
102
|
+
console.log('ā ļø Description is in ADF format, converted to plain text');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Build prompt values for template rendering
|
|
106
|
+
const promptValues = {
|
|
107
|
+
ticketKey: ticketContext.ticketKey,
|
|
108
|
+
ticketSummary: ticketContext.summary,
|
|
109
|
+
ticketDescription: descriptionText,
|
|
110
|
+
ticketType: ticketContext.type || 'Task',
|
|
111
|
+
ticketPriority: ticketContext.priority || 'Medium',
|
|
112
|
+
ticketLabels: Array.isArray(ticketContext.labels) ? ticketContext.labels.join(', ') : (ticketContext.labels || 'None'),
|
|
113
|
+
ticketComponents: Array.isArray(ticketContext.components)
|
|
114
|
+
? ticketContext.components.map(c => c.name || c).join(', ') || 'None'
|
|
115
|
+
: (ticketContext.components || 'None'),
|
|
116
|
+
additionalContext: ticketContext.additionalContext || null,
|
|
117
|
+
repositories: Array.isArray(repos) ? repos.map(r => ({
|
|
118
|
+
name: r.name,
|
|
119
|
+
...detectProjectInfo(join(workspace, r.name))
|
|
120
|
+
})) : []
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const images = (ticketContext.imageAttachments || [])
|
|
124
|
+
.filter(att => att.base64)
|
|
125
|
+
.map(att => ({ mimeType: att.mimeType, base64: att.base64, filename: att.filename }));
|
|
126
|
+
|
|
127
|
+
if (images.length > 0) {
|
|
128
|
+
console.log(`š¼ļø Including ${images.length} image attachment(s) for vision analysis`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`š Running AI Agent to analyze ticket with model: ${aiModel}...`);
|
|
132
|
+
|
|
133
|
+
// Use context.invokeAgent - template rendering handled by framework
|
|
134
|
+
const validatedOutput = await invokeAgent(promptValues, {
|
|
135
|
+
model: aiModel,
|
|
136
|
+
schema: AnalysisOutputSchema,
|
|
137
|
+
images
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Handle both structured response and raw response scenarios
|
|
141
|
+
const output = validatedOutput?.structured || validatedOutput;
|
|
142
|
+
|
|
143
|
+
const suggestedChanges = (output?.suggestedChanges || []).map(change => ({
|
|
144
|
+
id: generateId(),
|
|
145
|
+
...change,
|
|
146
|
+
status: 'pending'
|
|
147
|
+
}));
|
|
148
|
+
|
|
149
|
+
const analysis = {
|
|
150
|
+
raw: JSON.stringify(output),
|
|
151
|
+
structured: {
|
|
152
|
+
validation: output?.validation || { canProceed: false, status: 'invalid_ticket', reasoning: 'Failed to parse validation' },
|
|
153
|
+
suggestedChanges,
|
|
154
|
+
technicalAnalysis: output?.technicalAnalysis || { requirements: [], complexity: 'unknown' },
|
|
155
|
+
overallSummary: output?.overallSummary || 'Analysis failed',
|
|
156
|
+
implementationPlan: output?.implementationPlan || 'No plan generated'
|
|
157
|
+
},
|
|
158
|
+
timestamp: new Date().toISOString()
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
console.log(`ā
Parsed ${suggestedChanges.length} suggested changes`);
|
|
162
|
+
|
|
163
|
+
if (!output?.validation?.canProceed) {
|
|
164
|
+
console.log(`ā ļø Ticket validation failed: ${output?.validation?.status || 'unknown'}`);
|
|
165
|
+
console.log(` Reasoning: ${output?.validation?.reasoning || 'No reasoning provided'}`);
|
|
166
|
+
if (output?.validation?.blockers) {
|
|
167
|
+
console.log(` Blockers:`);
|
|
168
|
+
output.validation.blockers.forEach(blocker => {
|
|
169
|
+
console.log(` - ${blocker}`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
analysis,
|
|
177
|
+
validation: output?.validation || { canProceed: false, status: 'invalid_ticket', reasoning: 'No validation data' }
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
function detectProjectInfo(repoPath) {
|
|
183
|
+
const info = {
|
|
184
|
+
framework: 'Unknown',
|
|
185
|
+
language: 'Unknown',
|
|
186
|
+
packageManager: 'Unknown'
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (existsSync(join(repoPath, 'package.json'))) {
|
|
190
|
+
info.language = 'JavaScript/TypeScript';
|
|
191
|
+
const pkg = JSON.parse(readFileSync(join(repoPath, 'package.json'), 'utf-8'));
|
|
192
|
+
|
|
193
|
+
if (pkg.dependencies?.react) info.framework = 'React';
|
|
194
|
+
if (pkg.dependencies?.next) info.framework = 'Next.js';
|
|
195
|
+
if (pkg.dependencies?.vue) info.framework = 'Vue';
|
|
196
|
+
if (pkg.dependencies?.express) info.framework = 'Express';
|
|
197
|
+
|
|
198
|
+
if (existsSync(join(repoPath, 'yarn.lock'))) info.packageManager = 'yarn';
|
|
199
|
+
else if (existsSync(join(repoPath, 'pnpm-lock.yaml'))) info.packageManager = 'pnpm';
|
|
200
|
+
else info.packageManager = 'npm';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return info;
|
|
204
|
+
}
|