codeflow-hook 2.0.1 → 2.0.3
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/bin/codeflow-hook.js +1 -1
- package/lib/cli-integration/src/index.ts +111 -6
- package/lib/cli-integration/src/simulationEngine.ts +118 -4
- package/package.json +1 -1
- package/lib/cli-integration/dist/index.d.ts +0 -128
- package/lib/cli-integration/dist/index.js +0 -585
- package/lib/cli-integration/dist/pipelineConfigs.d.ts +0 -60
- package/lib/cli-integration/dist/pipelineConfigs.js +0 -549
- package/lib/cli-integration/dist/simulationEngine.d.ts +0 -86
- package/lib/cli-integration/dist/simulationEngine.js +0 -475
- package/lib/cli-integration/dist/types.d.ts +0 -156
- package/lib/cli-integration/dist/types.js +0 -15
|
@@ -1,585 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* CLI Integration Service - Phase 4
|
|
4
|
-
*
|
|
5
|
-
* Bridges the local CLI commands with EKG backend services.
|
|
6
|
-
* Transforms CLI operations from local processing to backend-driven workflows.
|
|
7
|
-
*
|
|
8
|
-
* Key transformations:
|
|
9
|
-
* - `codeflow index` → EKG Ingestion Service webhook simulation
|
|
10
|
-
* - `codeflow analyze-diff` → EKG Query Service context-enhanced analysis
|
|
11
|
-
*/
|
|
12
|
-
import axios from 'axios';
|
|
13
|
-
import { simpleGit } from 'simple-git';
|
|
14
|
-
import { config } from 'dotenv';
|
|
15
|
-
import winston from 'winston';
|
|
16
|
-
import path from 'path';
|
|
17
|
-
import fs from 'fs';
|
|
18
|
-
// Load environment variables
|
|
19
|
-
config();
|
|
20
|
-
// Configure logger
|
|
21
|
-
const logger = winston.createLogger({
|
|
22
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
23
|
-
format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()),
|
|
24
|
-
defaultMeta: { service: 'cli-integration' },
|
|
25
|
-
transports: [
|
|
26
|
-
new winston.transports.Console({
|
|
27
|
-
format: winston.format.combine(winston.format.colorize(), winston.format.simple())
|
|
28
|
-
}),
|
|
29
|
-
new winston.transports.File({ filename: 'cli-integration.log' })
|
|
30
|
-
]
|
|
31
|
-
});
|
|
32
|
-
/**
|
|
33
|
-
* CLI Integration Service
|
|
34
|
-
* Provides methods that CLI commands can call to interact with EKG backend
|
|
35
|
-
*/
|
|
36
|
-
export class CLIIntegrationService {
|
|
37
|
-
constructor() {
|
|
38
|
-
this.config = {
|
|
39
|
-
ingestionServiceUrl: process.env.INGESTION_SERVICE_URL || 'http://localhost:3000',
|
|
40
|
-
queryServiceUrl: process.env.QUERY_SERVICE_URL || 'http://localhost:4000',
|
|
41
|
-
timeout: parseInt(process.env.REQUEST_TIMEOUT || '30000'),
|
|
42
|
-
retries: parseInt(process.env.REQUEST_RETRIES || '3')
|
|
43
|
-
};
|
|
44
|
-
this.git = simpleGit();
|
|
45
|
-
logger.info('CLI Integration Service initialized', this.config);
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Index repository for EKG - equivalent to `codeflow index`
|
|
49
|
-
*
|
|
50
|
-
* Sends repository URL to EKG Ingestion Service for analysis and graph population
|
|
51
|
-
*/
|
|
52
|
-
async indexRepository(options = {}) {
|
|
53
|
-
const startTime = Date.now();
|
|
54
|
-
try {
|
|
55
|
-
// Get repository information
|
|
56
|
-
const repoInfo = await this.getRepositoryInfo();
|
|
57
|
-
if (options.dryRun) {
|
|
58
|
-
logger.info('Dry run mode - would index repository', repoInfo);
|
|
59
|
-
const filesToIndex = await this.getIndexableFiles(repoInfo.repositoryPath);
|
|
60
|
-
return {
|
|
61
|
-
success: true,
|
|
62
|
-
message: `Dry run: Would index ${filesToIndex.length} files from ${repoInfo.fullName}`,
|
|
63
|
-
stats: {
|
|
64
|
-
indexedFiles: filesToIndex.length,
|
|
65
|
-
analysisTime: Date.now() - startTime,
|
|
66
|
-
webhookAccepted: true // Would be accepted
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
// Calculate repository ID for tracking
|
|
71
|
-
const repositoryId = this.generateRepositoryId(repoInfo.fullName);
|
|
72
|
-
logger.info('Indexing repository via EKG backend', {
|
|
73
|
-
repositoryId,
|
|
74
|
-
fullName: repoInfo.fullName,
|
|
75
|
-
url: repoInfo.cloneUrl
|
|
76
|
-
});
|
|
77
|
-
// Send webhook payload to EKG Ingestion Service
|
|
78
|
-
const webhookPayload = {
|
|
79
|
-
action: 'repository.indexed',
|
|
80
|
-
repository: {
|
|
81
|
-
id: repositoryId,
|
|
82
|
-
name: repoInfo.name,
|
|
83
|
-
full_name: repoInfo.fullName,
|
|
84
|
-
clone_url: repoInfo.cloneUrl,
|
|
85
|
-
html_url: repoInfo.htmlUrl,
|
|
86
|
-
private: repoInfo.isPrivate
|
|
87
|
-
},
|
|
88
|
-
sender: {
|
|
89
|
-
login: this.getCurrentUser(),
|
|
90
|
-
type: 'User'
|
|
91
|
-
},
|
|
92
|
-
installation: {
|
|
93
|
-
id: 'cli-integration',
|
|
94
|
-
node_id: 'cli-integration-node'
|
|
95
|
-
},
|
|
96
|
-
// Add PRISM analysis request
|
|
97
|
-
prism: {
|
|
98
|
-
includePatterns: true,
|
|
99
|
-
includeDependencies: true,
|
|
100
|
-
includeMetrics: true
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
const response = await this.makeBackendRequest(`${this.config.ingestionServiceUrl}/webhooks/github`, webhookPayload, {
|
|
104
|
-
'X-GitHub-Event': 'repository.indexed',
|
|
105
|
-
'X-GitHub-Delivery': `delivery-cli-${repositoryId}-${Date.now()}`,
|
|
106
|
-
'X-Request-ID': `req-cli-index-${repositoryId}`,
|
|
107
|
-
'Content-Type': 'application/json'
|
|
108
|
-
});
|
|
109
|
-
const analysisTime = Date.now() - startTime;
|
|
110
|
-
logger.info('Repository indexing initiated', {
|
|
111
|
-
repositoryId,
|
|
112
|
-
responseStatus: response.status,
|
|
113
|
-
analysisTime
|
|
114
|
-
});
|
|
115
|
-
return {
|
|
116
|
-
success: true,
|
|
117
|
-
repositoryId,
|
|
118
|
-
message: `Repository ${repoInfo.fullName} submitted for EKG analysis`,
|
|
119
|
-
stats: {
|
|
120
|
-
indexedFiles: 0, // Will be populated by backend
|
|
121
|
-
analysisTime,
|
|
122
|
-
webhookAccepted: response.status === 200
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
const errorMessage = this.formatError(error);
|
|
128
|
-
logger.error('Repository indexing failed', { error: errorMessage });
|
|
129
|
-
return {
|
|
130
|
-
success: false,
|
|
131
|
-
message: `Repository indexing failed: ${errorMessage}`,
|
|
132
|
-
stats: {
|
|
133
|
-
indexedFiles: 0,
|
|
134
|
-
analysisTime: Date.now() - startTime,
|
|
135
|
-
webhookAccepted: false
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Analyze code diff with EKG context enhancement
|
|
142
|
-
*
|
|
143
|
-
* Sends diff to Query Service for EKG-enhanced analysis instead of local RAG
|
|
144
|
-
*/
|
|
145
|
-
async analyzeDiff(diffContent, options = {}) {
|
|
146
|
-
const startTime = Date.now();
|
|
147
|
-
try {
|
|
148
|
-
if (!diffContent.trim()) {
|
|
149
|
-
return {
|
|
150
|
-
success: true,
|
|
151
|
-
analysis: { type: 'no-changes', message: 'No changes to analyze' },
|
|
152
|
-
message: 'No changes to analyze'
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
if (options.legacy) {
|
|
156
|
-
// Fallback to local analysis (would integrate with existing agents)
|
|
157
|
-
logger.warn('Legacy mode requested - falling back to local analysis');
|
|
158
|
-
return {
|
|
159
|
-
success: false,
|
|
160
|
-
analysis: null,
|
|
161
|
-
message: 'Legacy mode not yet implemented with EKG integration'
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
logger.info('Analyzing diff with EKG context enhancement', {
|
|
165
|
-
diffSize: diffContent.length,
|
|
166
|
-
lines: diffContent.split('\n').length
|
|
167
|
-
});
|
|
168
|
-
// Analyze diff and extract context
|
|
169
|
-
const diffAnalysis = this.analyzeDiffContent(diffContent);
|
|
170
|
-
if (diffAnalysis.files.length === 0) {
|
|
171
|
-
return {
|
|
172
|
-
success: true,
|
|
173
|
-
analysis: { type: 'no-relevant-changes', message: 'No code changes detected' },
|
|
174
|
-
message: 'No code changes detected in diff'
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
// Query EKG for context on affected files
|
|
178
|
-
const ekgContext = await this.getEKGContext(diffAnalysis);
|
|
179
|
-
// Generate enhanced analysis with EKG data
|
|
180
|
-
const enhancedAnalysis = await this.generateEKGEnhancedAnalysis(diffAnalysis, ekgContext);
|
|
181
|
-
const analysisTime = Date.now() - startTime;
|
|
182
|
-
logger.info('Diff analysis completed with EKG enhancement', {
|
|
183
|
-
affectedFiles: diffAnalysis.files.length,
|
|
184
|
-
ekgQueries: ekgContext.queriesMade,
|
|
185
|
-
similarReposFound: ekgContext.similarRepositories?.length || 0,
|
|
186
|
-
analysisTime
|
|
187
|
-
});
|
|
188
|
-
return {
|
|
189
|
-
success: true,
|
|
190
|
-
analysis: enhancedAnalysis,
|
|
191
|
-
message: 'Diff analyzed with EKG context enhancement',
|
|
192
|
-
stats: {
|
|
193
|
-
ekg_queries: ekgContext.queriesMade,
|
|
194
|
-
similar_repos_found: ekgContext.similarRepositories?.length || 0,
|
|
195
|
-
analysis_time: analysisTime
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
catch (error) {
|
|
200
|
-
const errorMessage = this.formatError(error);
|
|
201
|
-
logger.error('Diff analysis failed', { error: errorMessage });
|
|
202
|
-
return {
|
|
203
|
-
success: false,
|
|
204
|
-
analysis: null,
|
|
205
|
-
message: `Diff analysis failed: ${errorMessage}`,
|
|
206
|
-
stats: {
|
|
207
|
-
ekg_queries: 0,
|
|
208
|
-
similar_repos_found: 0,
|
|
209
|
-
analysis_time: Date.now() - startTime
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Analyze diff content and extract structured information
|
|
216
|
-
*/
|
|
217
|
-
analyzeDiffContent(diffContent) {
|
|
218
|
-
const files = [];
|
|
219
|
-
const lines = diffContent.split('\n');
|
|
220
|
-
let currentFile = null;
|
|
221
|
-
let totalAdditions = 0;
|
|
222
|
-
let totalDeletions = 0;
|
|
223
|
-
for (const line of lines) {
|
|
224
|
-
if (line.startsWith('diff --git')) {
|
|
225
|
-
// Save previous file if exists
|
|
226
|
-
if (currentFile) {
|
|
227
|
-
files.push(currentFile);
|
|
228
|
-
}
|
|
229
|
-
// Extract file path
|
|
230
|
-
const match = line.match(/diff --git a\/(.+) b\/(.+)/);
|
|
231
|
-
if (match && match[2]) {
|
|
232
|
-
currentFile = {
|
|
233
|
-
path: match[2],
|
|
234
|
-
additions: 0,
|
|
235
|
-
deletions: 0,
|
|
236
|
-
isNew: false,
|
|
237
|
-
language: this.detectLanguage(match[2])
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else if (line.startsWith('new file mode')) {
|
|
242
|
-
if (currentFile) {
|
|
243
|
-
currentFile.isNew = true;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
247
|
-
if (currentFile) {
|
|
248
|
-
currentFile.additions++;
|
|
249
|
-
totalAdditions++;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
253
|
-
if (currentFile) {
|
|
254
|
-
currentFile.deletions++;
|
|
255
|
-
totalDeletions++;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
// Push final file
|
|
260
|
-
if (currentFile) {
|
|
261
|
-
files.push(currentFile);
|
|
262
|
-
}
|
|
263
|
-
const summary = `Modified ${files.length} files: +${totalAdditions} -${totalDeletions}`;
|
|
264
|
-
return {
|
|
265
|
-
files,
|
|
266
|
-
totalAdditions,
|
|
267
|
-
totalDeletions,
|
|
268
|
-
summary
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Query EKG for context on affected files
|
|
273
|
-
*/
|
|
274
|
-
async getEKGContext(diffAnalysis) {
|
|
275
|
-
let queriesMade = 0;
|
|
276
|
-
try {
|
|
277
|
-
// Get current repository information
|
|
278
|
-
const repoInfo = await this.getRepositoryInfo();
|
|
279
|
-
const repositoryId = this.generateRepositoryId(repoInfo.fullName);
|
|
280
|
-
// Query repository intelligence if repository exists in EKG
|
|
281
|
-
let repositoryIntelligence = null;
|
|
282
|
-
try {
|
|
283
|
-
const response = await this.makeGraphQLRequest(`
|
|
284
|
-
query GetRepositoryIntelligence($repoId: ID!) {
|
|
285
|
-
repositoryIntelligence(repositoryId: $repoId) {
|
|
286
|
-
repository {
|
|
287
|
-
id name fullName language
|
|
288
|
-
}
|
|
289
|
-
patterns {
|
|
290
|
-
name type confidence category
|
|
291
|
-
}
|
|
292
|
-
dependencies {
|
|
293
|
-
dependencyType currentVersion confidence
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
`, { repoId: repositoryId });
|
|
298
|
-
repositoryIntelligence = response.data?.repositoryIntelligence;
|
|
299
|
-
queriesMade++;
|
|
300
|
-
}
|
|
301
|
-
catch (error) {
|
|
302
|
-
logger.warn('Repository not found in EKG, continuing analysis', { repositoryId });
|
|
303
|
-
}
|
|
304
|
-
// Find similar repositories and patterns for context
|
|
305
|
-
let similarRepositories = [];
|
|
306
|
-
try {
|
|
307
|
-
const response = await this.makeGraphQLRequest(`
|
|
308
|
-
query FindSimilarRepositories($repoId: ID!, $limit: Int) {
|
|
309
|
-
similarRepositories(repositoryId: $repoId, limit: $limit) {
|
|
310
|
-
repository { name fullName language }
|
|
311
|
-
similarityScore reasons
|
|
312
|
-
sharedPatterns sizeComparison
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
`, { repoId: repositoryId, limit: 5 });
|
|
316
|
-
similarRepositories = response.data?.similarRepositories || [];
|
|
317
|
-
queriesMade++;
|
|
318
|
-
}
|
|
319
|
-
catch (error) {
|
|
320
|
-
logger.warn('Could not fetch similar repositories', { error: this.formatError(error) });
|
|
321
|
-
}
|
|
322
|
-
// Get enterprise-wide patterns that might be relevant
|
|
323
|
-
let patterns = [];
|
|
324
|
-
try {
|
|
325
|
-
const languages = [...new Set(diffAnalysis.files.map((f) => f.language))];
|
|
326
|
-
const response = await this.makeGraphQLRequest(`
|
|
327
|
-
query GetRelevantPatterns($language: String, $limit: Int) {
|
|
328
|
-
patterns(language: $language, minConfidence: 0.7, limit: $limit) {
|
|
329
|
-
name type category confidence observationCount
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
`, { language: languages[0], limit: 10 });
|
|
333
|
-
patterns = response.data?.patterns || [];
|
|
334
|
-
queriesMade++;
|
|
335
|
-
}
|
|
336
|
-
catch (error) {
|
|
337
|
-
logger.warn('Could not fetch patterns', { error: this.formatError(error) });
|
|
338
|
-
}
|
|
339
|
-
return {
|
|
340
|
-
queriesMade,
|
|
341
|
-
repositoryIntelligence,
|
|
342
|
-
similarRepositories,
|
|
343
|
-
patterns
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
catch (error) {
|
|
347
|
-
logger.error('EKG context retrieval failed', { error: this.formatError(error) });
|
|
348
|
-
return {
|
|
349
|
-
queriesMade,
|
|
350
|
-
similarRepositories: [],
|
|
351
|
-
patterns: []
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
/**
|
|
356
|
-
* Generate enhanced analysis using EKG context
|
|
357
|
-
*/
|
|
358
|
-
async generateEKGEnhancedAnalysis(diffAnalysis, ekgContext) {
|
|
359
|
-
// Analyze changes with EKG context
|
|
360
|
-
const issues = [];
|
|
361
|
-
const recommendations = [];
|
|
362
|
-
// Check against existing patterns
|
|
363
|
-
if (ekgContext.patterns && ekgContext.patterns.length > 0) {
|
|
364
|
-
for (const file of diffAnalysis.files) {
|
|
365
|
-
const relevantPatterns = ekgContext.patterns.filter((p) => p.type === 'security' || p.type === 'architecture');
|
|
366
|
-
if (relevantPatterns.length > 0) {
|
|
367
|
-
recommendations.push({
|
|
368
|
-
type: 'ekg_pattern_alignment',
|
|
369
|
-
description: `File ${file.path} modified - consider these established patterns: ${relevantPatterns.map((p) => p.name).join(', ')}`,
|
|
370
|
-
severity: 'info',
|
|
371
|
-
file: file.path
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
// Compare against similar repositories
|
|
377
|
-
if (ekgContext.similarRepositories && ekgContext.similarRepositories.length > 0) {
|
|
378
|
-
const similarRepoNames = ekgContext.similarRepositories.map((sr) => sr.repository.fullName);
|
|
379
|
-
recommendations.push({
|
|
380
|
-
type: 'similar_repositories',
|
|
381
|
-
description: `Changes similar to patterns seen in: ${similarRepoNames.slice(0, 3).join(', ')}`,
|
|
382
|
-
severity: 'info'
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
// Add repository-specific context if available
|
|
386
|
-
if (ekgContext.repositoryIntelligence) {
|
|
387
|
-
const repo = ekgContext.repositoryIntelligence.repository;
|
|
388
|
-
issues.push({
|
|
389
|
-
type: 'repository_context',
|
|
390
|
-
description: `Analyzing changes in repository ${repo.fullName} (${repo.language})`,
|
|
391
|
-
severity: 'info'
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
summary: {
|
|
396
|
-
totalFiles: diffAnalysis.files.length,
|
|
397
|
-
totalAdditions: diffAnalysis.totalAdditions,
|
|
398
|
-
totalDeletions: diffAnalysis.totalDeletions,
|
|
399
|
-
ekgEnhanced: true
|
|
400
|
-
},
|
|
401
|
-
files: diffAnalysis.files.map((file) => ({
|
|
402
|
-
path: file.path,
|
|
403
|
-
language: file.language,
|
|
404
|
-
additions: file.additions,
|
|
405
|
-
deletions: file.deletions,
|
|
406
|
-
isNew: file.isNew
|
|
407
|
-
})),
|
|
408
|
-
issues,
|
|
409
|
-
recommendations,
|
|
410
|
-
ekg_context: {
|
|
411
|
-
patterns_analyzed: ekgContext.patterns?.length || 0,
|
|
412
|
-
similar_repositories_found: ekgContext.similarRepositories?.length || 0,
|
|
413
|
-
repository_known: !!ekgContext.repositoryIntelligence
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
/**
|
|
418
|
-
* Get current repository information
|
|
419
|
-
*/
|
|
420
|
-
async getRepositoryInfo() {
|
|
421
|
-
try {
|
|
422
|
-
const remotes = await this.git.getRemotes(true);
|
|
423
|
-
const originRemote = remotes.find((r) => r.name === 'origin');
|
|
424
|
-
if (!originRemote) {
|
|
425
|
-
throw new Error('No origin remote found');
|
|
426
|
-
}
|
|
427
|
-
// Parse GitHub URL
|
|
428
|
-
const urlMatch = originRemote.refs.fetch.match(/github\.com[:/](.+?)(\.git)?$/);
|
|
429
|
-
if (!urlMatch) {
|
|
430
|
-
throw new Error('Remote URL is not a GitHub repository');
|
|
431
|
-
}
|
|
432
|
-
const fullName = urlMatch[1];
|
|
433
|
-
if (!fullName) {
|
|
434
|
-
throw new Error('Could not extract repository name from remote URL');
|
|
435
|
-
}
|
|
436
|
-
const repoParts = fullName.split('/');
|
|
437
|
-
const repo = repoParts[1];
|
|
438
|
-
if (!repo) {
|
|
439
|
-
throw new Error('Could not extract repository name from full name');
|
|
440
|
-
}
|
|
441
|
-
return {
|
|
442
|
-
name: repo,
|
|
443
|
-
fullName,
|
|
444
|
-
repositoryPath: process.cwd(),
|
|
445
|
-
cloneUrl: `https://github.com/${fullName}.git`,
|
|
446
|
-
htmlUrl: `https://github.com/${fullName}`,
|
|
447
|
-
isPrivate: false // Assume public unless told otherwise
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
catch (error) {
|
|
451
|
-
throw new Error(`Could not determine repository information: ${this.formatError(error)}`);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
/**
|
|
455
|
-
* Get list of files that would be indexed
|
|
456
|
-
*/
|
|
457
|
-
async getIndexableFiles(repoPath) {
|
|
458
|
-
// This mimics the logic from the original RAG indexer
|
|
459
|
-
const keyFileExtensions = [
|
|
460
|
-
'.js', '.ts', '.tsx', '.jsx', '.py', '.java', '.cpp', '.c', '.cs',
|
|
461
|
-
'.php', '.rb', '.go', '.rs', '.swift', '.kt', '.scala', '.clj'
|
|
462
|
-
];
|
|
463
|
-
try {
|
|
464
|
-
const files = [];
|
|
465
|
-
const walkDirectory = (dir, relativePath = '') => {
|
|
466
|
-
const items = fs.readdirSync(dir);
|
|
467
|
-
for (const item of items) {
|
|
468
|
-
const fullPath = path.join(dir, item);
|
|
469
|
-
const relativeFilePath = path.join(relativePath, item);
|
|
470
|
-
const stat = fs.statSync(fullPath);
|
|
471
|
-
// Skip common exclude patterns
|
|
472
|
-
if (item.startsWith('.') || item === 'node_modules' || item === 'dist' ||
|
|
473
|
-
item === 'build' || item === 'target' || item === '.git') {
|
|
474
|
-
continue;
|
|
475
|
-
}
|
|
476
|
-
if (stat.isDirectory()) {
|
|
477
|
-
walkDirectory(fullPath, relativeFilePath);
|
|
478
|
-
}
|
|
479
|
-
else if (stat.isFile()) {
|
|
480
|
-
const ext = path.extname(item);
|
|
481
|
-
if (keyFileExtensions.includes(ext)) {
|
|
482
|
-
files.push(relativeFilePath);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
walkDirectory(repoPath);
|
|
488
|
-
return files;
|
|
489
|
-
}
|
|
490
|
-
catch (error) {
|
|
491
|
-
logger.warn('Could not scan repository files', { error: this.formatError(error) });
|
|
492
|
-
return [];
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Make HTTP request to backend service with retry logic
|
|
497
|
-
*/
|
|
498
|
-
async makeBackendRequest(url, data, headers = {}) {
|
|
499
|
-
let lastError;
|
|
500
|
-
for (let attempt = 1; attempt <= this.config.retries; attempt++) {
|
|
501
|
-
try {
|
|
502
|
-
const response = await axios.post(url, data, {
|
|
503
|
-
headers: {
|
|
504
|
-
...headers,
|
|
505
|
-
'X-Attempt': attempt.toString()
|
|
506
|
-
},
|
|
507
|
-
timeout: this.config.timeout
|
|
508
|
-
});
|
|
509
|
-
return response;
|
|
510
|
-
}
|
|
511
|
-
catch (error) {
|
|
512
|
-
lastError = error;
|
|
513
|
-
logger.warn(`Backend request attempt ${attempt} failed`, {
|
|
514
|
-
url,
|
|
515
|
-
attempt,
|
|
516
|
-
error: this.formatError(error)
|
|
517
|
-
});
|
|
518
|
-
if (attempt < this.config.retries) {
|
|
519
|
-
// Wait before retry (exponential backoff)
|
|
520
|
-
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
throw lastError;
|
|
525
|
-
}
|
|
526
|
-
/**
|
|
527
|
-
* Make GraphQL request to Query Service
|
|
528
|
-
*/
|
|
529
|
-
async makeGraphQLRequest(query, variables = {}) {
|
|
530
|
-
return this.makeBackendRequest(`${this.config.queryServiceUrl}/graphql`, { query, variables }, { 'Content-Type': 'application/json' });
|
|
531
|
-
}
|
|
532
|
-
/**
|
|
533
|
-
* Generate repository ID (similar to ingestion service)
|
|
534
|
-
*/
|
|
535
|
-
generateRepositoryId(fullName) {
|
|
536
|
-
return `${fullName.replace('/', '-')}-${Date.now().toString(36)}`;
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Get current user information
|
|
540
|
-
*/
|
|
541
|
-
getCurrentUser() {
|
|
542
|
-
return process.env.USER || process.env.USERNAME || 'anonymous';
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* Detect language from file extension
|
|
546
|
-
*/
|
|
547
|
-
detectLanguage(filePath) {
|
|
548
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
549
|
-
const languageMap = {
|
|
550
|
-
'.js': 'javascript',
|
|
551
|
-
'.ts': 'typescript',
|
|
552
|
-
'.tsx': 'typescript',
|
|
553
|
-
'.jsx': 'javascript',
|
|
554
|
-
'.py': 'python',
|
|
555
|
-
'.java': 'java',
|
|
556
|
-
'.cpp': 'cpp',
|
|
557
|
-
'.c': 'c',
|
|
558
|
-
'.cs': 'csharp',
|
|
559
|
-
'.php': 'php',
|
|
560
|
-
'.rb': 'ruby',
|
|
561
|
-
'.go': 'go',
|
|
562
|
-
'.rs': 'rust',
|
|
563
|
-
'.swift': 'swift',
|
|
564
|
-
'.kt': 'kotlin',
|
|
565
|
-
'.scala': 'scala'
|
|
566
|
-
};
|
|
567
|
-
return languageMap[ext] || 'unknown';
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Format error for logging and display
|
|
571
|
-
*/
|
|
572
|
-
formatError(error) {
|
|
573
|
-
if (axios.isAxiosError(error)) {
|
|
574
|
-
return `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`;
|
|
575
|
-
}
|
|
576
|
-
return error.message || 'Unknown error';
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
// Export singleton instance for CLI use
|
|
580
|
-
export const cliIntegrationService = new CLIIntegrationService();
|
|
581
|
-
// Export main methods for backward compatibility
|
|
582
|
-
export const indexProject = cliIntegrationService.indexRepository.bind(cliIntegrationService);
|
|
583
|
-
export const analyzeDiff = cliIntegrationService.analyzeDiff.bind(cliIntegrationService);
|
|
584
|
-
// Default export
|
|
585
|
-
export default cliIntegrationService;
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { PipelineConfig } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Pre-built pipeline configurations for common CI/CD scenarios
|
|
4
|
-
* These can be used as templates or loaded directly for simulation
|
|
5
|
-
*/
|
|
6
|
-
export declare class PipelineConfigManager {
|
|
7
|
-
/**
|
|
8
|
-
* Get a basic Node.js CI/CD pipeline configuration
|
|
9
|
-
*/
|
|
10
|
-
static getNodeJSPipeline(): PipelineConfig;
|
|
11
|
-
/**
|
|
12
|
-
* Get a comprehensive enterprise pipeline with advanced features
|
|
13
|
-
*/
|
|
14
|
-
static getEnterprisePipeline(): PipelineConfig;
|
|
15
|
-
/**
|
|
16
|
-
* Get a fast pipeline for development/testing scenarios
|
|
17
|
-
*/
|
|
18
|
-
static getFastPipeline(): PipelineConfig;
|
|
19
|
-
/**
|
|
20
|
-
* Get a chaotic pipeline for testing error handling
|
|
21
|
-
*/
|
|
22
|
-
static getChaoticPipeline(): PipelineConfig;
|
|
23
|
-
/**
|
|
24
|
-
* Get a microservices pipeline with parallel execution
|
|
25
|
-
*/
|
|
26
|
-
static getMicroservicesPipeline(): PipelineConfig;
|
|
27
|
-
private static createTriggerStage;
|
|
28
|
-
private static createAiReviewStage;
|
|
29
|
-
private static createUnitTestStage;
|
|
30
|
-
private static createDockerBuildStage;
|
|
31
|
-
private static createDeployStage;
|
|
32
|
-
private static createSecurityScanStage;
|
|
33
|
-
private static createIntegrationTestStage;
|
|
34
|
-
private static createPerformanceTestStage;
|
|
35
|
-
private static createSecurityAuditStage;
|
|
36
|
-
private static createDeployStagingStage;
|
|
37
|
-
private static createE2eTestStage;
|
|
38
|
-
private static createDeployProductionStage;
|
|
39
|
-
/**
|
|
40
|
-
* Load a pipeline configuration from JSON file
|
|
41
|
-
*/
|
|
42
|
-
static loadFromFile(filePath: string): Promise<PipelineConfig>;
|
|
43
|
-
/**
|
|
44
|
-
* Save a pipeline configuration to JSON file
|
|
45
|
-
*/
|
|
46
|
-
static saveToFile(config: PipelineConfig, filePath: string): Promise<void>;
|
|
47
|
-
/**
|
|
48
|
-
* Get all available pipeline templates
|
|
49
|
-
*/
|
|
50
|
-
static getAvailableTemplates(): Array<{
|
|
51
|
-
id: string;
|
|
52
|
-
name: string;
|
|
53
|
-
description: string;
|
|
54
|
-
category: string;
|
|
55
|
-
}>;
|
|
56
|
-
/**
|
|
57
|
-
* Get a pipeline configuration by ID
|
|
58
|
-
*/
|
|
59
|
-
static getPipelineById(id: string): PipelineConfig | null;
|
|
60
|
-
}
|