@zibby/core 0.1.48 → 0.3.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.
Files changed (43) hide show
  1. package/dist/index.js +100 -100
  2. package/dist/package.json +2 -2
  3. package/dist/register-built-in-strategies.js +57 -57
  4. package/dist/strategies/assistant-strategy.js +1 -1
  5. package/dist/strategies/claude-strategy.js +3 -3
  6. package/dist/strategies/codex-strategy.js +3 -3
  7. package/dist/strategies/cursor-strategy.js +30 -30
  8. package/dist/strategies/gemini-strategy.js +13 -13
  9. package/dist/strategies/index.js +57 -57
  10. package/dist/templates/browser-test-automation/README.md +136 -0
  11. package/dist/templates/browser-test-automation/chat.mjs +36 -0
  12. package/dist/templates/browser-test-automation/graph.mjs +54 -0
  13. package/dist/templates/browser-test-automation/nodes/execute-live.mjs +222 -0
  14. package/dist/templates/browser-test-automation/nodes/generate-script.mjs +97 -0
  15. package/dist/templates/browser-test-automation/nodes/index.mjs +3 -0
  16. package/dist/templates/browser-test-automation/nodes/preflight.mjs +59 -0
  17. package/dist/templates/browser-test-automation/nodes/utils.mjs +297 -0
  18. package/dist/templates/browser-test-automation/pipeline-ids.js +12 -0
  19. package/dist/templates/browser-test-automation/result-handler.mjs +327 -0
  20. package/dist/templates/browser-test-automation/run-index.mjs +420 -0
  21. package/dist/templates/browser-test-automation/run_test.json +358 -0
  22. package/dist/templates/code-analysis/graph.js +72 -0
  23. package/dist/templates/code-analysis/index.js +18 -0
  24. package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +204 -0
  25. package/dist/templates/code-analysis/nodes/create-pr-node.js +175 -0
  26. package/dist/templates/code-analysis/nodes/finalize-node.js +118 -0
  27. package/dist/templates/code-analysis/nodes/generate-code-node.js +425 -0
  28. package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +376 -0
  29. package/dist/templates/code-analysis/nodes/services/prMetaService.js +86 -0
  30. package/dist/templates/code-analysis/nodes/setup-node.js +142 -0
  31. package/dist/templates/code-analysis/prompts/analyze-ticket.md +181 -0
  32. package/dist/templates/code-analysis/prompts/generate-code.md +33 -0
  33. package/dist/templates/code-analysis/prompts/generate-test-cases.md +110 -0
  34. package/dist/templates/code-analysis/state.js +40 -0
  35. package/dist/templates/code-implementation/graph.js +35 -0
  36. package/dist/templates/code-implementation/index.js +7 -0
  37. package/dist/templates/code-implementation/state.js +14 -0
  38. package/dist/templates/global-setup.js +56 -0
  39. package/dist/templates/index.js +94 -0
  40. package/dist/templates/register-nodes.js +24 -0
  41. package/dist/utils/run-index-post-cli.js +4 -4
  42. package/package.json +2 -2
  43. package/templates/browser-test-automation/run-index.mjs +4 -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
+ }