archicore 0.1.9 → 0.2.1

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.
@@ -444,7 +444,7 @@ async function handleIndexCommand() {
444
444
  async function handleAnalyzeCommand(args) {
445
445
  if (!state.projectId) {
446
446
  printError('No project selected');
447
- printInfo('Use /projects select <id> first');
447
+ printInfo('Use /index first');
448
448
  return;
449
449
  }
450
450
  const description = args.join(' ') || 'General analysis';
@@ -465,15 +465,20 @@ async function handleAnalyzeCommand(args) {
465
465
  const data = await response.json();
466
466
  spinner.succeed('Analysis complete');
467
467
  printSection('Impact Analysis');
468
- const impact = data.impact;
469
- // Affected components
470
- const affected = impact.affectedNodes || [];
468
+ // Handle various response formats
469
+ const impact = data.impact || data.result || data || {};
470
+ const affected = impact.affectedNodes || impact.affected || impact.nodes || [];
471
471
  console.log(` ${colors.highlight('Affected Components:')} ${affected.length}`);
472
+ if (affected.length === 0) {
473
+ printInfo('No components affected by this change');
474
+ showTokenUsage();
475
+ return;
476
+ }
472
477
  const byLevel = {
473
- critical: affected.filter((n) => n.impactLevel === 'critical'),
474
- high: affected.filter((n) => n.impactLevel === 'high'),
475
- medium: affected.filter((n) => n.impactLevel === 'medium'),
476
- low: affected.filter((n) => n.impactLevel === 'low'),
478
+ critical: affected.filter((n) => n.impactLevel === 'critical' || n.level === 'critical'),
479
+ high: affected.filter((n) => n.impactLevel === 'high' || n.level === 'high'),
480
+ medium: affected.filter((n) => n.impactLevel === 'medium' || n.level === 'medium'),
481
+ low: affected.filter((n) => n.impactLevel === 'low' || n.level === 'low'),
477
482
  };
478
483
  if (byLevel.critical.length > 0) {
479
484
  console.log(` ${colors.critical(`${icons.severityCritical} Critical: ${byLevel.critical.length}`)}`);
@@ -487,27 +492,47 @@ async function handleAnalyzeCommand(args) {
487
492
  if (byLevel.low.length > 0) {
488
493
  console.log(` ${colors.low(`${icons.severityLow} Low: ${byLevel.low.length}`)}`);
489
494
  }
495
+ // Show top affected files
496
+ if (affected.length > 0) {
497
+ console.log();
498
+ console.log(` ${colors.highlight('Top affected:')}`);
499
+ for (const node of affected.slice(0, 5)) {
500
+ const name = node.name || node.symbol || 'unknown';
501
+ const file = node.filePath || node.file || '';
502
+ const level = node.impactLevel || node.level || 'unknown';
503
+ const levelColor = level === 'critical' ? colors.critical :
504
+ level === 'high' ? colors.high :
505
+ level === 'medium' ? colors.medium : colors.muted;
506
+ console.log(` ${levelColor(`[${level}]`)} ${name}`);
507
+ if (file)
508
+ console.log(` ${colors.dim(file)}`);
509
+ }
510
+ }
490
511
  // Risks
491
- if (impact.risks?.length > 0) {
512
+ const risks = impact.risks || [];
513
+ if (risks.length > 0) {
492
514
  console.log();
493
515
  console.log(` ${colors.highlight('Risks:')}`);
494
- for (const risk of impact.risks.slice(0, 5)) {
495
- console.log(` ${colors.warning(icons.warning)} ${risk.description}`);
516
+ for (const risk of risks.slice(0, 5)) {
517
+ const desc = risk.description || risk.message || risk;
518
+ console.log(` ${colors.warning(icons.warning)} ${desc}`);
496
519
  }
497
520
  }
498
521
  // Recommendations
499
- if (impact.recommendations?.length > 0) {
522
+ const recommendations = impact.recommendations || [];
523
+ if (recommendations.length > 0) {
500
524
  console.log();
501
525
  console.log(` ${colors.highlight('Recommendations:')}`);
502
- for (const rec of impact.recommendations.slice(0, 3)) {
503
- console.log(` ${colors.info(icons.info)} ${rec.description}`);
526
+ for (const rec of recommendations.slice(0, 3)) {
527
+ const desc = rec.description || rec.message || rec;
528
+ console.log(` ${colors.info(icons.info)} ${desc}`);
504
529
  }
505
530
  }
506
531
  showTokenUsage();
507
532
  }
508
533
  catch (error) {
509
534
  spinner.fail('Analysis failed');
510
- throw error;
535
+ printFormattedError(error, { operation: 'Impact analysis' });
511
536
  }
512
537
  }
513
538
  async function handleSearchCommand(query) {
@@ -603,23 +628,33 @@ async function handleDeadCodeCommand() {
603
628
  if (!response.ok)
604
629
  throw new Error('Analysis failed');
605
630
  const data = await response.json();
606
- const result = data.deadCode;
631
+ // Handle various response formats
632
+ const result = data.deadCode || data.result || data || {};
607
633
  spinner.succeed('Analysis complete');
608
634
  printSection('Dead Code');
609
- console.log(` Unused exports: ${result.unusedExports?.length || 0}`);
610
- console.log(` Unused variables: ${result.unusedVariables?.length || 0}`);
611
- console.log(` Unreachable code: ${result.unreachableCode?.length || 0}`);
612
- if (result.unusedExports?.length > 0) {
635
+ const unusedExports = result.unusedExports || [];
636
+ const unusedVariables = result.unusedVariables || [];
637
+ const unreachableCode = result.unreachableCode || [];
638
+ console.log(` Unused exports: ${unusedExports.length}`);
639
+ console.log(` Unused variables: ${unusedVariables.length}`);
640
+ console.log(` Unreachable code: ${unreachableCode.length}`);
641
+ if (unusedExports.length === 0 && unusedVariables.length === 0 && unreachableCode.length === 0) {
642
+ printSuccess('No dead code found!');
643
+ return;
644
+ }
645
+ if (unusedExports.length > 0) {
613
646
  console.log();
614
647
  console.log(colors.muted(' Top unused exports:'));
615
- for (const item of result.unusedExports.slice(0, 5)) {
616
- console.log(` ${colors.warning(icons.warning)} ${item.name} ${colors.dim(`(${item.filePath})`)}`);
648
+ for (const item of unusedExports.slice(0, 5)) {
649
+ const name = item.name || item.symbol || 'unknown';
650
+ const file = item.filePath || item.file || '';
651
+ console.log(` ${colors.warning(icons.warning)} ${name} ${colors.dim(`(${file})`)}`);
617
652
  }
618
653
  }
619
654
  }
620
655
  catch (error) {
621
656
  spinner.fail('Analysis failed');
622
- throw error;
657
+ printFormattedError(error, { operation: 'Dead code analysis' });
623
658
  }
624
659
  }
625
660
  async function handleSecurityCommand() {
@@ -634,7 +669,9 @@ async function handleSecurityCommand() {
634
669
  if (!response.ok)
635
670
  throw new Error('Analysis failed');
636
671
  const data = await response.json();
637
- const vulns = data.security?.vulnerabilities || [];
672
+ // Handle various response formats
673
+ const security = data.security || data.result || data || {};
674
+ const vulns = security.vulnerabilities || security.issues || [];
638
675
  spinner.succeed('Analysis complete');
639
676
  printSection('Security');
640
677
  if (vulns.length === 0) {
@@ -655,13 +692,15 @@ async function handleSecurityCommand() {
655
692
  console.log();
656
693
  console.log(colors.muted(' Critical/High issues:'));
657
694
  for (const v of vulns.filter((v) => v.severity === 'critical' || v.severity === 'high').slice(0, 5)) {
658
- console.log(` ${colors.error(icons.error)} ${v.type}: ${v.description}`);
695
+ const type = v.type || v.category || 'Issue';
696
+ const desc = v.description || v.message || 'No description';
697
+ console.log(` ${colors.error(icons.error)} ${type}: ${desc}`);
659
698
  }
660
699
  }
661
700
  }
662
701
  catch (error) {
663
702
  spinner.fail('Analysis failed');
664
- throw error;
703
+ printFormattedError(error, { operation: 'Security analysis' });
665
704
  }
666
705
  }
667
706
  async function handleMetricsCommand() {
@@ -676,19 +715,38 @@ async function handleMetricsCommand() {
676
715
  if (!response.ok)
677
716
  throw new Error('Analysis failed');
678
717
  const data = await response.json();
679
- const metrics = data.metrics;
718
+ // Handle various response formats
719
+ const metrics = data.metrics || data.result || data || {};
720
+ const summary = metrics.summary || metrics || {};
680
721
  spinner.succeed('Metrics calculated');
681
722
  printSection('Code Metrics');
682
- if (metrics.summary) {
683
- console.log(` Files: ${metrics.summary.totalFiles || 0}`);
684
- console.log(` Lines: ${metrics.summary.totalLines || 0}`);
685
- console.log(` Avg Complexity: ${(metrics.summary.avgComplexity || 0).toFixed(2)}`);
686
- console.log(` Avg Maintainability: ${(metrics.summary.avgMaintainability || 0).toFixed(1)}`);
723
+ // Display available metrics
724
+ const totalFiles = summary.totalFiles || summary.files || metrics.totalFiles || 0;
725
+ const totalLines = summary.totalLines || summary.lines || metrics.totalLines || 0;
726
+ const avgComplexity = summary.avgComplexity || summary.complexity || metrics.avgComplexity || 0;
727
+ const avgMaintainability = summary.avgMaintainability || summary.maintainability || metrics.avgMaintainability || 0;
728
+ console.log(` Files: ${totalFiles}`);
729
+ console.log(` Lines: ${totalLines}`);
730
+ if (avgComplexity > 0) {
731
+ console.log(` Avg Complexity: ${Number(avgComplexity).toFixed(2)}`);
732
+ }
733
+ if (avgMaintainability > 0) {
734
+ console.log(` Avg Maintainability: ${Number(avgMaintainability).toFixed(1)}`);
735
+ }
736
+ // Show additional metrics if available
737
+ if (metrics.functions || summary.functions) {
738
+ console.log(` Functions: ${metrics.functions || summary.functions}`);
739
+ }
740
+ if (metrics.classes || summary.classes) {
741
+ console.log(` Classes: ${metrics.classes || summary.classes}`);
742
+ }
743
+ if (totalFiles === 0 && totalLines === 0) {
744
+ printWarning('No metrics data available. Make sure project is indexed.');
687
745
  }
688
746
  }
689
747
  catch (error) {
690
748
  spinner.fail('Analysis failed');
691
- throw error;
749
+ printFormattedError(error, { operation: 'Metrics calculation' });
692
750
  }
693
751
  }
694
752
  async function handleExportCommand(args) {
@@ -781,28 +839,37 @@ async function handleDuplicationCommand() {
781
839
  if (!response.ok)
782
840
  throw new Error('Analysis failed');
783
841
  const data = await response.json();
784
- const result = data.duplication;
842
+ // Handle various response formats
843
+ const result = data.duplication || data.result || data || {};
844
+ const clones = result.clones || [];
845
+ const duplicationRate = result.duplicationRate || result.rate || 0;
846
+ const duplicatedLines = result.duplicatedLines || result.lines || 0;
785
847
  spinner.succeed('Analysis complete');
786
848
  printSection('Code Duplication');
787
- console.log(` Code clones: ${result.clones?.length || 0}`);
788
- console.log(` Duplication rate: ${(result.duplicationRate || 0).toFixed(1)}%`);
789
- console.log(` Duplicated lines: ${result.duplicatedLines || 0}`);
790
- if (result.clones?.length > 0) {
791
- console.log();
792
- console.log(colors.muted(' Top duplicates:'));
793
- for (const clone of result.clones.slice(0, 5)) {
794
- console.log(` ${colors.warning(icons.warning)} ${clone.files?.length || 2} files, ${clone.lines || 0} lines`);
795
- if (clone.files) {
796
- for (const f of clone.files.slice(0, 2)) {
797
- console.log(` ${colors.dim(f)}`);
798
- }
849
+ console.log(` Code clones: ${clones.length}`);
850
+ console.log(` Duplication rate: ${Number(duplicationRate).toFixed(1)}%`);
851
+ console.log(` Duplicated lines: ${duplicatedLines}`);
852
+ if (clones.length === 0) {
853
+ printSuccess('No code duplication found!');
854
+ return;
855
+ }
856
+ console.log();
857
+ console.log(colors.muted(' Top duplicates:'));
858
+ for (const clone of clones.slice(0, 5)) {
859
+ const files = clone.files || clone.locations || [];
860
+ const lines = clone.lines || clone.lineCount || 0;
861
+ console.log(` ${colors.warning(icons.warning)} ${files.length || 2} files, ${lines} lines`);
862
+ if (files.length > 0) {
863
+ for (const f of files.slice(0, 2)) {
864
+ const filePath = typeof f === 'string' ? f : f.file || f.path || '';
865
+ console.log(` ${colors.dim(filePath)}`);
799
866
  }
800
867
  }
801
868
  }
802
869
  }
803
870
  catch (error) {
804
871
  spinner.fail('Analysis failed');
805
- throw error;
872
+ printFormattedError(error, { operation: 'Duplication analysis' });
806
873
  }
807
874
  }
808
875
  async function handleRefactoringCommand() {
@@ -818,7 +885,9 @@ async function handleRefactoringCommand() {
818
885
  if (!response.ok)
819
886
  throw new Error('Analysis failed');
820
887
  const data = await response.json();
821
- const suggestions = data.refactoring?.suggestions || [];
888
+ // Handle various response formats
889
+ const refactoring = data.refactoring || data.result || data || {};
890
+ const suggestions = refactoring.suggestions || refactoring.items || [];
822
891
  spinner.succeed('Analysis complete');
823
892
  printSection('Refactoring Suggestions');
824
893
  if (suggestions.length === 0) {
@@ -837,17 +906,20 @@ async function handleRefactoringCommand() {
837
906
  console.log();
838
907
  console.log(colors.muted(' Top suggestions:'));
839
908
  for (const s of suggestions.slice(0, 5)) {
840
- const priorityColor = s.priority === 'critical' ? colors.critical :
841
- s.priority === 'high' ? colors.high : colors.muted;
842
- console.log(` ${priorityColor(`[${s.priority}]`)} ${s.type}: ${s.description}`);
843
- if (s.file) {
844
- console.log(` ${colors.dim(s.file)}`);
909
+ const priority = s.priority || 'medium';
910
+ const type = s.type || s.category || 'Suggestion';
911
+ const desc = s.description || s.message || '';
912
+ const priorityColor = priority === 'critical' ? colors.critical :
913
+ priority === 'high' ? colors.high : colors.muted;
914
+ console.log(` ${priorityColor(`[${priority}]`)} ${type}: ${desc}`);
915
+ if (s.file || s.filePath) {
916
+ console.log(` ${colors.dim(s.file || s.filePath)}`);
845
917
  }
846
918
  }
847
919
  }
848
920
  catch (error) {
849
921
  spinner.fail('Analysis failed');
850
- throw error;
922
+ printFormattedError(error, { operation: 'Refactoring analysis' });
851
923
  }
852
924
  }
853
925
  async function handleRulesCommand() {
@@ -863,7 +935,9 @@ async function handleRulesCommand() {
863
935
  if (!response.ok)
864
936
  throw new Error('Analysis failed');
865
937
  const data = await response.json();
866
- const violations = data.rules?.violations || [];
938
+ // Handle various response formats
939
+ const rules = data.rules || data.result || data || {};
940
+ const violations = rules.violations || rules.issues || [];
867
941
  spinner.succeed('Analysis complete');
868
942
  printSection('Architectural Rules');
869
943
  if (violations.length === 0) {
@@ -873,17 +947,27 @@ async function handleRulesCommand() {
873
947
  console.log(` Violations: ${violations.length}`);
874
948
  console.log();
875
949
  for (const v of violations.slice(0, 10)) {
876
- const severityColor = v.severity === 'error' ? colors.error :
877
- v.severity === 'warning' ? colors.warning : colors.muted;
878
- console.log(` ${severityColor(icons.error)} ${v.rule}: ${v.message}`);
879
- if (v.file) {
880
- console.log(` ${colors.dim(v.file)}`);
950
+ const severity = v.severity || 'warning';
951
+ const rule = v.rule || v.type || v.name || '';
952
+ const message = v.message || v.description || '';
953
+ const file = v.file || v.filePath || '';
954
+ const severityColor = severity === 'error' ? colors.error :
955
+ severity === 'warning' ? colors.warning : colors.muted;
956
+ // Format: show rule type if present, otherwise just message
957
+ if (rule && message) {
958
+ console.log(` ${severityColor(icons.error)} [${rule}] ${message}`);
959
+ }
960
+ else {
961
+ console.log(` ${severityColor(icons.error)} ${message || rule}`);
962
+ }
963
+ if (file) {
964
+ console.log(` ${colors.dim(file)}`);
881
965
  }
882
966
  }
883
967
  }
884
968
  catch (error) {
885
969
  spinner.fail('Analysis failed');
886
- throw error;
970
+ printFormattedError(error, { operation: 'Rules check' });
887
971
  }
888
972
  }
889
973
  async function handleDocsCommand(args) {
@@ -96,9 +96,13 @@ export declare class GitHubService {
96
96
  */
97
97
  private deleteWebhook;
98
98
  /**
99
- * Verify webhook signature
99
+ * Verify webhook signature (HMAC-SHA256)
100
100
  */
101
101
  verifyWebhookSignature(payload: string, signature: string, secret: string): boolean;
102
+ /**
103
+ * Get decrypted webhook secret
104
+ */
105
+ getWebhookSecret(encryptedSecret: string): string;
102
106
  /**
103
107
  * Find repository by webhook payload
104
108
  */
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Handles OAuth, API calls, webhooks, and repository management
5
5
  */
6
- import { randomBytes, createHash, createHmac, createCipheriv, createDecipheriv } from 'crypto';
6
+ import { randomBytes, createHash, createHmac, createCipheriv, createDecipheriv, timingSafeEqual } from 'crypto';
7
7
  import { readFile, writeFile, mkdir } from 'fs/promises';
8
8
  import { join } from 'path';
9
9
  import { Logger } from '../utils/logger.js';
@@ -483,12 +483,30 @@ export class GitHubService {
483
483
  });
484
484
  }
485
485
  /**
486
- * Verify webhook signature
486
+ * Verify webhook signature (HMAC-SHA256)
487
487
  */
488
488
  verifyWebhookSignature(payload, signature, secret) {
489
489
  const hmac = createHmac('sha256', secret);
490
490
  const digest = 'sha256=' + hmac.update(payload).digest('hex');
491
- return signature === digest;
491
+ // Use timing-safe comparison to prevent timing attacks
492
+ if (signature.length !== digest.length) {
493
+ return false;
494
+ }
495
+ // Convert to buffers for timing-safe comparison
496
+ const signatureBuffer = Buffer.from(signature);
497
+ const digestBuffer = Buffer.from(digest);
498
+ try {
499
+ return timingSafeEqual(signatureBuffer, digestBuffer);
500
+ }
501
+ catch {
502
+ return false;
503
+ }
504
+ }
505
+ /**
506
+ * Get decrypted webhook secret
507
+ */
508
+ getWebhookSecret(encryptedSecret) {
509
+ return this.decrypt(encryptedSecret);
492
510
  }
493
511
  /**
494
512
  * Find repository by webhook payload
@@ -7,11 +7,17 @@ export declare class EmbeddingService {
7
7
  private config;
8
8
  private initialized;
9
9
  private _isAvailable;
10
- private jinaApiKey?;
10
+ private jinaApiKeys;
11
+ private currentKeyIndex;
12
+ private keyFailures;
11
13
  private embeddingDimension;
12
14
  constructor(config: EmbeddingConfig);
13
15
  getEmbeddingDimension(): number;
14
16
  private ensureInitialized;
17
+ private getCurrentJinaKey;
18
+ private rotateToNextKey;
19
+ private shouldSkipKey;
20
+ private findWorkingKeyIndex;
15
21
  isAvailable(): boolean;
16
22
  generateEmbedding(text: string): Promise<number[]>;
17
23
  private generateJinaEmbedding;
@@ -19,6 +25,7 @@ export declare class EmbeddingService {
19
25
  * Generate embeddings for multiple texts - uses true batch API when available
20
26
  */
21
27
  generateBatchEmbeddings(texts: string[], progressCallback?: (current: number, total: number) => void): Promise<number[][]>;
28
+ private generateJinaBatchWithRetry;
22
29
  private generateOpenAIEmbedding;
23
30
  prepareCodeForEmbedding(code: string, context?: string): string;
24
31
  generateCodeEmbedding(code: string, metadata: {