@zibby/core 0.4.6 โ†’ 0.5.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 (78) hide show
  1. package/dist/index.js +150 -153
  2. package/dist/package.json +1 -8
  3. package/dist/utils/run-index-post-cli.js +1 -4
  4. package/package.json +1 -8
  5. package/dist/templates/browser-test-automation/README.md +0 -136
  6. package/dist/templates/browser-test-automation/chat.mjs +0 -36
  7. package/dist/templates/browser-test-automation/graph.mjs +0 -80
  8. package/dist/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
  9. package/dist/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
  10. package/dist/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
  11. package/dist/templates/browser-test-automation/nodes/index.mjs +0 -4
  12. package/dist/templates/browser-test-automation/nodes/preflight.mjs +0 -94
  13. package/dist/templates/browser-test-automation/nodes/utils.mjs +0 -297
  14. package/dist/templates/browser-test-automation/pipeline-ids.js +0 -12
  15. package/dist/templates/browser-test-automation/result-handler.mjs +0 -327
  16. package/dist/templates/browser-test-automation/run-index.mjs +0 -420
  17. package/dist/templates/browser-test-automation/run_test.json +0 -358
  18. package/dist/templates/browser-test-automation/state.js +0 -61
  19. package/dist/templates/code-analysis/README.md +0 -60
  20. package/dist/templates/code-analysis/graph.js +0 -72
  21. package/dist/templates/code-analysis/graph.mjs +0 -33
  22. package/dist/templates/code-analysis/index.js +0 -18
  23. package/dist/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
  24. package/dist/templates/code-analysis/nodes/create-pr-node.js +0 -175
  25. package/dist/templates/code-analysis/nodes/finalize-node.js +0 -118
  26. package/dist/templates/code-analysis/nodes/generate-code-node.js +0 -425
  27. package/dist/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
  28. package/dist/templates/code-analysis/nodes/services/prMetaService.js +0 -86
  29. package/dist/templates/code-analysis/nodes/setup-node.js +0 -142
  30. package/dist/templates/code-analysis/prompts/analyze-ticket.md +0 -181
  31. package/dist/templates/code-analysis/prompts/generate-code.md +0 -33
  32. package/dist/templates/code-analysis/prompts/generate-test-cases.md +0 -110
  33. package/dist/templates/code-analysis/state.js +0 -48
  34. package/dist/templates/generate-test-cases/README.md +0 -72
  35. package/dist/templates/generate-test-cases/graph.mjs +0 -46
  36. package/dist/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
  37. package/dist/templates/generate-test-cases/nodes/setup-node.js +0 -142
  38. package/dist/templates/generate-test-cases/state.js +0 -54
  39. package/dist/templates/global-setup.js +0 -56
  40. package/dist/templates/index.js +0 -147
  41. package/dist/templates/register-nodes.js +0 -24
  42. package/templates/browser-test-automation/README.md +0 -136
  43. package/templates/browser-test-automation/chat.mjs +0 -36
  44. package/templates/browser-test-automation/graph.mjs +0 -80
  45. package/templates/browser-test-automation/nodes/cache-replay.mjs +0 -213
  46. package/templates/browser-test-automation/nodes/execute-live.mjs +0 -254
  47. package/templates/browser-test-automation/nodes/generate-script.mjs +0 -108
  48. package/templates/browser-test-automation/nodes/index.mjs +0 -4
  49. package/templates/browser-test-automation/nodes/preflight.mjs +0 -94
  50. package/templates/browser-test-automation/nodes/utils.mjs +0 -297
  51. package/templates/browser-test-automation/pipeline-ids.js +0 -12
  52. package/templates/browser-test-automation/result-handler.mjs +0 -327
  53. package/templates/browser-test-automation/run-index.mjs +0 -420
  54. package/templates/browser-test-automation/run_test.json +0 -358
  55. package/templates/browser-test-automation/state.js +0 -61
  56. package/templates/code-analysis/README.md +0 -60
  57. package/templates/code-analysis/graph.js +0 -72
  58. package/templates/code-analysis/graph.mjs +0 -33
  59. package/templates/code-analysis/index.js +0 -18
  60. package/templates/code-analysis/nodes/analyze-ticket-node.js +0 -204
  61. package/templates/code-analysis/nodes/create-pr-node.js +0 -175
  62. package/templates/code-analysis/nodes/finalize-node.js +0 -118
  63. package/templates/code-analysis/nodes/generate-code-node.js +0 -425
  64. package/templates/code-analysis/nodes/generate-test-cases-node.js +0 -376
  65. package/templates/code-analysis/nodes/services/prMetaService.js +0 -86
  66. package/templates/code-analysis/nodes/setup-node.js +0 -142
  67. package/templates/code-analysis/prompts/analyze-ticket.md +0 -181
  68. package/templates/code-analysis/prompts/generate-code.md +0 -33
  69. package/templates/code-analysis/prompts/generate-test-cases.md +0 -110
  70. package/templates/code-analysis/state.js +0 -48
  71. package/templates/generate-test-cases/README.md +0 -72
  72. package/templates/generate-test-cases/graph.mjs +0 -46
  73. package/templates/generate-test-cases/nodes/generate-test-cases-node.js +0 -381
  74. package/templates/generate-test-cases/nodes/setup-node.js +0 -142
  75. package/templates/generate-test-cases/state.js +0 -54
  76. package/templates/global-setup.js +0 -56
  77. package/templates/index.js +0 -147
  78. package/templates/register-nodes.js +0 -24
@@ -1,204 +0,0 @@
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 '@zibby/core/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
- }
@@ -1,175 +0,0 @@
1
- import axios from 'axios';
2
- import { adfToText } from '@zibby/core/utils/adf-converter.js';
3
- import { z } from 'zod';
4
-
5
- const CreatePROutputSchema = z.object({
6
- success: z.boolean(),
7
- pullRequests: z.array(z.object({
8
- number: z.number(),
9
- url: z.string(),
10
- title: z.string(),
11
- repo: z.string(),
12
- branch: z.string(),
13
- timestamp: z.string()
14
- })),
15
- pr: z.object({
16
- number: z.number(),
17
- url: z.string(),
18
- title: z.string(),
19
- repo: z.string(),
20
- branch: z.string(),
21
- timestamp: z.string()
22
- }).nullable()
23
- });
24
-
25
- export const createPRNode = {
26
- name: 'create_pr',
27
- outputSchema: CreatePROutputSchema,
28
- execute: async (state) => {
29
- console.log('\n๐Ÿ”€ Creating Pull Request(s)...');
30
-
31
- const { ticketContext, repos, githubToken } = state;
32
- const codeImpl = state.implement_code_output?.codeImplementation;
33
- const tests = state.generate_test_cases_output?.tests;
34
-
35
- if (!codeImpl) {
36
- throw new Error('No code implementation found');
37
- }
38
-
39
- if (!githubToken) {
40
- console.log('โš ๏ธ No GitHub token found, skipping PR creation');
41
- return { success: true, pullRequests: [] };
42
- }
43
-
44
- const githubRepos = (repos || []).filter(r => {
45
- if (r.provider && r.provider !== 'github') return false;
46
- return r.url?.includes('github.com');
47
- });
48
-
49
- if (githubRepos.length === 0) {
50
- console.log('โš ๏ธ No GitHub repositories found, skipping PR creation');
51
- return { success: true, pullRequests: [] };
52
- }
53
-
54
- const committedRepos = new Set(codeImpl.committedRepos || []);
55
- const prBody = buildPRBody(ticketContext, codeImpl, tests);
56
- const pullRequests = [];
57
- const errors = [];
58
-
59
- for (const repo of githubRepos) {
60
- if (committedRepos.size > 0 && !committedRepos.has(repo.name)) {
61
- console.log(`โญ๏ธ No changes committed in ${repo.name}, skipping PR`);
62
- continue;
63
- }
64
-
65
- const match = repo.url.match(/github\.com\/([^/]+)\/([^/]+)/);
66
- if (!match) {
67
- console.warn(`โš ๏ธ Invalid GitHub URL for ${repo.name}: ${repo.url}`);
68
- continue;
69
- }
70
-
71
- const [, owner, repoName] = match;
72
- const cleanRepoName = repoName.replace(/\.git$/, '');
73
-
74
- console.log(`๐Ÿ“ค Creating PR in ${owner}/${cleanRepoName}...`);
75
-
76
- try {
77
- const response = await axios.post(
78
- `https://api.github.com/repos/${owner}/${cleanRepoName}/pulls`,
79
- {
80
- title: `${ticketContext.ticketKey}: ${ticketContext.summary}`,
81
- head: codeImpl.branchName,
82
- base: repo.branch || 'main',
83
- body: prBody,
84
- draft: false
85
- },
86
- {
87
- headers: {
88
- 'Authorization': `token ${githubToken}`,
89
- 'Accept': 'application/vnd.github.v3+json',
90
- 'Content-Type': 'application/json'
91
- }
92
- }
93
- );
94
-
95
- const pr = response.data;
96
- console.log(`โœ… PR created: ${pr.html_url} (#${pr.number})`);
97
-
98
- pullRequests.push({
99
- number: pr.number,
100
- url: pr.html_url,
101
- title: pr.title,
102
- repo: `${owner}/${cleanRepoName}`,
103
- branch: codeImpl.branchName,
104
- timestamp: new Date().toISOString()
105
- });
106
- } catch (error) {
107
- const msg = error.response?.data?.message || error.message;
108
- console.error(`โŒ Failed to create PR for ${owner}/${cleanRepoName}: ${msg}`);
109
- errors.push({ repo: `${owner}/${cleanRepoName}`, error: msg });
110
- }
111
- }
112
-
113
- if (pullRequests.length === 0 && errors.length > 0) {
114
- throw new Error(`All PR creations failed: ${errors.map(e => `${e.repo}: ${e.error}`).join('; ')}`);
115
- }
116
-
117
- return {
118
- success: true,
119
- pullRequests,
120
- pr: pullRequests[0] || null
121
- };
122
- }
123
- };
124
-
125
- function buildPRBody(ticketContext, codeImpl, tests) {
126
- const description = typeof ticketContext.description === 'object'
127
- ? adfToText(ticketContext.description)
128
- : ticketContext.description;
129
- let body = `## ๐ŸŽซ Ticket: ${ticketContext.ticketKey}
130
-
131
- ${description || ticketContext.summary}
132
-
133
- ## ๐Ÿ“Š Changes
134
-
135
- `;
136
-
137
- if (codeImpl.metrics) {
138
- body += `### Metrics
139
- - **Files Changed**: ${codeImpl.metrics.filesChanged}
140
- - **Lines Added**: +${codeImpl.metrics.additions}
141
- - **Lines Deleted**: -${codeImpl.metrics.deletions}
142
- - **Net Change**: ${codeImpl.metrics.netChange} lines
143
-
144
- `;
145
- }
146
-
147
- body += `### Affected Files
148
- ${codeImpl.changedFiles.map(f => `- \`${f}\``).join('\n')}
149
-
150
- `;
151
-
152
- if (tests && tests.cases && tests.cases.length > 0) {
153
- body += `## ๐Ÿงช Test Cases
154
-
155
- Generated ${tests.cases.length} test cases:
156
-
157
- ${tests.cases.map((tc, i) => `### ${i + 1}. ${tc.name}
158
-
159
- ${tc.description}
160
-
161
- **Steps:**
162
- ${tc.steps.map((s, j) => `${j + 1}. ${s}`).join('\n')}
163
-
164
- **Expected:** ${tc.expected}
165
-
166
- `).join('\n')}
167
- `;
168
- }
169
-
170
- body += `---
171
- ๐Ÿค– *This PR was automatically generated by [Zibby Agent](https://zibby.dev)*
172
- `;
173
-
174
- return body;
175
- }
@@ -1,118 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- const ReportSchema = z.object({
4
- status: z.enum(['completed', 'blocked', 'insufficient_context', 'failed'])
5
- .describe('Final execution status'),
6
-
7
- summary: z.string()
8
- .describe('1-2 sentence human-readable summary of what was analyzed/implemented'),
9
-
10
- storyPoints: z.object({
11
- points: z.number(),
12
- confidence: z.enum(['high', 'medium', 'low'])
13
- }).optional()
14
- .describe('Story point estimate from analysis'),
15
-
16
- complexity: z.enum(['simple', 'medium', 'complex', 'unknown']).optional()
17
- .describe('Implementation complexity from analysis'),
18
-
19
- codeMetrics: z.object({
20
- filesChanged: z.number(),
21
- additions: z.number(),
22
- deletions: z.number(),
23
- netChange: z.number()
24
- }).optional()
25
- .describe('Code change metrics (only present when code was generated)'),
26
-
27
- testCount: z.number().optional()
28
- .describe('Number of test cases generated'),
29
-
30
- risks: z.array(z.string()).optional()
31
- .describe('Potential risks or challenges identified during analysis'),
32
-
33
- blockers: z.array(z.string()).optional()
34
- .describe('Blockers preventing implementation (when status is blocked/insufficient_context)')
35
- });
36
-
37
- const FinalizeOutputSchema = z.object({
38
- success: z.boolean(),
39
- report: ReportSchema
40
- });
41
-
42
- export const finalizeNode = {
43
- name: 'finalize',
44
- outputSchema: FinalizeOutputSchema,
45
-
46
- execute: async (state) => {
47
- console.log('\n๐Ÿ“ Finalizing...');
48
-
49
- const { ticketContext } = state;
50
-
51
- const analysis = state.analysis;
52
- const codeImpl = state.codeImplementation;
53
- const tests = state.tests;
54
-
55
- const structured = analysis?.structured;
56
- const validation = structured?.validation;
57
-
58
- let status = 'completed';
59
- let blockers;
60
- if (validation && !validation.canProceed) {
61
- status = validation.status === 'insufficient_context'
62
- ? 'insufficient_context'
63
- : 'blocked';
64
- blockers = validation.blockers;
65
- }
66
-
67
- const testCases = tests?.structured?.testCases || tests?.cases || [];
68
-
69
- const report = {
70
- status,
71
- summary: structured?.overallSummary || ticketContext.summary || `Analysis for ${ticketContext.ticketKey}`,
72
-
73
- ...(structured?.storyPointEstimate && {
74
- storyPoints: {
75
- points: structured.storyPointEstimate.points,
76
- confidence: structured.storyPointEstimate.confidence
77
- }
78
- }),
79
-
80
- ...(structured?.technicalAnalysis?.complexity && {
81
- complexity: structured.technicalAnalysis.complexity
82
- }),
83
-
84
- ...(codeImpl?.metrics && {
85
- codeMetrics: {
86
- filesChanged: codeImpl.metrics.filesChanged,
87
- additions: codeImpl.metrics.additions,
88
- deletions: codeImpl.metrics.deletions,
89
- netChange: codeImpl.metrics.netChange
90
- }
91
- }),
92
-
93
- ...(testCases.length > 0 && { testCount: testCases.length }),
94
-
95
- ...(structured?.technicalAnalysis?.risks?.length > 0 && {
96
- risks: structured.technicalAnalysis.risks
97
- }),
98
-
99
- ...(blockers?.length > 0 && { blockers })
100
- };
101
-
102
- console.log(`\n${ '='.repeat(60)}`);
103
- console.log('FINALIZE SUMMARY');
104
- console.log('='.repeat(60));
105
- console.log(JSON.stringify(report, null, 2));
106
- console.log('='.repeat(60));
107
-
108
- if (status !== 'completed') {
109
- console.log(`\nโš ๏ธ Execution blocked: ${status}`);
110
- if (validation?.reasoning) console.log(` Reason: ${validation.reasoning}`);
111
- }
112
-
113
- return {
114
- success: true,
115
- report
116
- };
117
- }
118
- };