codeflow-hook 1.3.0 → 2.0.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.
@@ -8,9 +8,11 @@ import fs from 'fs';
8
8
  import path from 'path';
9
9
  import os from 'os'; // Make sure os is imported
10
10
  import readline from 'readline';
11
- import { indexProject } from './rag.js';
12
11
  import { orchestrateReview } from './agents.js';
13
12
 
13
+ // Import CLI integration service
14
+ import { indexProject, analyzeDiff } from '../lib/cli-integration/dist/index.js';
15
+
14
16
  // Export for use in agents module
15
17
  export { callAIProvider };
16
18
 
@@ -18,8 +20,8 @@ const program = new Command();
18
20
 
19
21
  program
20
22
  .name('codeflow-hook')
21
- .description('Interactive CI/CD simulator and AI-powered code reviewer')
22
- .version('1.0.0');
23
+ .description('Interactive CI/CD simulator and AI-powered code reviewer with EKG backend integration')
24
+ .version('4.0.0');
23
25
 
24
26
  // Configure AI provider settings
25
27
  program
@@ -199,41 +201,34 @@ program
199
201
  }
200
202
  });
201
203
 
202
- // Index project knowledge base for RAG
204
+ // Index repository via EKG Ingestion Service (Phase 4)
203
205
  program
204
206
  .command('index')
205
- .description('Index project files for Retrieval-Augmented Generation (RAG)')
206
- .option('-d, --dry-run', 'Show what files would be indexed without actually indexing')
207
+ .description('Index repository via EKG Ingestion Service')
208
+ .option('-d, --dry-run', 'Show what would be indexed without actually indexing')
207
209
  .action(async (options) => {
208
210
  try {
209
- const configPath = path.join(os.homedir(), '.codeflow-hook', 'config.json');
211
+ const spinner = ora('Submitting repository for EKG analysis...').start();
210
212
 
211
- if (!fs.existsSync(configPath)) {
212
- console.log(chalk.red('No configuration found. Run: codeflow-hook config -k <api-key>'));
213
- process.exit(1);
214
- }
213
+ const result = await indexProject({
214
+ dryRun: options.dryRun || false
215
+ });
215
216
 
216
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
217
- const spinner = ora('Indexing project knowledge base...').start();
217
+ spinner.succeed('Repository indexing initiated');
218
+ console.log(chalk.green(`✅ ${result.message}`));
218
219
 
219
- if (options.dryRun) {
220
- spinner.stop();
221
- console.log(chalk.blue('🔍 Dry run mode - files to be indexed:'));
222
- const { findKeyFiles } = await import('./rag.js');
223
- const keyFiles = await findKeyFiles(process.cwd());
224
- keyFiles.forEach(file => console.log(chalk.gray(` - ${file}`)));
225
- console.log(chalk.green(`📊 Total files to index: ${keyFiles.length}`));
226
- return;
220
+ if (result.repositoryId) {
221
+ console.log(chalk.blue(`📋 Repository ID: ${result.repositoryId}`));
227
222
  }
228
223
 
229
- const result = await indexProject(config);
224
+ if (result.stats) {
225
+ console.log(chalk.gray(`📊 Stats: ${JSON.stringify(result.stats, null, 2)}`));
226
+ }
230
227
 
231
- spinner.succeed('Knowledge base indexing complete');
232
- console.log(chalk.green(`✅ Indexed ${result.indexedFiles} files with ${result.totalChunks} chunks`));
233
- console.log(chalk.blue('📁 Knowledge base stored in: .codeflow/index/'));
228
+ console.log(chalk.cyan('🔗 Repository submitted to EKG Ingestion Service for analysis'));
234
229
 
235
230
  } catch (error) {
236
- console.error(chalk.red(`❌ Indexing failed: ${error.message}`));
231
+ console.log(chalk.red(`❌ Indexing failed: ${error.message}`));
237
232
  process.exit(1);
238
233
  }
239
234
  });
@@ -298,13 +293,12 @@ exit 0
298
293
  }
299
294
  });
300
295
 
301
- // Analyze diff with specialized AI agents
296
+ // Analyze diff with EKG Query Service context enhancement (Phase 4)
302
297
  program
303
298
  .command('analyze-diff')
304
- .description('Analyze git diff using specialized AI agents (RAG-enhanced)')
299
+ .description('Analyze git diff using EKG context enhancement')
305
300
  .argument('[diff]', 'Git diff content')
306
- .option('--legacy', 'Use legacy monolithic analysis instead of agentic workflow')
307
- .option('--no-rag', 'Disable RAG context retrieval')
301
+ .option('--legacy', 'Use legacy analysis instead of EKG-enhanced analysis')
308
302
  .action(async (diff, options) => {
309
303
  try {
310
304
  // Read diff content from stdin or argument
@@ -317,63 +311,120 @@ program
317
311
  diffContent = Buffer.concat(chunks).toString('utf8');
318
312
  }
319
313
 
320
- const configPath = path.join(os.homedir(), '.codeflow-hook', 'config.json');
321
-
322
- if (!fs.existsSync(configPath)) {
323
- console.log(chalk.red('No configuration found. Run: codeflow-hook config -k <api-key>'));
324
- process.exit(1);
325
- }
326
-
327
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
328
-
329
314
  if (diffContent.trim() === '') {
330
315
  console.log(chalk.gray('â„šī¸ No changes to analyze'));
331
316
  return;
332
317
  }
333
318
 
334
- // Legacy mode: use original monolithic analysis
335
- if (options.legacy) {
336
- const spinner = ora(`Analyzing code with ${config.provider}...`).start();
337
- const prompt = generateCodeReviewPrompt(diffContent);
319
+ console.log(chalk.blue('đŸ”Ŧ Analyzing diff with EKG context enhancement...'));
338
320
 
339
- let result;
340
- try {
341
- result = await callAIProvider(config, prompt);
342
- } catch (error) {
343
- spinner.fail('Analysis failed');
344
- console.error(chalk.red(`AI API Error: ${error.message}`));
321
+ const result = await analyzeDiff(diffContent, {
322
+ legacy: options.legacy || false,
323
+ outputFormat: 'console'
324
+ });
325
+
326
+ if (result.success) {
327
+ console.log(chalk.green(`✅ ${result.message}`));
328
+ displayEKGAnalysisResults(result.analysis);
329
+
330
+ if (result.stats) {
331
+ console.log(chalk.gray(`📊 EKG Queries: ${result.stats.ekg_queries}`));
332
+ console.log(chalk.gray(`đŸ‘Ĩ Similar Repos Found: ${result.stats.similar_repos_found}`));
333
+ console.log(chalk.gray(`âąī¸ Analysis Time: ${result.stats.analysis_time}ms`));
334
+ }
335
+ } else {
336
+ console.log(chalk.red(`❌ Analysis failed: ${result.message}`));
337
+ process.exit(1);
338
+ }
339
+
340
+ } catch (error) {
341
+ console.log(chalk.red(`❌ Analysis error: ${error.message}`));
342
+ process.exit(1);
343
+ }
344
+ });
345
+
346
+ // Run pipeline simulation (Enhanced Frontend Integration)
347
+ program
348
+ .command('simulate')
349
+ .description('Run configurable CI/CD pipeline simulation')
350
+ .argument('[template]', 'Pipeline template ID (nodejs-basic, enterprise-advanced, fast-dev, chaotic-test, microservices-parallel)')
351
+ .option('-c, --config <file>', 'Custom pipeline configuration JSON file')
352
+ .option('-o, --output <file>', 'Output results to JSON file')
353
+ .option('-m, --mode <mode>', 'Simulation mode (REALISTIC, FAST, DETERMINISTIC, CHAOTIC)', 'REALISTIC')
354
+ .option('--commit-message <message>', 'Custom commit message for simulation')
355
+ .option('--json', 'Output results as JSON only')
356
+ .action(async (template, options) => {
357
+ try {
358
+ let pipelineConfig;
359
+
360
+ // Load configuration from file or template
361
+ if (options.config) {
362
+ console.log(chalk.blue(`📄 Loading pipeline config from: ${options.config}`));
363
+ const fs = await import('fs/promises');
364
+ const configData = await fs.readFile(options.config, 'utf8');
365
+ pipelineConfig = JSON.parse(configData);
366
+ } else {
367
+ const templateId = template || 'nodejs-basic';
368
+ console.log(chalk.blue(`đŸŽ¯ Using pipeline template: ${templateId}`));
369
+
370
+ // Import simulation engine and config manager dynamically
371
+ const { PipelineConfigManager } = await import('../lib/cli-integration/dist/pipelineConfigs.js');
372
+ pipelineConfig = PipelineConfigManager.getPipelineById(templateId);
373
+
374
+ if (!pipelineConfig) {
375
+ console.log(chalk.red(`❌ Unknown pipeline template: ${templateId}`));
376
+ console.log(chalk.gray('Available templates:'));
377
+ const templates = PipelineConfigManager.getAvailableTemplates();
378
+ templates.forEach(t => {
379
+ console.log(chalk.gray(` â€ĸ ${t.id}: ${t.description}`));
380
+ });
345
381
  process.exit(1);
346
382
  }
383
+ }
347
384
 
348
- spinner.succeed('Analysis complete');
349
- displayAnalysisResults(result);
350
- return;
385
+ // Apply customizations
386
+ if (options.mode) {
387
+ pipelineConfig.settings.mode = options.mode.toUpperCase();
351
388
  }
352
389
 
353
- // Agentic workflow mode
354
- const spinner = ora(`Running specialized code review agents...`).start();
390
+ if (options.commitMessage) {
391
+ pipelineConfig.environment = {
392
+ ...pipelineConfig.environment,
393
+ COMMIT_MESSAGE: options.commitMessage
394
+ };
395
+ }
355
396
 
356
- let results;
357
- try {
358
- if (options.rag === false) {
359
- // Force no RAG context
360
- const { orchestrateReviewWithoutRAG } = await import('./agents.js');
361
- results = await orchestrateReviewWithoutRAG(diffContent, config);
362
- } else {
363
- // Use RAG-enabled workflow
364
- results = await orchestrateReview(diffContent, config);
365
- }
366
- } catch (error) {
367
- spinner.fail('Analysis failed');
368
- console.error(chalk.red(`Agent analysis failed: ${error.message}`));
369
- process.exit(1);
397
+ console.log(chalk.blue(`🚀 Starting pipeline simulation: ${pipelineConfig.name}`));
398
+ console.log(chalk.gray(` Mode: ${pipelineConfig.settings.mode}`));
399
+ console.log(chalk.gray(` Stages: ${pipelineConfig.stages.length}`));
400
+
401
+ const spinner = ora('Running pipeline simulation...').start();
402
+
403
+ // Import and run simulation engine
404
+ const { simulationEngine } = await import('../lib/cli-integration/dist/simulationEngine.js');
405
+ const result = await simulationEngine.executePipeline(pipelineConfig);
406
+
407
+ spinner.succeed('Simulation completed');
408
+
409
+ // Display results
410
+ if (options.json) {
411
+ console.log(JSON.stringify(result, null, 2));
412
+ } else {
413
+ displaySimulationResults(result);
370
414
  }
371
415
 
372
- spinner.succeed('Agentic analysis complete');
373
- displayAgenticResults(results);
416
+ // Save to file if requested
417
+ if (options.output) {
418
+ const fs = await import('fs/promises');
419
+ await fs.writeFile(options.output, JSON.stringify(result, null, 2), 'utf8');
420
+ console.log(chalk.green(`💾 Results saved to: ${options.output}`));
421
+ }
422
+
423
+ // Exit with appropriate code
424
+ process.exit(result.status === 'success' ? 0 : 1);
374
425
 
375
426
  } catch (error) {
376
- console.log(chalk.red(`Configuration error: ${error.message}`));
427
+ console.log(chalk.red(`❌ Simulation failed: ${error.message}`));
377
428
  process.exit(1);
378
429
  }
379
430
  });
@@ -734,5 +785,163 @@ function getTypeIcon(type) {
734
785
  }
735
786
  }
736
787
 
788
+ // Display EKG-enhanced analysis results (Phase 4)
789
+ function displayEKGAnalysisResults(analysis) {
790
+ if (!analysis) {
791
+ console.log(chalk.yellow('âš ī¸ No analysis results available'));
792
+ return;
793
+ }
794
+
795
+ // Display summary
796
+ if (analysis.summary) {
797
+ console.log(chalk.blue('📊 Analysis Summary:'));
798
+ console.log(` 📁 Files modified: ${analysis.summary.totalFiles}`);
799
+ console.log(` ➕ Additions: ${analysis.summary.totalAdditions}`);
800
+ console.log(` ➖ Deletions: ${analysis.summary.totalDeletions}`);
801
+ console.log(` 🧠 EKG enhanced: ${analysis.summary.ekgEnhanced ? 'Yes' : 'No'}`);
802
+ console.log();
803
+ }
804
+
805
+ // Display EKG context information
806
+ if (analysis.ekg_context) {
807
+ console.log(chalk.blue('🧠 EKG Context:'));
808
+ console.log(` 📚 Patterns analyzed: ${analysis.ekg_context.patterns_analyzed || 0}`);
809
+ console.log(` đŸ‘Ĩ Similar repositories: ${analysis.ekg_context.similar_repositories_found || 0}`);
810
+ console.log(` 🔍 Repository known to EKG: ${analysis.ekg_context.repository_known ? 'Yes' : 'No'}`);
811
+ console.log();
812
+ }
813
+
814
+ // Display issues
815
+ if (analysis.issues && analysis.issues.length > 0) {
816
+ console.log(chalk.yellow('âš ī¸ Issues Found:'));
817
+ analysis.issues.forEach(issue => {
818
+ const severityColor = getSeverityColor(issue.severity);
819
+ const typeIcon = getTypeIcon(issue.type);
820
+ console.log(` ${severityColor}${typeIcon} ${issue.severity}: ${issue.description}`);
821
+ });
822
+ console.log();
823
+ }
824
+
825
+ // Display recommendations
826
+ if (analysis.recommendations && analysis.recommendations.length > 0) {
827
+ console.log(chalk.green('💡 Recommendations:'));
828
+ analysis.recommendations.forEach(rec => {
829
+ const severityColor = getSeverityColor(rec.severity);
830
+ console.log(` ${severityColor}â€ĸ ${rec.description}`);
831
+ if (rec.file) {
832
+ console.log(chalk.gray(` 📁 File: ${rec.file}`));
833
+ }
834
+ });
835
+ console.log();
836
+ }
837
+
838
+ // Display file details
839
+ if (analysis.files && analysis.files.length > 0) {
840
+ console.log(chalk.blue('📂 Files Changed:'));
841
+ analysis.files.forEach(file => {
842
+ const changeType = file.isNew ? 'NEW' : 'MODIFIED';
843
+ const changeColor = file.isNew ? chalk.green : chalk.blue;
844
+ console.log(`${changeColor} ${changeType} ${file.path} (${file.language})`);
845
+ console.log(chalk.gray(` +${file.additions} -${file.deletions} changes`));
846
+ });
847
+ console.log();
848
+ }
849
+ }
850
+
851
+ // Display pipeline simulation results
852
+ function displaySimulationResults(result) {
853
+ if (!result) {
854
+ console.log(chalk.yellow('âš ī¸ No simulation results available'));
855
+ return;
856
+ }
857
+
858
+ // Overall status
859
+ const statusIcon = result.status === 'success' ? '✅' :
860
+ result.status === 'failed' ? '❌' :
861
+ result.status === 'partial' ? 'âš ī¸' : 'â¸ī¸';
862
+ const statusColor = result.status === 'success' ? chalk.green :
863
+ result.status === 'failed' ? chalk.red :
864
+ result.status === 'partial' ? chalk.yellow : chalk.gray;
865
+
866
+ console.log(statusColor(`${statusIcon} Pipeline ${result.status.toUpperCase()}`));
867
+ console.log(chalk.blue(`📋 Execution ID: ${result.executionId}`));
868
+ console.log(chalk.gray(`âąī¸ Duration: ${(result.metrics.totalDuration / 1000).toFixed(2)}s`));
869
+ console.log();
870
+
871
+ // Pipeline metrics
872
+ console.log(chalk.blue('📊 Pipeline Metrics:'));
873
+ console.log(` 📊 Stages: ${result.metrics.successCount}/${result.metrics.stageCount} passed`);
874
+ if (result.metrics.failureCount > 0) {
875
+ console.log(chalk.red(` ❌ Failed: ${result.metrics.failureCount}`));
876
+ }
877
+ if (result.metrics.skippedCount > 0) {
878
+ console.log(chalk.yellow(` â­ī¸ Skipped: ${result.metrics.skippedCount}`));
879
+ }
880
+ console.log(` 📈 Avg Stage Duration: ${(result.metrics.averageStageDuration / 1000).toFixed(2)}s`);
881
+ if (result.metrics.bottleneckStage) {
882
+ console.log(chalk.yellow(` 🐌 Bottleneck: ${result.metrics.bottleneckStage}`));
883
+ }
884
+ console.log();
885
+
886
+ // Resource utilization
887
+ console.log(chalk.blue('đŸ’ģ Resource Utilization:'));
888
+ console.log(` đŸ–Ĩī¸ Avg CPU: ${result.metrics.resourceUtilization.avgCpu}%`);
889
+ console.log(` 🧠 Avg Memory: ${result.metrics.resourceUtilization.avgMemory}MB`);
890
+ console.log(` đŸ”Ĩ Peak CPU: ${result.metrics.resourceUtilization.peakCpu}%`);
891
+ console.log(` 💾 Peak Memory: ${result.metrics.resourceUtilization.peakMemory}MB`);
892
+ console.log();
893
+
894
+ // Stage details
895
+ console.log(chalk.blue('🔧 Stage Results:'));
896
+ result.stages.forEach(stage => {
897
+ const stageIcon = stage.status === 'SUCCESS' ? '✅' :
898
+ stage.status === 'FAILED' ? '❌' :
899
+ stage.status === 'SKIPPED' ? 'â­ī¸' : 'âŗ';
900
+ const stageColor = stage.status === 'SUCCESS' ? chalk.green :
901
+ stage.status === 'FAILED' ? chalk.red :
902
+ stage.status === 'SKIPPED' ? chalk.yellow : chalk.gray;
903
+
904
+ const duration = stage.duration ? `${(stage.duration / 1000).toFixed(2)}s` : 'N/A';
905
+ console.log(`${stageColor(` ${stageIcon} ${stage.id}: ${duration}`)}`);
906
+
907
+ // Show metrics if available
908
+ if (stage.metrics) {
909
+ console.log(chalk.gray(` CPU: ${stage.metrics.cpuUsage}%, Mem: ${stage.metrics.memoryUsage}MB`));
910
+ }
911
+
912
+ // Show errors if any
913
+ if (stage.errors && stage.errors.length > 0) {
914
+ stage.errors.forEach(error => {
915
+ console.log(chalk.red(` đŸ’Ĩ ${error.message}`));
916
+ });
917
+ }
918
+ });
919
+ console.log();
920
+
921
+ // Artifacts
922
+ if (result.artifacts && result.artifacts.length > 0) {
923
+ console.log(chalk.blue('đŸ“Ļ Generated Artifacts:'));
924
+ result.artifacts.forEach(artifact => {
925
+ console.log(` 📄 ${artifact.name} (${(artifact.size / 1024 / 1024).toFixed(2)}MB)`);
926
+ console.log(chalk.gray(` ${artifact.path}`));
927
+ });
928
+ console.log();
929
+ }
930
+
931
+ // Execution logs summary
932
+ if (result.logs && result.logs.length > 0) {
933
+ console.log(chalk.blue('📝 Execution Summary:'));
934
+ // Show last few log lines
935
+ const recentLogs = result.logs.slice(-5);
936
+ recentLogs.forEach(log => {
937
+ console.log(chalk.gray(` ${log}`));
938
+ });
939
+ if (result.logs.length > 5) {
940
+ console.log(chalk.gray(` ... and ${result.logs.length - 5} more log entries`));
941
+ }
942
+ console.log();
943
+ }
944
+ }
945
+
737
946
  // Make sure the final line uses parseAsync
738
947
  program.parseAsync(process.argv);
@@ -0,0 +1,128 @@
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
+ /**
13
+ * CLI Integration Service
14
+ * Provides methods that CLI commands can call to interact with EKG backend
15
+ */
16
+ export declare class CLIIntegrationService {
17
+ private config;
18
+ private git;
19
+ constructor();
20
+ /**
21
+ * Index repository for EKG - equivalent to `codeflow index`
22
+ *
23
+ * Sends repository URL to EKG Ingestion Service for analysis and graph population
24
+ */
25
+ indexRepository(options?: {
26
+ repositoryUrl?: string;
27
+ dryRun?: boolean;
28
+ }): Promise<{
29
+ success: boolean;
30
+ repositoryId?: string;
31
+ message: string;
32
+ stats?: {
33
+ indexedFiles: number;
34
+ analysisTime: number;
35
+ webhookAccepted: boolean;
36
+ };
37
+ }>;
38
+ /**
39
+ * Analyze code diff with EKG context enhancement
40
+ *
41
+ * Sends diff to Query Service for EKG-enhanced analysis instead of local RAG
42
+ */
43
+ analyzeDiff(diffContent: string, options?: {
44
+ legacy?: boolean;
45
+ outputFormat?: 'console' | 'json';
46
+ }): Promise<{
47
+ success: boolean;
48
+ analysis: any;
49
+ message: string;
50
+ stats?: {
51
+ ekg_queries: number;
52
+ similar_repos_found: number;
53
+ analysis_time: number;
54
+ };
55
+ }>;
56
+ /**
57
+ * Analyze diff content and extract structured information
58
+ */
59
+ private analyzeDiffContent;
60
+ /**
61
+ * Query EKG for context on affected files
62
+ */
63
+ private getEKGContext;
64
+ /**
65
+ * Generate enhanced analysis using EKG context
66
+ */
67
+ private generateEKGEnhancedAnalysis;
68
+ /**
69
+ * Get current repository information
70
+ */
71
+ private getRepositoryInfo;
72
+ /**
73
+ * Get list of files that would be indexed
74
+ */
75
+ private getIndexableFiles;
76
+ /**
77
+ * Make HTTP request to backend service with retry logic
78
+ */
79
+ private makeBackendRequest;
80
+ /**
81
+ * Make GraphQL request to Query Service
82
+ */
83
+ private makeGraphQLRequest;
84
+ /**
85
+ * Generate repository ID (similar to ingestion service)
86
+ */
87
+ private generateRepositoryId;
88
+ /**
89
+ * Get current user information
90
+ */
91
+ private getCurrentUser;
92
+ /**
93
+ * Detect language from file extension
94
+ */
95
+ private detectLanguage;
96
+ /**
97
+ * Format error for logging and display
98
+ */
99
+ private formatError;
100
+ }
101
+ export declare const cliIntegrationService: CLIIntegrationService;
102
+ export declare const indexProject: (options?: {
103
+ repositoryUrl?: string;
104
+ dryRun?: boolean;
105
+ }) => Promise<{
106
+ success: boolean;
107
+ repositoryId?: string;
108
+ message: string;
109
+ stats?: {
110
+ indexedFiles: number;
111
+ analysisTime: number;
112
+ webhookAccepted: boolean;
113
+ };
114
+ }>;
115
+ export declare const analyzeDiff: (diffContent: string, options?: {
116
+ legacy?: boolean;
117
+ outputFormat?: 'console' | 'json';
118
+ }) => Promise<{
119
+ success: boolean;
120
+ analysis: any;
121
+ message: string;
122
+ stats?: {
123
+ ekg_queries: number;
124
+ similar_repos_found: number;
125
+ analysis_time: number;
126
+ };
127
+ }>;
128
+ export default cliIntegrationService;