chore: import upstream snapshot with attribution
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:02:19 +08:00
commit 23f7624596
5116 changed files with 1656296 additions and 0 deletions
+381
View File
@@ -0,0 +1,381 @@
# @claude-flow/plugin-code-intelligence
[![npm version](https://img.shields.io/npm/v/@claude-flow/plugin-code-intelligence.svg)](https://www.npmjs.com/package/@claude-flow/plugin-code-intelligence)
[![npm downloads](https://img.shields.io/npm/dm/@claude-flow/plugin-code-intelligence.svg)](https://www.npmjs.com/package/@claude-flow/plugin-code-intelligence)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
A comprehensive code intelligence plugin combining graph neural networks for code structure analysis with ultra-fast vector search for semantic code similarity. The plugin enables dead code detection, API surface analysis, refactoring impact prediction, and architectural drift monitoring while integrating seamlessly with existing IDE workflows.
## Features
- **Semantic Code Search**: Find semantically similar code across the codebase using natural language or code snippets
- **Architecture Analysis**: Analyze dependency graphs, detect layer violations, circular dependencies, and architectural drift
- **Refactoring Impact Prediction**: Predict the impact of proposed code changes using GNN analysis
- **Module Splitting**: Suggest optimal module boundaries using MinCut algorithms
- **Pattern Learning**: Learn recurring patterns from code changes using SONA (Self-Optimizing Neural Architecture)
## Installation
### npm
```bash
npm install @claude-flow/plugin-code-intelligence
```
### CLI
```bash
npx claude-flow plugins install --name @claude-flow/plugin-code-intelligence
```
## Quick Start
```typescript
import { CodeIntelligencePlugin } from '@claude-flow/plugin-code-intelligence';
// Initialize the plugin
const codeIntel = new CodeIntelligencePlugin({
indexPath: './data/code-index',
repoPath: './',
languages: ['typescript', 'javascript']
});
// Semantic code search
const results = await codeIntel.semanticSearch({
query: 'function that validates user email',
scope: { paths: ['src/'], excludeTests: true },
searchType: 'semantic',
topK: 10
});
// Analyze architecture
const architecture = await codeIntel.architectureAnalyze({
rootPath: './src',
analysis: ['dependency_graph', 'circular_deps', 'dead_code'],
outputFormat: 'mermaid'
});
// Predict refactoring impact
const impact = await codeIntel.refactorImpact({
changes: [
{ file: 'src/utils/auth.ts', type: 'rename', details: { newName: 'authentication.ts' } }
],
depth: 3,
includeTests: true
});
```
## MCP Tools
### 1. `code/semantic-search`
Find semantically similar code across the codebase.
```typescript
const result = await mcp.invoke('code/semantic-search', {
query: 'handle authentication token refresh',
scope: {
paths: ['src/'],
languages: ['typescript'],
excludeTests: false
},
searchType: 'semantic',
topK: 10
});
// Returns:
// {
// results: [
// {
// file: 'src/auth/tokenManager.ts',
// function: 'refreshAccessToken',
// snippet: 'async function refreshAccessToken(refreshToken: string)...',
// similarity: 0.94,
// lineRange: [45, 72]
// },
// ...
// ]
// }
```
### 2. `code/architecture-analyze`
Analyze codebase architecture using graph algorithms.
```typescript
const result = await mcp.invoke('code/architecture-analyze', {
rootPath: './src',
analysis: [
'dependency_graph',
'layer_violations',
'circular_deps',
'component_coupling',
'dead_code'
],
baseline: 'main', // Git ref for drift comparison
outputFormat: 'mermaid'
});
// Returns:
// {
// dependencyGraph: '...mermaid diagram...',
// layerViolations: [
// { from: 'presentation/UserForm', to: 'data/UserRepository', rule: 'no-direct-data-access' }
// ],
// circularDeps: [
// ['moduleA', 'moduleB', 'moduleC', 'moduleA']
// ],
// deadCode: [
// { file: 'src/utils/legacy.ts', reason: 'no-references', confidence: 0.95 }
// ],
// architecturalDrift: {
// newDependencies: 5,
// removedDependencies: 2,
// couplingChange: +0.03
// }
// }
```
### 3. `code/refactor-impact`
Predict impact of proposed refactoring.
```typescript
const result = await mcp.invoke('code/refactor-impact', {
changes: [
{ file: 'src/services/UserService.ts', type: 'extract', details: { method: 'validateUser' } },
{ file: 'src/models/User.ts', type: 'rename', details: { property: 'name', to: 'fullName' } }
],
depth: 3,
includeTests: true
});
// Returns:
// {
// impactedFiles: [
// { file: 'src/controllers/AuthController.ts', changes: ['import path', 'method call'] },
// { file: 'src/services/AdminService.ts', changes: ['method call'] },
// { file: 'tests/UserService.test.ts', changes: ['test assertions'] }
// ],
// totalFilesAffected: 12,
// riskScore: 0.35,
// breakingChanges: [
// { type: 'property-rename', description: 'User.name -> User.fullName', usages: 45 }
// ],
// suggestedMigrationSteps: [...]
// }
```
### 4. `code/split-suggest`
Suggest optimal module boundaries using MinCut.
```typescript
const result = await mcp.invoke('code/split-suggest', {
targetPath: './src/monolith',
strategy: 'minimize_coupling',
constraints: {
maxModuleSize: 5000, // LOC
minModuleSize: 500,
preserveBoundaries: ['src/monolith/core']
},
targetModules: 4
});
// Returns:
// {
// suggestedModules: [
// {
// name: 'user-management',
// files: ['User.ts', 'UserService.ts', 'UserRepository.ts'],
// loc: 2340,
// externalDependencies: 3
// },
// {
// name: 'order-processing',
// files: ['Order.ts', 'OrderService.ts', 'PaymentHandler.ts'],
// loc: 3120,
// externalDependencies: 5
// },
// ...
// ],
// couplingReduction: '45%',
// migrationComplexity: 'medium'
// }
```
### 5. `code/learn-patterns`
Learn code patterns from repository history.
```typescript
const result = await mcp.invoke('code/learn-patterns', {
scope: {
gitRange: 'HEAD~100..HEAD',
authors: [], // All authors
paths: ['src/']
},
patternTypes: ['bug_patterns', 'refactor_patterns'],
minOccurrences: 3
});
// Returns:
// {
// patterns: [
// {
// type: 'bug_pattern',
// name: 'null-check-before-access',
// occurrences: 12,
// description: 'Adding null checks before property access',
// example: { before: 'user.name', after: 'user?.name' }
// },
// {
// type: 'refactor_pattern',
// name: 'async-await-migration',
// occurrences: 8,
// description: 'Converting Promise.then chains to async/await'
// }
// ]
// }
```
## Configuration Options
```typescript
interface CodeIntelligenceConfig {
// Indexing
indexPath: string; // Path to code index storage
repoPath: string; // Repository root path
// Language support
languages: string[]; // Languages to index
excludePaths: string[]; // Paths to exclude
// Performance
maxMemoryMB: number; // WASM memory limit (default: 1024)
maxCpuTimeSeconds: number; // Operation timeout (default: 300)
incrementalIndexing: boolean; // Enable incremental updates (default: true)
// Security
blockSensitiveFiles: boolean; // Block .env, credentials, etc. (default: true)
maskSecrets: boolean; // Mask detected secrets in results (default: true)
}
```
## Supported Languages
| Tier | Languages | Support Level |
|------|-----------|---------------|
| Tier 1 (Full) | TypeScript, JavaScript, React, Vue, Angular | Complete AST analysis |
| Tier 2 (Partial) | Python, Java, Ruby, PHP | Core analysis features |
| Tier 3 (Basic) | Rust, Go, C++, Swift, Kotlin | Dependency and search |
## Security Considerations
### Path Traversal Prevention
All file paths are validated to prevent directory traversal:
- Paths normalized and resolved against allowed root
- Blocked patterns: `.env`, `.git/config`, credentials, secrets, private keys
### Secret Detection and Masking
Secrets are automatically detected and masked in search results:
- API keys and tokens
- Private keys and certificates
- Password strings
- Cloud provider credentials (AWS, GCP, Azure)
### WASM Security Constraints
| Constraint | Value | Rationale |
|------------|-------|-----------|
| Memory Limit | 1GB max | Handle large codebases |
| CPU Time Limit | 300 seconds | Allow full repo analysis |
| No Network Access | Enforced | Prevent code exfiltration |
| No Shell Execution | Enforced | Prevent command injection |
| Read-Only Mode | Enforced | Prevent code modification |
### Input Validation
All inputs are validated using Zod schemas:
```typescript
// All inputs validated:
- Query strings: 1-5000 characters
- File paths: Maximum 500 characters, max 100 paths per request
- Languages: Maximum 20 languages per request
- topK: 1-1000 results
- Git refs: Validated against safe patterns (no shell metacharacters)
- Module size limits: 100-100000 LOC
```
### Rate Limiting
```typescript
const rateLimits = {
'code/semantic-search': { requestsPerMinute: 60, maxConcurrent: 5 },
'code/architecture-analyze': { requestsPerMinute: 10, maxConcurrent: 2 },
'code/refactor-impact': { requestsPerMinute: 20, maxConcurrent: 3 },
'code/split-suggest': { requestsPerMinute: 5, maxConcurrent: 1 },
'code/learn-patterns': { requestsPerMinute: 5, maxConcurrent: 1 }
};
```
## Performance
| Metric | Target |
|--------|--------|
| Semantic code search | <100ms for 1M LOC |
| Architecture analysis | <10s for 100K LOC |
| Refactor impact | <5s for single change |
| Module splitting | <30s for 50K LOC |
| Pattern learning | <2min for 1000 commits |
## IDE Integration
- **VS Code Extension**: Real-time analysis and suggestions
- **JetBrains Plugin**: IntelliJ, WebStorm, PyCharm support
- **CLI**: CI/CD pipeline integration
- **MCP**: Direct Claude Code integration
## Dependencies
- `micro-hnsw-wasm`: Semantic code search and clone detection (150x faster)
- `ruvector-gnn-wasm`: Code dependency graphs, call graphs, and control flow analysis
- `ruvector-mincut-wasm`: Module boundary detection and optimal code splitting
- `sona`: Self-optimizing learning from code review patterns
- `ruvector-dag-wasm`: Build dependency analysis and incremental compilation
- `@babel/parser`: JavaScript/TypeScript AST parsing
- `typescript`: TypeScript compiler API
## Related Plugins
| Plugin | Description | Use Case |
|--------|-------------|----------|
| [@claude-flow/plugin-test-intelligence](../test-intelligence) | Test optimization | Predictive test selection based on code changes |
| [@claude-flow/plugin-perf-optimizer](../perf-optimizer) | Performance optimization | Code performance bottleneck detection |
| [@claude-flow/plugin-legal-contracts](../legal-contracts) | Contract analysis | Software licensing compliance |
## License
MIT License
Copyright (c) 2026 Claude Flow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+77
View File
@@ -0,0 +1,77 @@
{
"name": "@claude-flow/plugin-code-intelligence",
"version": "3.0.0-alpha.3",
"description": "Advanced code intelligence plugin for Claude Flow V3 - semantic search, architecture analysis, refactoring impact, module splitting, and pattern learning",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./tools": {
"import": "./dist/mcp-tools.js",
"types": "./dist/mcp-tools.d.ts"
},
"./types": {
"import": "./dist/types.js",
"types": "./dist/types.d.ts"
},
"./bridges": {
"import": "./dist/bridges/index.js",
"types": "./dist/bridges/index.d.ts"
}
},
"files": [
"dist",
"README.md",
"plugin.yaml"
],
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"clean": "rm -rf dist",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src --ext .ts",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run clean && npm run build && npm run typecheck"
},
"publishConfig": {
"access": "public",
"tag": "v3alpha"
},
"keywords": [
"claude-flow",
"plugin",
"code-intelligence",
"semantic-search",
"architecture",
"refactoring",
"code-analysis",
"mcp"
],
"author": "Claude Flow Team",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/ruvnet/claude-flow.git",
"directory": "v3/plugins/code-intelligence"
},
"dependencies": {
"zod": "^3.23.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
},
"peerDependencies": {
"@claude-flow/plugins": "^3.0.0-alpha.1"
},
"engines": {
"node": ">=20.0.0"
}
}
@@ -0,0 +1,740 @@
/**
* GNN Bridge for Code Graph Analysis
*
* Provides graph neural network operations for code structure analysis
* using ruvector-gnn-wasm for high-performance graph algorithms.
*
* Features:
* - Code graph construction
* - Node embedding computation
* - Impact prediction using graph propagation
* - Community detection for module discovery
* - Pattern matching in code graphs
*
* Based on ADR-035: Advanced Code Intelligence Plugin
*
* @module v3/plugins/code-intelligence/bridges/gnn-bridge
*/
import type {
IGNNBridge,
DependencyGraph,
DependencyNode,
DependencyEdge,
} from '../types.js';
/**
* WASM module interface for GNN operations
*/
interface GNNWasmModule {
/** Build graph from adjacency list */
gnn_build_graph(
nodeCount: number,
edges: Uint32Array,
edgeCount: number
): number;
/** Compute GNN embeddings */
gnn_compute_embeddings(
graphPtr: number,
features: Float32Array,
featureDim: number,
outputDim: number,
layers: number
): Float32Array;
/** Propagate labels for impact analysis */
gnn_propagate(
graphPtr: number,
initialLabels: Float32Array,
iterations: number,
dampingFactor: number
): Float32Array;
/** Community detection using Louvain algorithm */
gnn_detect_communities(
graphPtr: number,
weights: Float32Array
): Uint32Array;
/** Subgraph matching */
gnn_match_subgraph(
graphPtr: number,
patternPtr: number,
threshold: number
): Float32Array;
/** Free graph */
gnn_free(graphPtr: number): void;
/** Memory management */
alloc(size: number): number;
dealloc(ptr: number, size: number): void;
memory: WebAssembly.Memory;
}
/**
* GNN Bridge Implementation
*/
export class GNNBridge implements IGNNBridge {
// WASM module for future performance optimization (currently uses JS fallback)
private wasmModule: GNNWasmModule | null = null;
private initialized = false;
private readonly embeddingDim: number;
constructor(embeddingDim = 128) {
this.embeddingDim = embeddingDim;
}
/**
* Initialize the WASM module
*/
async initialize(): Promise<void> {
if (this.initialized) return;
try {
// Dynamic import of WASM module
this.wasmModule = await this.loadWasmModule();
this.initialized = true;
} catch {
// Fallback to pure JS implementation
console.warn('WASM GNN module not available, using JS fallback');
this.wasmModule = null;
this.initialized = true;
}
}
/**
* Check if initialized
*/
isInitialized(): boolean {
return this.initialized;
}
/**
* Build code graph from files
*/
async buildCodeGraph(
files: string[],
_includeCallGraph: boolean
): Promise<DependencyGraph> {
if (!this.initialized) {
await this.initialize();
}
const nodes: DependencyNode[] = [];
const edges: DependencyEdge[] = [];
const nodeMap = new Map<string, number>();
// Create nodes for each file
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (!file) continue;
nodeMap.set(file, i);
nodes.push({
id: file,
label: file.split('/').pop() ?? file,
type: 'file',
language: this.detectLanguage(file),
});
}
// Build edges from imports (simplified - in production would parse AST)
for (const file of files) {
const imports = await this.extractImports(file, files);
for (const imp of imports) {
if (nodeMap.has(imp)) {
edges.push({
from: file,
to: imp,
type: 'import',
weight: 1,
});
}
}
}
// Calculate metadata
const avgDegree = edges.length > 0 ? (edges.length * 2) / nodes.length : 0;
const maxDepth = this.calculateMaxDepth(nodes, edges);
return {
nodes,
edges,
metadata: {
totalNodes: nodes.length,
totalEdges: edges.length,
avgDegree,
maxDepth,
},
};
}
/**
* Compute node embeddings using GNN
*/
async computeNodeEmbeddings(
graph: DependencyGraph,
embeddingDim: number
): Promise<Map<string, Float32Array>> {
if (!this.initialized) {
await this.initialize();
}
const embeddings = new Map<string, Float32Array>();
const nodeCount = graph.nodes.length;
if (nodeCount === 0) {
return embeddings;
}
// Create node lookup
const nodeMap = new Map<string, number>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
});
// Build adjacency list for WASM
const edgeArray = new Uint32Array(graph.edges.length * 2);
for (let i = 0; i < graph.edges.length; i++) {
const edge = graph.edges[i];
if (!edge) continue;
const fromIdx = nodeMap.get(edge.from) ?? 0;
const toIdx = nodeMap.get(edge.to) ?? 0;
edgeArray[i * 2] = fromIdx;
edgeArray[i * 2 + 1] = toIdx;
}
// Initialize features (simple degree-based features)
const featureDim = 16;
const features = new Float32Array(nodeCount * featureDim);
for (let i = 0; i < nodeCount; i++) {
const node = graph.nodes[i];
if (!node) continue;
// In-degree
features[i * featureDim] = graph.edges.filter(e => e.to === node.id).length;
// Out-degree
features[i * featureDim + 1] = graph.edges.filter(e => e.from === node.id).length;
// Node type encoding
features[i * featureDim + 2] = this.encodeNodeType(node.type);
// Language encoding
features[i * featureDim + 3] = node.language ? this.encodeLanguage(node.language) : 0;
}
// Compute embeddings (using JS fallback)
const embeddingMatrix = this.computeEmbeddingsJS(
graph,
features,
featureDim,
embeddingDim
);
// Extract embeddings per node
for (let i = 0; i < nodeCount; i++) {
const node = graph.nodes[i];
if (!node) continue;
const nodeEmbedding = embeddingMatrix.slice(
i * embeddingDim,
(i + 1) * embeddingDim
);
embeddings.set(node.id, nodeEmbedding);
}
return embeddings;
}
/**
* Predict impact of changes using GNN
*/
async predictImpact(
graph: DependencyGraph,
changedNodes: string[],
depth: number
): Promise<Map<string, number>> {
if (!this.initialized) {
await this.initialize();
}
const impact = new Map<string, number>();
const nodeCount = graph.nodes.length;
if (nodeCount === 0) {
return impact;
}
// Create node lookup
const nodeMap = new Map<string, number>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
});
// Build adjacency list (reverse direction for impact propagation)
const adj: number[][] = Array.from({ length: nodeCount }, () => []);
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
// Reverse: impact flows from dependency to dependent
adj[toIdx]?.push(fromIdx);
}
}
// Initialize impact scores
const scores = new Float32Array(nodeCount);
for (const nodeId of changedNodes) {
const idx = nodeMap.get(nodeId);
if (idx !== undefined) {
scores[idx] = 1.0;
}
}
// Propagate impact using BFS with decay
const visited = new Set<number>();
const queue: Array<{ node: number; depth: number; score: number }> = [];
// Initialize queue with changed nodes
for (const nodeId of changedNodes) {
const idx = nodeMap.get(nodeId);
if (idx !== undefined) {
queue.push({ node: idx, depth: 0, score: 1.0 });
visited.add(idx);
}
}
// BFS propagation
while (queue.length > 0) {
const current = queue.shift();
if (!current || current.depth >= depth) continue;
for (const neighbor of adj[current.node] ?? []) {
const newScore = current.score * 0.7; // Decay factor
const neighborScore = scores[neighbor];
if (neighborScore !== undefined && newScore > neighborScore) {
scores[neighbor] = newScore;
}
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push({
node: neighbor,
depth: current.depth + 1,
score: newScore,
});
}
}
}
// Convert to map
for (let i = 0; i < nodeCount; i++) {
const node = graph.nodes[i];
const score = scores[i];
if (node && score !== undefined && score > 0) {
impact.set(node.id, score);
}
}
return impact;
}
/**
* Detect communities in code graph
*/
async detectCommunities(
graph: DependencyGraph
): Promise<Map<string, number>> {
if (!this.initialized) {
await this.initialize();
}
const communities = new Map<string, number>();
const nodeCount = graph.nodes.length;
if (nodeCount === 0) {
return communities;
}
// Create node lookup
const nodeMap = new Map<string, number>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
});
// Build adjacency list (undirected)
const adj: Set<number>[] = Array.from({ length: nodeCount }, () => new Set());
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
adj[fromIdx]?.add(toIdx);
adj[toIdx]?.add(fromIdx);
}
}
// Simple community detection using connected components
// In production, would use Louvain or similar
const community = new Array(nodeCount).fill(-1);
let communityId = 0;
for (let i = 0; i < nodeCount; i++) {
if (community[i] !== -1) continue;
// BFS to find connected component
const queue = [i];
community[i] = communityId;
while (queue.length > 0) {
const current = queue.shift()!;
for (const neighbor of adj[current] ?? []) {
if (community[neighbor] === -1) {
community[neighbor] = communityId;
queue.push(neighbor);
}
}
}
communityId++;
}
// Convert to map
for (let i = 0; i < nodeCount; i++) {
const node = graph.nodes[i];
const comm = community[i];
if (node && comm !== undefined) {
communities.set(node.id, comm);
}
}
return communities;
}
/**
* Find similar code patterns
*/
async findSimilarPatterns(
graph: DependencyGraph,
patternGraph: DependencyGraph,
threshold: number
): Promise<Array<{ matchId: string; score: number }>> {
if (!this.initialized) {
await this.initialize();
}
const matches: Array<{ matchId: string; score: number }> = [];
// Compute embeddings for both graphs
const graphEmbeddings = await this.computeNodeEmbeddings(graph, this.embeddingDim);
const patternEmbeddings = await this.computeNodeEmbeddings(patternGraph, this.embeddingDim);
// Average pattern embedding
const patternAvg = new Float32Array(this.embeddingDim);
let patternCount = 0;
for (const [, embedding] of patternEmbeddings) {
for (let i = 0; i < this.embeddingDim; i++) {
patternAvg[i] = (patternAvg[i] ?? 0) + (embedding[i] ?? 0);
}
patternCount++;
}
if (patternCount > 0) {
for (let i = 0; i < this.embeddingDim; i++) {
patternAvg[i] = (patternAvg[i] ?? 0) / patternCount;
}
}
// Find similar nodes in main graph
for (const [nodeId, embedding] of graphEmbeddings) {
const similarity = this.cosineSimilarity(embedding, patternAvg);
if (similarity >= threshold) {
matches.push({ matchId: nodeId, score: similarity });
}
}
// Sort by score descending
matches.sort((a, b) => b.score - a.score);
return matches;
}
// ============================================================================
// Private Helper Methods
// ============================================================================
/**
* Load WASM module dynamically
*/
private async loadWasmModule(): Promise<GNNWasmModule> {
throw new Error('WASM module loading not implemented');
}
/**
* Detect language from file extension
*/
private detectLanguage(filePath: string): DependencyNode['language'] {
const ext = filePath.split('.').pop()?.toLowerCase();
const langMap: Record<string, DependencyNode['language']> = {
ts: 'typescript',
tsx: 'typescript',
js: 'javascript',
jsx: 'javascript',
py: 'python',
java: 'java',
go: 'go',
rs: 'rust',
cpp: 'cpp',
c: 'cpp',
cs: 'csharp',
rb: 'ruby',
php: 'php',
swift: 'swift',
kt: 'kotlin',
scala: 'scala',
};
return ext ? langMap[ext] : undefined;
}
/**
* Extract imports from file. Returns the subset of `allFiles` that this
* file imports via relative specifiers — used to build edges in the
* dependency graph. #1554/#1553: previously returned `[]` which produced
* graphs with zero edges and broke architecture-analyze, refactor-impact,
* and split-suggest. Regex-based (no AST parser dep) — handles `import …
* from`, `export … from`, and `require('…')` for relative paths only.
*/
private async extractImports(file: string, allFiles: string[]): Promise<string[]> {
const fs = await import('node:fs');
const path = await import('node:path');
let content: string;
try {
content = fs.readFileSync(file, 'utf-8');
} catch {
return [];
}
const allFilesSet = new Set(allFiles.map((f) => path.resolve(f)));
const exts = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
const baseDir = path.dirname(path.resolve(file));
const out = new Set<string>();
const importRx = /^\s*(?:import\s+[^'"]+from\s+|import\s+|export\s+\*?\s*from\s+|export\s+\{[^}]*\}\s+from\s+)['"]([^'"]+)['"]|^\s*(?:const|let|var)\s+[^=]+=\s*require\(\s*['"]([^'"]+)['"]\s*\)/gm;
let m: RegExpExecArray | null;
while ((m = importRx.exec(content)) !== null) {
const spec = m[1] ?? m[2];
if (!spec) continue;
// Only resolve relative/absolute paths — node_modules imports aren't
// graph nodes here.
if (!spec.startsWith('./') && !spec.startsWith('../') && !spec.startsWith('/')) continue;
const candidates: string[] = [];
const base = spec.startsWith('/') ? spec : path.resolve(baseDir, spec);
// Try the bare path, each extension, and `index.<ext>` under a directory.
candidates.push(base);
for (const e of exts) {
candidates.push(base + e);
candidates.push(path.join(base, `index${e}`));
}
// TS module-specifier convention: imports use `.js` even when the
// actual source is `.ts`/`.tsx`. Strip a trailing `.js`/`.cjs`/`.mjs`
// and re-try with TS extensions so source-level edges resolve.
const jsLikeMatch = base.match(/^(.+)\.(js|cjs|mjs)$/);
if (jsLikeMatch && jsLikeMatch[1]) {
const stripped = jsLikeMatch[1];
for (const e of ['.ts', '.tsx', '.js', '.jsx']) {
candidates.push(stripped + e);
candidates.push(path.join(stripped, `index${e}`));
}
}
for (const c of candidates) {
const resolved = path.resolve(c);
if (allFilesSet.has(resolved)) {
out.add(resolved);
break;
}
}
}
return Array.from(out);
}
/**
* Calculate max depth of dependency graph
*/
private calculateMaxDepth(nodes: DependencyNode[], edges: DependencyEdge[]): number {
if (nodes.length === 0) return 0;
// Build adjacency list
const adj = new Map<string, string[]>();
for (const node of nodes) {
adj.set(node.id, []);
}
for (const edge of edges) {
adj.get(edge.from)?.push(edge.to);
}
// Find nodes with no incoming edges (roots)
const hasIncoming = new Set<string>();
for (const edge of edges) {
hasIncoming.add(edge.to);
}
const roots = nodes.filter(n => !hasIncoming.has(n.id)).map(n => n.id);
if (roots.length === 0) {
// Cycle - use first node
roots.push(nodes[0]!.id);
}
// BFS to find max depth
let maxDepth = 0;
const visited = new Set<string>();
const queue: Array<{ node: string; depth: number }> = [];
for (const root of roots) {
queue.push({ node: root, depth: 0 });
visited.add(root);
}
while (queue.length > 0) {
const current = queue.shift()!;
maxDepth = Math.max(maxDepth, current.depth);
for (const neighbor of adj.get(current.node) ?? []) {
if (!visited.has(neighbor)) {
visited.add(neighbor);
queue.push({ node: neighbor, depth: current.depth + 1 });
}
}
}
return maxDepth;
}
/**
* Encode node type as number
*/
private encodeNodeType(type: DependencyNode['type']): number {
const types: Record<DependencyNode['type'], number> = {
file: 0.1,
module: 0.2,
package: 0.3,
class: 0.4,
function: 0.5,
};
return types[type];
}
/**
* Encode language as number
*/
private encodeLanguage(language: string): number {
const languages: Record<string, number> = {
typescript: 0.1,
javascript: 0.15,
python: 0.2,
java: 0.25,
go: 0.3,
rust: 0.35,
cpp: 0.4,
csharp: 0.45,
ruby: 0.5,
php: 0.55,
};
return languages[language] ?? 0;
}
/**
* Compute embeddings using JS (fallback)
*/
private computeEmbeddingsJS(
graph: DependencyGraph,
features: Float32Array,
featureDim: number,
outputDim: number
): Float32Array {
const nodeCount = graph.nodes.length;
const embeddings = new Float32Array(nodeCount * outputDim);
// Create adjacency matrix
const nodeMap = new Map<string, number>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
});
// Simple message passing (1 layer)
for (let i = 0; i < nodeCount; i++) {
const node = graph.nodes[i];
if (!node) continue;
// Aggregate neighbor features
const neighbors = graph.edges
.filter(e => e.to === node.id)
.map(e => nodeMap.get(e.from))
.filter((idx): idx is number => idx !== undefined);
// Initialize with own features (projected to output dim)
for (let j = 0; j < outputDim; j++) {
const featureIdx = j % featureDim;
embeddings[i * outputDim + j] = features[i * featureDim + featureIdx] ?? 0;
}
// Add neighbor contributions
if (neighbors.length > 0) {
for (const neighborIdx of neighbors) {
for (let j = 0; j < outputDim; j++) {
const featureIdx = j % featureDim;
const contribution = (features[neighborIdx * featureDim + featureIdx] ?? 0) / neighbors.length;
const embIdx = i * outputDim + j;
embeddings[embIdx] = (embeddings[embIdx] ?? 0) + contribution * 0.5;
}
}
}
}
// Normalize embeddings
for (let i = 0; i < nodeCount; i++) {
let norm = 0;
for (let j = 0; j < outputDim; j++) {
const val = embeddings[i * outputDim + j] ?? 0;
norm += val * val;
}
norm = Math.sqrt(norm);
if (norm > 0) {
for (let j = 0; j < outputDim; j++) {
embeddings[i * outputDim + j] = (embeddings[i * outputDim + j] ?? 0) / norm;
}
}
}
return embeddings;
}
/**
* Compute cosine similarity
*/
private cosineSimilarity(a: Float32Array, b: Float32Array): number {
let dot = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
const aVal = a[i] ?? 0;
const bVal = b[i] ?? 0;
dot += aVal * bVal;
normA += aVal * aVal;
normB += bVal * bVal;
}
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
return denominator > 0 ? dot / denominator : 0;
}
}
/**
* Create and export default bridge instance
*/
export function createGNNBridge(embeddingDim = 128): IGNNBridge {
return new GNNBridge(embeddingDim);
}
export default GNNBridge;
@@ -0,0 +1,15 @@
/**
* Code Intelligence Plugin - Bridges Barrel Export
*
* @module @claude-flow/plugin-code-intelligence/bridges
*/
export {
GNNBridge,
createGNNBridge,
} from './gnn-bridge.js';
export {
MinCutBridge,
createMinCutBridge,
} from './mincut-bridge.js';
@@ -0,0 +1,652 @@
/**
* MinCut Bridge for Module Splitting
*
* Provides graph min-cut operations for optimal module boundary detection
* using ruvector-mincut-wasm for high-performance graph partitioning.
*
* Features:
* - Optimal module boundary detection
* - Multi-way graph partitioning
* - Constraint-aware splitting
* - Cut weight optimization
*
* Based on ADR-035: Advanced Code Intelligence Plugin
*
* @module v3/plugins/code-intelligence/bridges/mincut-bridge
*/
import type {
IMinCutBridge,
DependencyGraph,
SplitConstraints,
} from '../types.js';
/**
* WASM module interface for MinCut operations
*/
interface MinCutWasmModule {
/** Build flow network from graph */
mincut_build_network(
nodeCount: number,
edges: Uint32Array,
capacities: Float32Array,
edgeCount: number
): number;
/** Find min s-t cut using Ford-Fulkerson */
mincut_ford_fulkerson(
networkPtr: number,
source: number,
sink: number
): Float32Array;
/** Multi-way cut using recursive bisection */
mincut_multiway(
networkPtr: number,
terminals: Uint32Array,
numTerminals: number
): Uint32Array;
/** Spectral partitioning */
mincut_spectral_partition(
networkPtr: number,
numPartitions: number,
weights: Float32Array
): Uint32Array;
/** Free network */
mincut_free(networkPtr: number): void;
/** Memory management */
alloc(size: number): number;
dealloc(ptr: number, size: number): void;
memory: WebAssembly.Memory;
}
/**
* MinCut Bridge Implementation
*/
export class MinCutBridge implements IMinCutBridge {
// WASM module for future performance optimization (currently uses JS fallback)
private wasmModule: MinCutWasmModule | null = null;
private initialized = false;
/**
* Initialize the WASM module
*/
async initialize(): Promise<void> {
if (this.initialized) return;
try {
// Dynamic import of WASM module
this.wasmModule = await this.loadWasmModule();
this.initialized = true;
} catch {
// Fallback to pure JS implementation
console.warn('WASM MinCut module not available, using JS fallback');
this.wasmModule = null;
this.initialized = true;
}
}
/**
* Check if initialized
*/
isInitialized(): boolean {
return this.initialized;
}
/**
* Find optimal module boundaries using MinCut
*/
async findOptimalCuts(
graph: DependencyGraph,
numModules: number,
constraints: SplitConstraints
): Promise<Map<string, number>> {
if (!this.initialized) {
await this.initialize();
}
const partition = new Map<string, number>();
const nodeCount = graph.nodes.length;
if (nodeCount === 0 || numModules < 2) {
// All nodes in single partition
for (const node of graph.nodes) {
partition.set(node.id, 0);
}
return partition;
}
// Create node lookup
const nodeMap = new Map<string, number>();
const indexMap = new Map<number, string>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
indexMap.set(index, node.id);
});
// Apply constraints for preserved boundaries
const fixed = new Map<number, number>();
if (constraints.preserveBoundaries) {
for (let i = 0; i < constraints.preserveBoundaries.length && i < numModules; i++) {
const boundary = constraints.preserveBoundaries[i];
if (boundary) {
const nodeIdx = nodeMap.get(boundary);
if (nodeIdx !== undefined) {
fixed.set(nodeIdx, i);
}
}
}
}
// Build adjacency matrix with weights
const weights: number[][] = Array.from({ length: nodeCount }, () =>
Array(nodeCount).fill(0)
);
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
weights[fromIdx]![toIdx] = edge.weight;
weights[toIdx]![fromIdx] = edge.weight; // Symmetric for partitioning
}
}
// Use spectral partitioning (JS fallback)
const partitionArray = this.spectralPartition(
weights,
numModules,
fixed,
constraints
);
// Convert to map
for (let i = 0; i < nodeCount; i++) {
const nodeId = indexMap.get(i);
const part = partitionArray[i];
if (nodeId && part !== undefined) {
partition.set(nodeId, part);
}
}
// Apply keepTogether constraints
if (constraints.keepTogether) {
for (const group of constraints.keepTogether) {
if (group.length > 0) {
const firstIdx = nodeMap.get(group[0] ?? '');
const firstPart = firstIdx !== undefined ? partitionArray[firstIdx] : undefined;
if (firstPart !== undefined) {
for (const nodeId of group) {
partition.set(nodeId, firstPart);
}
}
}
}
}
return partition;
}
/**
* Calculate cut weight for a given partition
*/
async calculateCutWeight(
graph: DependencyGraph,
partition: Map<string, number>
): Promise<number> {
if (!this.initialized) {
await this.initialize();
}
let cutWeight = 0;
for (const edge of graph.edges) {
const fromPart = partition.get(edge.from);
const toPart = partition.get(edge.to);
if (fromPart !== undefined && toPart !== undefined && fromPart !== toPart) {
cutWeight += edge.weight;
}
}
return cutWeight;
}
/**
* Find minimum s-t cut
*/
async minSTCut(
graph: DependencyGraph,
source: string,
sink: string
): Promise<{
cutValue: number;
cutEdges: Array<{ from: string; to: string }>;
sourceSet: string[];
sinkSet: string[];
}> {
if (!this.initialized) {
await this.initialize();
}
const nodeCount = graph.nodes.length;
// Create node lookup
const nodeMap = new Map<string, number>();
const indexMap = new Map<number, string>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
indexMap.set(index, node.id);
});
const sourceIdx = nodeMap.get(source);
const sinkIdx = nodeMap.get(sink);
if (sourceIdx === undefined || sinkIdx === undefined) {
return {
cutValue: 0,
cutEdges: [],
sourceSet: [],
sinkSet: graph.nodes.map(n => n.id),
};
}
// Build capacity matrix
const capacity: number[][] = Array.from({ length: nodeCount }, () =>
Array(nodeCount).fill(0)
);
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
capacity[fromIdx]![toIdx] = edge.weight;
}
}
// Ford-Fulkerson with BFS (Edmonds-Karp)
const { maxFlow, residual } = this.edmondsKarp(capacity, sourceIdx, sinkIdx);
// Find source set using BFS on residual graph
const sourceSet = new Set<number>();
const queue = [sourceIdx];
sourceSet.add(sourceIdx);
while (queue.length > 0) {
const current = queue.shift()!;
for (let next = 0; next < nodeCount; next++) {
if (!sourceSet.has(next) && (residual[current]?.[next] ?? 0) > 0) {
sourceSet.add(next);
queue.push(next);
}
}
}
// Find cut edges
const cutEdges: Array<{ from: string; to: string }> = [];
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
if (sourceSet.has(fromIdx) && !sourceSet.has(toIdx)) {
cutEdges.push({ from: edge.from, to: edge.to });
}
}
}
// Convert sets to arrays
const sourceSetNodes: string[] = [];
const sinkSetNodes: string[] = [];
for (let i = 0; i < nodeCount; i++) {
const nodeId = indexMap.get(i);
if (nodeId) {
if (sourceSet.has(i)) {
sourceSetNodes.push(nodeId);
} else {
sinkSetNodes.push(nodeId);
}
}
}
return {
cutValue: maxFlow,
cutEdges,
sourceSet: sourceSetNodes,
sinkSet: sinkSetNodes,
};
}
/**
* Multi-way cut for module splitting
*/
async multiWayCut(
graph: DependencyGraph,
terminals: string[],
_weights: Map<string, number>
): Promise<{
cutValue: number;
partitions: Map<string, number>;
}> {
if (!this.initialized) {
await this.initialize();
}
const numTerminals = terminals.length;
if (numTerminals < 2) {
const partitions = new Map<string, number>();
for (const node of graph.nodes) {
partitions.set(node.id, 0);
}
return { cutValue: 0, partitions };
}
// Create node lookup
const nodeMap = new Map<string, number>();
graph.nodes.forEach((node, index) => {
nodeMap.set(node.id, index);
});
// Get terminal indices
const terminalIndices = terminals
.map(t => nodeMap.get(t))
.filter((idx): idx is number => idx !== undefined);
if (terminalIndices.length < 2) {
const partitions = new Map<string, number>();
for (const node of graph.nodes) {
partitions.set(node.id, 0);
}
return { cutValue: 0, partitions };
}
// Use isolating cuts algorithm
// Assign each node to the nearest terminal
const partitions = new Map<string, number>();
const distances = this.computeDistances(graph, terminalIndices, nodeMap);
for (const node of graph.nodes) {
const nodeIdx = nodeMap.get(node.id);
if (nodeIdx === undefined) continue;
let minDist = Infinity;
let minTerminal = 0;
for (let t = 0; t < terminalIndices.length; t++) {
const dist = distances.get(`${nodeIdx}-${t}`) ?? Infinity;
if (dist < minDist) {
minDist = dist;
minTerminal = t;
}
}
partitions.set(node.id, minTerminal);
}
// Calculate cut value
const cutValue = await this.calculateCutWeight(graph, partitions);
return { cutValue, partitions };
}
// ============================================================================
// Private Helper Methods
// ============================================================================
/**
* Load WASM module dynamically
*/
private async loadWasmModule(): Promise<MinCutWasmModule> {
throw new Error('WASM module loading not implemented');
}
/**
* Spectral partitioning using Fiedler vector
*/
private spectralPartition(
weights: number[][],
numPartitions: number,
fixed: Map<number, number>,
_constraints: SplitConstraints
): number[] {
const n = weights.length;
const partition = new Array(n).fill(0);
if (n === 0) return partition;
// Apply fixed partitions
for (const [node, part] of fixed) {
partition[node] = part;
}
// If all fixed, return
if (fixed.size >= n) return partition;
// Compute Laplacian
const laplacian: number[][] = Array.from({ length: n }, () =>
Array(n).fill(0)
);
for (let i = 0; i < n; i++) {
let degree = 0;
for (let j = 0; j < n; j++) {
if (i !== j) {
const w = (weights[i]?.[j] ?? 0) + (weights[j]?.[i] ?? 0);
laplacian[i]![j] = -w;
degree += w;
}
}
laplacian[i]![i] = degree;
}
// Power iteration to find Fiedler vector (second smallest eigenvector)
// Simplified: use random initialization and iterate
const fiedler = new Array(n).fill(0).map(() => Math.random() - 0.5);
// Normalize
let norm = Math.sqrt(fiedler.reduce((sum, v) => sum + v * v, 0));
for (let i = 0; i < n; i++) {
fiedler[i] = (fiedler[i] ?? 0) / norm;
}
// Iterate
for (let iter = 0; iter < 50; iter++) {
// Multiply by Laplacian
const newFiedler = new Array(n).fill(0);
for (let i = 0; i < n; i++) {
for (let j = 0; j < n; j++) {
newFiedler[i] += (laplacian[i]?.[j] ?? 0) * (fiedler[j] ?? 0);
}
}
// Orthogonalize against constant vector
const mean = newFiedler.reduce((a, b) => a + b, 0) / n;
for (let i = 0; i < n; i++) {
newFiedler[i] = (newFiedler[i] ?? 0) - mean;
}
// Normalize
norm = Math.sqrt(newFiedler.reduce((sum, v) => sum + v * v, 0));
if (norm > 0) {
for (let i = 0; i < n; i++) {
fiedler[i] = (newFiedler[i] ?? 0) / norm;
}
}
}
// Partition based on Fiedler vector
if (numPartitions === 2) {
// Simple bisection
for (let i = 0; i < n; i++) {
if (!fixed.has(i)) {
partition[i] = (fiedler[i] ?? 0) >= 0 ? 0 : 1;
}
}
} else {
// K-means clustering on Fiedler values
const sorted = fiedler
.map((v, i) => ({ value: v, index: i }))
.filter(item => !fixed.has(item.index))
.sort((a, b) => (a.value ?? 0) - (b.value ?? 0));
const binSize = Math.ceil(sorted.length / numPartitions);
for (let i = 0; i < sorted.length; i++) {
const item = sorted[i];
if (item) {
partition[item.index] = Math.min(Math.floor(i / binSize), numPartitions - 1);
}
}
}
return partition;
}
/**
* Edmonds-Karp algorithm (Ford-Fulkerson with BFS)
*/
private edmondsKarp(
capacity: number[][],
source: number,
sink: number
): { maxFlow: number; residual: number[][] } {
const n = capacity.length;
const residual: number[][] = capacity.map(row => [...row]);
let maxFlow = 0;
// BFS to find augmenting path
const bfs = (): number[] | null => {
const parent = new Array(n).fill(-1);
const visited = new Array(n).fill(false);
const queue = [source];
visited[source] = true;
while (queue.length > 0) {
const current = queue.shift()!;
if (current === sink) {
// Reconstruct path
const path: number[] = [];
let node = sink;
while (node !== source) {
path.unshift(node);
node = parent[node] ?? source;
}
path.unshift(source);
return path;
}
for (let next = 0; next < n; next++) {
if (!visited[next] && (residual[current]?.[next] ?? 0) > 0) {
visited[next] = true;
parent[next] = current;
queue.push(next);
}
}
}
return null;
};
// Find augmenting paths
let path = bfs();
while (path !== null) {
// Find minimum capacity along path
let minCap = Infinity;
for (let i = 0; i < path.length - 1; i++) {
const from = path[i];
const to = path[i + 1];
if (from !== undefined && to !== undefined) {
minCap = Math.min(minCap, residual[from]?.[to] ?? 0);
}
}
// Update residual capacities
for (let i = 0; i < path.length - 1; i++) {
const from = path[i];
const to = path[i + 1];
if (from !== undefined && to !== undefined) {
residual[from]![to] = (residual[from]?.[to] ?? 0) - minCap;
residual[to]![from] = (residual[to]?.[from] ?? 0) + minCap;
}
}
maxFlow += minCap;
path = bfs();
}
return { maxFlow, residual };
}
/**
* Compute distances from terminals to all nodes
*/
private computeDistances(
graph: DependencyGraph,
terminalIndices: number[],
nodeMap: Map<string, number>
): Map<string, number> {
const distances = new Map<string, number>();
const nodeCount = graph.nodes.length;
// Build adjacency list with weights
const adj: Map<number, Array<{ to: number; weight: number }>> = new Map();
for (let i = 0; i < nodeCount; i++) {
adj.set(i, []);
}
for (const edge of graph.edges) {
const fromIdx = nodeMap.get(edge.from);
const toIdx = nodeMap.get(edge.to);
if (fromIdx !== undefined && toIdx !== undefined) {
adj.get(fromIdx)?.push({ to: toIdx, weight: edge.weight });
adj.get(toIdx)?.push({ to: fromIdx, weight: edge.weight });
}
}
// Dijkstra from each terminal
for (let t = 0; t < terminalIndices.length; t++) {
const terminal = terminalIndices[t];
if (terminal === undefined) continue;
const dist = new Array(nodeCount).fill(Infinity);
dist[terminal] = 0;
const pq: Array<{ node: number; dist: number }> = [{ node: terminal, dist: 0 }];
while (pq.length > 0) {
pq.sort((a, b) => a.dist - b.dist);
const current = pq.shift()!;
if (current.dist > (dist[current.node] ?? Infinity)) continue;
for (const neighbor of adj.get(current.node) ?? []) {
const newDist = current.dist + (1 / Math.max(neighbor.weight, 0.1)); // Inverse weight as distance
if (newDist < (dist[neighbor.to] ?? Infinity)) {
dist[neighbor.to] = newDist;
pq.push({ node: neighbor.to, dist: newDist });
}
}
}
// Store distances
for (let i = 0; i < nodeCount; i++) {
distances.set(`${i}-${t}`, dist[i] ?? Infinity);
}
}
return distances;
}
}
/**
* Create and export default bridge instance
*/
export function createMinCutBridge(): IMinCutBridge {
return new MinCutBridge();
}
export default MinCutBridge;
+199
View File
@@ -0,0 +1,199 @@
/**
* Code Intelligence Plugin for Claude Flow V3
*
* A comprehensive code intelligence plugin combining graph neural networks
* for code structure analysis with ultra-fast vector search for semantic
* code similarity.
*
* Features:
* - Semantic code search
* - Architecture analysis and drift detection
* - Refactoring impact prediction using GNN
* - Module splitting suggestions using MinCut
* - Pattern learning from code history
*
* Based on ADR-035: Advanced Code Intelligence Plugin
*
* @module @claude-flow/plugin-code-intelligence
*/
// Export types
export * from './types.js';
// Export bridges
export { GNNBridge, createGNNBridge } from './bridges/gnn-bridge.js';
export { MinCutBridge, createMinCutBridge } from './bridges/mincut-bridge.js';
// Export MCP tools
export {
semanticSearchTool,
architectureAnalyzeTool,
refactorImpactTool,
splitSuggestTool,
learnPatternsTool,
codeIntelligenceTools,
toolHandlers,
createToolContext,
} from './mcp-tools.js';
export type { MCPTool, ToolContext, MCPToolResult } from './mcp-tools.js';
// Import for plugin creation
import { codeIntelligenceTools } from './mcp-tools.js';
import { createGNNBridge } from './bridges/gnn-bridge.js';
import { createMinCutBridge } from './bridges/mincut-bridge.js';
import type {
CodeIntelligenceConfig,
IGNNBridge,
IMinCutBridge,
} from './types.js';
import { DEFAULT_CONFIG } from './types.js';
/**
* Plugin metadata
*/
export const pluginMetadata = {
name: '@claude-flow/plugin-code-intelligence',
version: '3.0.0-alpha.1',
description: 'Advanced code intelligence plugin for semantic search, architecture analysis, and refactoring',
author: 'Claude Flow Team',
category: 'code-intelligence',
keywords: ['code', 'intelligence', 'semantic-search', 'architecture', 'refactoring'],
homepage: 'https://github.com/ruvnet/claude-flow',
repository: 'https://github.com/ruvnet/claude-flow.git',
};
/**
* Plugin state
*/
export type PluginState = 'uninitialized' | 'initializing' | 'ready' | 'error' | 'shutdown';
/**
* Code Intelligence Plugin Class
*/
export class CodeIntelligencePlugin {
private state: PluginState = 'uninitialized';
private config: CodeIntelligenceConfig;
private gnnBridge: IGNNBridge | null = null;
private mincutBridge: IMinCutBridge | null = null;
constructor(config: Partial<CodeIntelligenceConfig> = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
}
/**
* Get plugin metadata
*/
getMetadata() {
return pluginMetadata;
}
/**
* Get current state
*/
getState(): PluginState {
return this.state;
}
/**
* Initialize the plugin
*/
async initialize(): Promise<void> {
if (this.state === 'ready') return;
this.state = 'initializing';
try {
// Initialize WASM bridges
this.gnnBridge = createGNNBridge(this.config.search.embeddingDimension);
this.mincutBridge = createMinCutBridge();
await Promise.all([
this.gnnBridge.initialize(),
this.mincutBridge.initialize(),
]);
this.state = 'ready';
} catch (error) {
this.state = 'error';
throw error;
}
}
/**
* Shutdown the plugin
*/
async shutdown(): Promise<void> {
this.state = 'shutdown';
this.gnnBridge = null;
this.mincutBridge = null;
}
/**
* Get MCP tools provided by this plugin
*/
getMCPTools() {
return codeIntelligenceTools;
}
/**
* Get tool context for execution
*/
getToolContext() {
if (!this.gnnBridge || !this.mincutBridge) {
throw new Error('Plugin not initialized');
}
const store = new Map<string, unknown>();
const blockedPatterns = this.config.security.blockedPatterns.map(
p => new RegExp(p)
);
return {
get: <T>(key: string) => store.get(key) as T | undefined,
set: <T>(key: string, value: T) => { store.set(key, value); },
bridges: {
gnn: this.gnnBridge,
mincut: this.mincutBridge,
},
config: {
allowedRoots: this.config.security.allowedRoots,
blockedPatterns,
maskSecrets: this.config.security.maskSecrets,
},
};
}
/**
* Get configuration
*/
getConfig(): CodeIntelligenceConfig {
return this.config;
}
/**
* Update configuration
*/
updateConfig(config: Partial<CodeIntelligenceConfig>): void {
this.config = {
...this.config,
...config,
search: { ...this.config.search, ...config.search },
architecture: { ...this.config.architecture, ...config.architecture },
refactoring: { ...this.config.refactoring, ...config.refactoring },
security: { ...this.config.security, ...config.security },
};
}
}
/**
* Create plugin instance
*/
export function createPlugin(config?: Partial<CodeIntelligenceConfig>): CodeIntelligencePlugin {
return new CodeIntelligencePlugin(config);
}
/**
* Default export
*/
export default CodeIntelligencePlugin;
File diff suppressed because it is too large Load Diff
+966
View File
@@ -0,0 +1,966 @@
/**
* Code Intelligence Plugin - Type Definitions
*
* Core types for advanced code analysis including semantic search,
* architecture analysis, refactoring impact prediction, module splitting,
* and pattern learning.
*
* Based on ADR-035: Advanced Code Intelligence Plugin
*
* @module v3/plugins/code-intelligence/types
*/
import { z } from 'zod';
// ============================================================================
// Language & Search Types
// ============================================================================
/**
* Supported programming languages
*/
export const Language = z.enum([
'typescript',
'javascript',
'python',
'java',
'go',
'rust',
'cpp',
'csharp',
'ruby',
'php',
'swift',
'kotlin',
'scala',
]);
export type Language = z.infer<typeof Language>;
/**
* Language tiers for support level
*/
export const LanguageTier: Record<Language, 'tier1' | 'tier2' | 'tier3'> = {
typescript: 'tier1',
javascript: 'tier1',
python: 'tier2',
java: 'tier2',
go: 'tier3',
rust: 'tier3',
cpp: 'tier3',
csharp: 'tier2',
ruby: 'tier2',
php: 'tier2',
swift: 'tier3',
kotlin: 'tier3',
scala: 'tier2',
};
/**
* Search type for semantic code search
*/
export const SearchType = z.enum([
'semantic', // Meaning-based search
'structural', // AST pattern matching
'clone', // Code clone detection
'api_usage', // API usage pattern search
]);
export type SearchType = z.infer<typeof SearchType>;
/**
* Code search result
*/
export interface CodeSearchResult {
/** File path */
readonly filePath: string;
/** Line number */
readonly lineNumber: number;
/** Code snippet */
readonly snippet: string;
/** Match type */
readonly matchType: SearchType;
/** Relevance score (0-1) */
readonly score: number;
/** Context (surrounding lines) */
readonly context: string;
/** Symbol name if applicable */
readonly symbol?: string;
/** Language of the file */
readonly language: Language;
/** Explanation of why this matched */
readonly explanation: string;
}
/**
* Semantic search result
*/
export interface SemanticSearchResult {
/** Success status */
readonly success: boolean;
/** Search query */
readonly query: string;
/** Search type used */
readonly searchType: SearchType;
/** Results */
readonly results: CodeSearchResult[];
/** Total matches (before limit) */
readonly totalMatches: number;
/** Search scope used */
readonly scope: SearchScope;
/** Execution time in ms */
readonly durationMs: number;
}
/**
* Search scope configuration
*/
export interface SearchScope {
/** Paths to include */
readonly paths?: string[];
/** Languages to search */
readonly languages?: Language[];
/** Exclude test files */
readonly excludeTests?: boolean;
/** Exclude node_modules and similar */
readonly excludeVendor?: boolean;
/** File patterns to exclude */
readonly excludePatterns?: string[];
}
// ============================================================================
// Architecture Analysis Types
// ============================================================================
/**
* Analysis types for architecture
*/
export const AnalysisType = z.enum([
'dependency_graph',
'layer_violations',
'circular_deps',
'component_coupling',
'module_cohesion',
'dead_code',
'api_surface',
'architectural_drift',
]);
export type AnalysisType = z.infer<typeof AnalysisType>;
/**
* Output format for architecture analysis
*/
export const OutputFormat = z.enum([
'json',
'graphviz',
'mermaid',
]);
export type OutputFormat = z.infer<typeof OutputFormat>;
/**
* Dependency node
*/
export interface DependencyNode {
/** Node ID (file path or module name) */
readonly id: string;
/** Node label */
readonly label: string;
/** Node type */
readonly type: 'file' | 'module' | 'package' | 'class' | 'function';
/** Language */
readonly language?: Language;
/** Lines of code */
readonly loc?: number;
/** Complexity score */
readonly complexity?: number;
/** Layer (if applicable) */
readonly layer?: string;
}
/**
* Dependency edge
*/
export interface DependencyEdge {
/** Source node ID */
readonly from: string;
/** Target node ID */
readonly to: string;
/** Edge type */
readonly type: 'import' | 'extends' | 'implements' | 'uses' | 'calls';
/** Weight (import count or call frequency) */
readonly weight: number;
/** Is dynamic import */
readonly dynamic?: boolean;
}
/**
* Dependency graph
*/
export interface DependencyGraph {
/** All nodes */
readonly nodes: DependencyNode[];
/** All edges */
readonly edges: DependencyEdge[];
/** Graph metadata */
readonly metadata: {
totalNodes: number;
totalEdges: number;
avgDegree: number;
maxDepth: number;
};
}
/**
* Layer violation
*/
export interface LayerViolation {
/** Source module */
readonly source: string;
/** Target module */
readonly target: string;
/** Source layer */
readonly sourceLayer: string;
/** Target layer */
readonly targetLayer: string;
/** Violation type */
readonly violationType: 'upward' | 'skip' | 'cross';
/** Severity */
readonly severity: 'low' | 'medium' | 'high';
/** Suggested fix */
readonly suggestedFix: string;
}
/**
* Circular dependency
*/
export interface CircularDependency {
/** Cycle path (node IDs) */
readonly cycle: string[];
/** Cycle length */
readonly length: number;
/** Severity */
readonly severity: 'low' | 'medium' | 'high';
/** Suggested break point */
readonly suggestedBreakPoint: string;
}
/**
* Component coupling metrics
*/
export interface CouplingMetrics {
/** Component ID */
readonly componentId: string;
/** Afferent coupling (incoming dependencies) */
readonly afferentCoupling: number;
/** Efferent coupling (outgoing dependencies) */
readonly efferentCoupling: number;
/** Instability (Ce / (Ca + Ce)) */
readonly instability: number;
/** Abstractness */
readonly abstractness: number;
/** Distance from main sequence */
readonly distanceFromMain: number;
/** Is in zone of pain (high stability, low abstractness) */
readonly inZoneOfPain: boolean;
/** Is in zone of uselessness (low stability, high abstractness) */
readonly inZoneOfUselessness: boolean;
}
/**
* Module cohesion metrics
*/
export interface CohesionMetrics {
/** Module ID */
readonly moduleId: string;
/** Lack of Cohesion in Methods (LCOM) */
readonly lcom: number;
/** Tight Class Cohesion (TCC) */
readonly tcc: number;
/** Loose Class Cohesion (LCC) */
readonly lcc: number;
/** Cohesion level */
readonly level: 'high' | 'medium' | 'low';
/** Suggestions for improvement */
readonly suggestions: string[];
}
/**
* Dead code finding
*/
export interface DeadCodeFinding {
/** File path */
readonly filePath: string;
/** Symbol name */
readonly symbol: string;
/** Symbol type */
readonly symbolType: 'function' | 'class' | 'variable' | 'import' | 'export';
/** Line number */
readonly lineNumber: number;
/** Confidence (0-1) */
readonly confidence: number;
/** Reason */
readonly reason: string;
/** Is exported */
readonly isExported: boolean;
}
/**
* API surface element
*/
export interface APISurfaceElement {
/** Symbol name */
readonly name: string;
/** Symbol type */
readonly type: 'function' | 'class' | 'interface' | 'type' | 'constant';
/** File path */
readonly filePath: string;
/** Export type */
readonly exportType: 'named' | 'default' | 're-export';
/** Usage count */
readonly usageCount: number;
/** Is deprecated */
readonly deprecated: boolean;
/** Documentation coverage */
readonly documented: boolean;
}
/**
* Architectural drift
*/
export interface ArchitecturalDrift {
/** Component */
readonly component: string;
/** Baseline hash */
readonly baselineRef: string;
/** Current hash */
readonly currentRef: string;
/** Drift type */
readonly driftType: 'dependency_added' | 'dependency_removed' | 'layer_change' | 'coupling_increase';
/** Description */
readonly description: string;
/** Severity */
readonly severity: 'low' | 'medium' | 'high';
}
/**
* Architecture analysis result
*/
export interface ArchitectureAnalysisResult {
/** Success status */
readonly success: boolean;
/** Root path analyzed */
readonly rootPath: string;
/** Analyses performed */
readonly analyses: AnalysisType[];
/** Dependency graph */
readonly dependencyGraph?: DependencyGraph;
/** Layer violations */
readonly layerViolations?: LayerViolation[];
/** Circular dependencies */
readonly circularDeps?: CircularDependency[];
/** Coupling metrics */
readonly couplingMetrics?: CouplingMetrics[];
/** Cohesion metrics */
readonly cohesionMetrics?: CohesionMetrics[];
/** Dead code findings */
readonly deadCode?: DeadCodeFinding[];
/** API surface */
readonly apiSurface?: APISurfaceElement[];
/** Architectural drift */
readonly drift?: ArchitecturalDrift[];
/** Summary */
readonly summary: {
totalFiles: number;
totalModules: number;
healthScore: number;
issues: number;
warnings: number;
};
/** Execution time in ms */
readonly durationMs: number;
}
// ============================================================================
// Refactoring Impact Types
// ============================================================================
/**
* Change type for refactoring
*/
export const ChangeType = z.enum([
'rename',
'move',
'delete',
'extract',
'inline',
]);
export type ChangeType = z.infer<typeof ChangeType>;
/**
* Proposed change
*/
export interface ProposedChange {
/** File to change */
readonly file: string;
/** Change type */
readonly type: ChangeType;
/** Change details */
readonly details: {
/** Original name (for rename) */
oldName?: string;
/** New name (for rename) */
newName?: string;
/** New location (for move) */
newPath?: string;
/** Symbol to extract (for extract) */
symbol?: string;
/** Target file (for extract) */
targetFile?: string;
};
}
/**
* Impact on a file
*/
export interface FileImpact {
/** File path */
readonly filePath: string;
/** Impact type */
readonly impactType: 'direct' | 'indirect' | 'transitive';
/** Requires modification */
readonly requiresChange: boolean;
/** Changes needed */
readonly changesNeeded: string[];
/** Risk level */
readonly risk: 'low' | 'medium' | 'high';
/** Tests affected */
readonly testsAffected: string[];
}
/**
* Refactoring impact result
*/
export interface RefactoringImpactResult {
/** Success status */
readonly success: boolean;
/** Proposed changes */
readonly changes: ProposedChange[];
/** Impacted files */
readonly impactedFiles: FileImpact[];
/** Impact summary */
readonly summary: {
directlyAffected: number;
indirectlyAffected: number;
testsAffected: number;
totalRisk: 'low' | 'medium' | 'high';
};
/** Suggested order of changes */
readonly suggestedOrder: string[];
/** Potential breaking changes */
readonly breakingChanges: string[];
/** Execution time in ms */
readonly durationMs: number;
}
// ============================================================================
// Module Splitting Types
// ============================================================================
/**
* Splitting strategy
*/
export const SplitStrategy = z.enum([
'minimize_coupling',
'balance_size',
'feature_isolation',
]);
export type SplitStrategy = z.infer<typeof SplitStrategy>;
/**
* Split constraints
*/
export interface SplitConstraints {
/** Maximum module size (lines) */
readonly maxModuleSize?: number;
/** Minimum module size (lines) */
readonly minModuleSize?: number;
/** Boundaries to preserve */
readonly preserveBoundaries?: string[];
/** Files that must stay together */
readonly keepTogether?: string[][];
}
/**
* Suggested module
*/
export interface SuggestedModule {
/** Module name */
readonly name: string;
/** Files included */
readonly files: string[];
/** Total lines of code */
readonly loc: number;
/** Internal cohesion score */
readonly cohesion: number;
/** External coupling score */
readonly coupling: number;
/** Public API */
readonly publicApi: string[];
/** Dependencies on other suggested modules */
readonly dependencies: string[];
}
/**
* Module split suggestion result
*/
export interface ModuleSplitResult {
/** Success status */
readonly success: boolean;
/** Target path analyzed */
readonly targetPath: string;
/** Strategy used */
readonly strategy: SplitStrategy;
/** Suggested modules */
readonly modules: SuggestedModule[];
/** Cut edges (dependencies that cross module boundaries) */
readonly cutEdges: Array<{
from: string;
to: string;
weight: number;
}>;
/** Quality metrics */
readonly quality: {
totalCutWeight: number;
avgCohesion: number;
avgCoupling: number;
balanceScore: number;
};
/** Migration steps */
readonly migrationSteps: string[];
/** Execution time in ms */
readonly durationMs: number;
}
// ============================================================================
// Pattern Learning Types
// ============================================================================
/**
* Pattern types to learn
*/
export const PatternType = z.enum([
'bug_patterns',
'refactor_patterns',
'api_patterns',
'test_patterns',
]);
export type PatternType = z.infer<typeof PatternType>;
/**
* Learned pattern
*/
export interface LearnedPattern {
/** Pattern ID */
readonly id: string;
/** Pattern type */
readonly type: PatternType;
/** Pattern description */
readonly description: string;
/** Code before (for refactoring patterns) */
readonly codeBefore?: string;
/** Code after (for refactoring patterns) */
readonly codeAfter?: string;
/** Occurrence count */
readonly occurrences: number;
/** Authors who used this pattern */
readonly authors: string[];
/** Files where pattern appears */
readonly files: string[];
/** Confidence score */
readonly confidence: number;
/** Impact (positive/negative/neutral) */
readonly impact: 'positive' | 'negative' | 'neutral';
/** Suggested action */
readonly suggestedAction?: string;
}
/**
* Pattern learning scope
*/
export interface LearningScope {
/** Git range to analyze */
readonly gitRange?: string;
/** Authors to filter */
readonly authors?: string[];
/** Paths to include */
readonly paths?: string[];
/** Since date */
readonly since?: Date;
/** Until date */
readonly until?: Date;
}
/**
* Pattern learning result
*/
export interface PatternLearningResult {
/** Success status */
readonly success: boolean;
/** Scope used */
readonly scope: LearningScope;
/** Pattern types analyzed */
readonly patternTypes: PatternType[];
/** Learned patterns */
readonly patterns: LearnedPattern[];
/** Summary */
readonly summary: {
commitsAnalyzed: number;
filesAnalyzed: number;
patternsFound: number;
byType: Record<PatternType, number>;
};
/** Recommendations based on patterns */
readonly recommendations: string[];
/** Execution time in ms */
readonly durationMs: number;
}
// ============================================================================
// MCP Tool Input Schemas
// ============================================================================
/**
* Input schema for code/semantic-search
*/
export const SemanticSearchInputSchema = z.object({
query: z.string().min(1).max(5000),
scope: z.object({
paths: z.array(z.string().max(500)).max(100).optional(),
languages: z.array(Language).max(20).optional(),
excludeTests: z.boolean().default(false),
}).optional(),
searchType: SearchType.default('semantic'),
topK: z.number().int().min(1).max(1000).default(10),
});
export type SemanticSearchInput = z.infer<typeof SemanticSearchInputSchema>;
/**
* Input schema for code/architecture-analyze
*/
export const ArchitectureAnalyzeInputSchema = z.object({
rootPath: z.string().max(500).default('.'),
analysis: z.array(AnalysisType).optional(),
baseline: z.string().max(100).optional(),
outputFormat: OutputFormat.optional(),
layers: z.record(z.string(), z.array(z.string())).optional(),
});
export type ArchitectureAnalyzeInput = z.infer<typeof ArchitectureAnalyzeInputSchema>;
/**
* Input schema for code/refactor-impact
*/
export const RefactorImpactInputSchema = z.object({
changes: z.array(z.object({
file: z.string().max(500),
type: ChangeType,
details: z.record(z.string(), z.unknown()).optional(),
})).min(1).max(100),
depth: z.number().int().min(1).max(10).default(3),
includeTests: z.boolean().default(true),
});
export type RefactorImpactInput = z.infer<typeof RefactorImpactInputSchema>;
/**
* Input schema for code/split-suggest
*/
export const SplitSuggestInputSchema = z.object({
targetPath: z.string().max(500),
strategy: SplitStrategy.default('minimize_coupling'),
constraints: z.object({
maxModuleSize: z.number().optional(),
minModuleSize: z.number().optional(),
preserveBoundaries: z.array(z.string()).optional(),
}).optional(),
targetModules: z.number().int().min(2).max(50).optional(),
});
export type SplitSuggestInput = z.infer<typeof SplitSuggestInputSchema>;
/**
* Input schema for code/learn-patterns
*/
export const LearnPatternsInputSchema = z.object({
scope: z.object({
gitRange: z.string().default('HEAD~100..HEAD'),
authors: z.array(z.string()).optional(),
paths: z.array(z.string()).optional(),
}).optional(),
patternTypes: z.array(PatternType).optional(),
minOccurrences: z.number().int().min(1).max(100).default(3),
});
export type LearnPatternsInput = z.infer<typeof LearnPatternsInputSchema>;
// ============================================================================
// Bridge Interfaces
// ============================================================================
/**
* GNN Bridge for code graph analysis
*/
export interface IGNNBridge {
/**
* Build code graph from files
*/
buildCodeGraph(
files: string[],
includeCallGraph: boolean
): Promise<DependencyGraph>;
/**
* Compute node embeddings using GNN
*/
computeNodeEmbeddings(
graph: DependencyGraph,
embeddingDim: number
): Promise<Map<string, Float32Array>>;
/**
* Predict impact of changes using GNN
*/
predictImpact(
graph: DependencyGraph,
changedNodes: string[],
depth: number
): Promise<Map<string, number>>;
/**
* Detect communities in code graph
*/
detectCommunities(
graph: DependencyGraph
): Promise<Map<string, number>>;
/**
* Find similar code patterns
*/
findSimilarPatterns(
graph: DependencyGraph,
patternGraph: DependencyGraph,
threshold: number
): Promise<Array<{ matchId: string; score: number }>>;
/**
* Initialize the WASM module
*/
initialize(): Promise<void>;
/**
* Check if initialized
*/
isInitialized(): boolean;
}
/**
* MinCut Bridge for module splitting
*/
export interface IMinCutBridge {
/**
* Find optimal module boundaries using MinCut
*/
findOptimalCuts(
graph: DependencyGraph,
numModules: number,
constraints: SplitConstraints
): Promise<Map<string, number>>;
/**
* Calculate cut weight for a given partition
*/
calculateCutWeight(
graph: DependencyGraph,
partition: Map<string, number>
): Promise<number>;
/**
* Find minimum s-t cut
*/
minSTCut(
graph: DependencyGraph,
source: string,
sink: string
): Promise<{
cutValue: number;
cutEdges: Array<{ from: string; to: string }>;
sourceSet: string[];
sinkSet: string[];
}>;
/**
* Multi-way cut for module splitting
*/
multiWayCut(
graph: DependencyGraph,
terminals: string[],
weights: Map<string, number>
): Promise<{
cutValue: number;
partitions: Map<string, number>;
}>;
/**
* Initialize the WASM module
*/
initialize(): Promise<void>;
/**
* Check if initialized
*/
isInitialized(): boolean;
}
// ============================================================================
// Configuration Types
// ============================================================================
/**
* Plugin configuration
*/
export interface CodeIntelligenceConfig {
/** Semantic search settings */
search: {
/** Embedding dimension */
embeddingDimension: number;
/** Default top-K results */
defaultTopK: number;
/** Similarity threshold */
similarityThreshold: number;
};
/** Architecture analysis settings */
architecture: {
/** Layer definitions */
layers?: Record<string, string[]>;
/** Maximum graph depth */
maxGraphDepth: number;
/** Include vendor/node_modules */
includeVendor: boolean;
};
/** Refactoring settings */
refactoring: {
/** Default impact depth */
defaultDepth: number;
/** Include test files */
includeTests: boolean;
};
/** Security settings */
security: {
/** Allowed root paths */
allowedRoots: string[];
/** Block sensitive file patterns */
blockedPatterns: string[];
/** Mask secrets in output */
maskSecrets: boolean;
};
}
/**
* Default configuration
*/
export const DEFAULT_CONFIG: CodeIntelligenceConfig = {
search: {
embeddingDimension: 384,
defaultTopK: 10,
similarityThreshold: 0.7,
},
architecture: {
maxGraphDepth: 10,
includeVendor: false,
},
refactoring: {
defaultDepth: 3,
includeTests: true,
},
security: {
allowedRoots: ['.'],
blockedPatterns: [
'\\.env$',
'\\.git/config$',
'credentials',
'secrets?\\.',
'\\.pem$',
'\\.key$',
'id_rsa',
],
maskSecrets: true,
},
};
// ============================================================================
// Error Types
// ============================================================================
/**
* Code intelligence plugin error codes
*/
export const CodeIntelligenceErrorCodes = {
PATH_TRAVERSAL: 'CODE_PATH_TRAVERSAL',
SENSITIVE_FILE: 'CODE_SENSITIVE_FILE',
GRAPH_TOO_LARGE: 'CODE_GRAPH_TOO_LARGE',
ANALYSIS_FAILED: 'CODE_ANALYSIS_FAILED',
PARSER_ERROR: 'CODE_PARSER_ERROR',
WASM_NOT_INITIALIZED: 'CODE_WASM_NOT_INITIALIZED',
LANGUAGE_NOT_SUPPORTED: 'CODE_LANGUAGE_NOT_SUPPORTED',
GIT_ERROR: 'CODE_GIT_ERROR',
} as const;
export type CodeIntelligenceErrorCode = (typeof CodeIntelligenceErrorCodes)[keyof typeof CodeIntelligenceErrorCodes];
/**
* Code intelligence plugin error
*/
export class CodeIntelligenceError extends Error {
public readonly code: CodeIntelligenceErrorCode;
public readonly details?: Record<string, unknown>;
constructor(code: CodeIntelligenceErrorCode, message: string, details?: Record<string, unknown>) {
super(message);
this.name = 'CodeIntelligenceError';
this.code = code;
this.details = details;
}
}
// ============================================================================
// Security Utilities
// ============================================================================
/**
* Secret patterns for masking
*/
export const SECRET_PATTERNS = [
/(['"])(?:api[_-]?key|apikey|secret|password|token|auth)['"]\s*[:=]\s*['"][^'"]+['"]/gi,
/(?:sk|pk)[-_](?:live|test)[-_][a-zA-Z0-9]{24,}/g,
/ghp_[a-zA-Z0-9]{36}/g,
/-----BEGIN (?:RSA |EC )?PRIVATE KEY-----/g,
/xox[baprs]-[a-zA-Z0-9-]+/g,
/AKIA[0-9A-Z]{16}/g,
];
/**
* Mask secrets in code snippet
*/
export function maskSecrets(code: string): string {
let masked = code;
for (const pattern of SECRET_PATTERNS) {
masked = masked.replace(pattern, '[REDACTED]');
}
return masked;
}
@@ -0,0 +1,268 @@
/**
* Code Intelligence Plugin - Bridge Tests
*
* Tests for GNNBridge initialization, lifecycle, and methods
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { GNNBridge } from '../src/bridges/gnn-bridge.js';
// Mock WASM module
vi.mock('../src/bridges/gnn-wasm.js', () => ({
initWasm: vi.fn().mockResolvedValue(undefined),
wasmAvailable: vi.fn().mockReturnValue(false),
}));
describe('GNNBridge', () => {
let bridge: GNNBridge;
beforeEach(() => {
vi.clearAllMocks();
bridge = new GNNBridge();
});
afterEach(() => {
// GNNBridge doesn't have explicit destroy
});
describe('Initialization', () => {
it('should create bridge instance', () => {
expect(bridge).toBeInstanceOf(GNNBridge);
});
it('should not be initialized before init', () => {
expect(bridge.isInitialized()).toBe(false);
});
it('should initialize successfully', async () => {
await bridge.initialize();
expect(bridge.isInitialized()).toBe(true);
});
it('should initialize with custom embedding dimension', async () => {
const customBridge = new GNNBridge(256);
await customBridge.initialize();
expect(customBridge.isInitialized()).toBe(true);
});
it('should handle double initialization gracefully', async () => {
await bridge.initialize();
await bridge.initialize();
expect(bridge.isInitialized()).toBe(true);
});
});
describe('Build Code Graph', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should build code graph from files', async () => {
const files = [
'src/utils.ts',
'src/main.ts',
'src/types.ts',
];
const graph = await bridge.buildCodeGraph(files, true);
expect(graph).toHaveProperty('nodes');
expect(graph).toHaveProperty('edges');
expect(graph).toHaveProperty('metadata');
expect(graph.nodes.length).toBe(3);
});
it('should handle empty file list', async () => {
const graph = await bridge.buildCodeGraph([], false);
expect(graph.nodes.length).toBe(0);
expect(graph.edges.length).toBe(0);
});
it('should detect file types correctly', async () => {
const files = [
'src/component.tsx',
'src/style.css',
'src/config.json',
'src/script.py',
];
const graph = await bridge.buildCodeGraph(files, false);
expect(graph.nodes.length).toBe(4);
// Each node should have detected language
for (const node of graph.nodes) {
expect(node).toHaveProperty('language');
}
});
});
describe('Compute Node Embeddings', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should compute embeddings for graph nodes', async () => {
const files = ['src/a.ts', 'src/b.ts'];
const graph = await bridge.buildCodeGraph(files, false);
const embeddings = await bridge.computeNodeEmbeddings(graph, 64);
expect(embeddings.size).toBe(2);
expect(embeddings.has('src/a.ts')).toBe(true);
expect(embeddings.has('src/b.ts')).toBe(true);
});
it('should return embeddings of correct dimension', async () => {
const files = ['src/test.ts'];
const graph = await bridge.buildCodeGraph(files, false);
const embeddingDim = 128;
const embeddings = await bridge.computeNodeEmbeddings(graph, embeddingDim);
const embedding = embeddings.get('src/test.ts');
expect(embedding).toBeDefined();
expect(embedding!.length).toBe(embeddingDim);
});
it('should handle empty graph', async () => {
const graph = await bridge.buildCodeGraph([], false);
const embeddings = await bridge.computeNodeEmbeddings(graph, 64);
expect(embeddings.size).toBe(0);
});
});
describe('Impact Prediction', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should predict impact of changes', async () => {
const files = [
'src/core.ts',
'src/utils.ts',
'src/api.ts',
];
const graph = await bridge.buildCodeGraph(files, true);
// predictImpact returns Map<string, number> with node impact scores
const impact = await bridge.predictImpact(graph, ['src/core.ts'], 3);
expect(impact).toBeInstanceOf(Map);
expect(impact.has('src/core.ts')).toBe(true);
});
it('should handle empty changed files', async () => {
const files = ['src/a.ts', 'src/b.ts'];
const graph = await bridge.buildCodeGraph(files, false);
const impact = await bridge.predictImpact(graph, [], 3);
expect(impact).toBeInstanceOf(Map);
expect(impact.size).toBe(0);
});
});
describe('Community Detection', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should detect communities in code graph', async () => {
const files = [
'src/auth/login.ts',
'src/auth/logout.ts',
'src/data/fetch.ts',
'src/data/save.ts',
];
const graph = await bridge.buildCodeGraph(files, true);
// detectCommunities returns Map<string, number> (nodeId -> communityId)
const communities = await bridge.detectCommunities(graph);
expect(communities).toBeInstanceOf(Map);
expect(communities.size).toBe(4);
});
it('should handle single node graph', async () => {
const files = ['src/single.ts'];
const graph = await bridge.buildCodeGraph(files, false);
const communities = await bridge.detectCommunities(graph);
expect(communities).toBeInstanceOf(Map);
expect(communities.size).toBe(1);
});
});
describe('Pattern Matching', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should find similar patterns', async () => {
const files = [
'src/userService.ts',
'src/orderService.ts',
'src/productService.ts',
];
const graph = await bridge.buildCodeGraph(files, true);
// Create a pattern graph to search for
const patternGraph = await bridge.buildCodeGraph(['src/userService.ts'], false);
// findSimilarPatterns expects (graph, patternGraph, threshold)
const patterns = await bridge.findSimilarPatterns(graph, patternGraph, 0.5);
expect(Array.isArray(patterns)).toBe(true);
for (const pattern of patterns) {
expect(pattern).toHaveProperty('matchId');
expect(pattern).toHaveProperty('score');
}
});
});
describe('JavaScript Fallback', () => {
it('should work without WASM', async () => {
const fallbackBridge = new GNNBridge();
await fallbackBridge.initialize();
const files = ['src/test.ts'];
const graph = await fallbackBridge.buildCodeGraph(files, false);
expect(graph.nodes.length).toBe(1);
});
});
describe('Performance', () => {
beforeEach(async () => {
await bridge.initialize();
});
it('should handle large file lists efficiently', async () => {
const files = Array(100).fill(null).map((_, i) => `src/file${i}.ts`);
const start = performance.now();
const graph = await bridge.buildCodeGraph(files, true);
const duration = performance.now() - start;
expect(graph.nodes.length).toBe(100);
expect(duration).toBeLessThan(5000); // Should complete in < 5 seconds
});
});
describe('Memory Management', () => {
it('should handle multiple graph operations', async () => {
await bridge.initialize();
for (let i = 0; i < 5; i++) {
const files = Array(20).fill(null).map((_, j) => `src/module${i}/file${j}.ts`);
const graph = await bridge.buildCodeGraph(files, true);
await bridge.computeNodeEmbeddings(graph, 64);
}
expect(bridge.isInitialized()).toBe(true);
});
});
});
@@ -0,0 +1,667 @@
/**
* Code Intelligence Plugin - MCP Tools Tests
*
* Tests for MCP tool handlers with mock data
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
semanticSearchTool,
architectureAnalyzeTool,
refactorImpactTool,
splitSuggestTool,
learnPatternsTool,
codeIntelligenceTools,
getTool,
getToolNames,
} from '../src/mcp-tools.js';
// Mock bridges
vi.mock('../src/bridges/hnsw-bridge.js', () => ({
CodeHNSWBridge: vi.fn().mockImplementation(() => ({
initialized: false,
initialize: vi.fn().mockResolvedValue(undefined),
searchSemantic: vi.fn().mockResolvedValue([
{
id: 'result-1',
path: 'src/auth/login.ts',
content: 'async function login(username, password) { ... }',
score: 0.92,
language: 'typescript',
},
{
id: 'result-2',
path: 'src/auth/session.ts',
content: 'function createSession(user) { ... }',
score: 0.85,
language: 'typescript',
},
]),
count: vi.fn().mockResolvedValue(5000),
})),
}));
vi.mock('../src/bridges/gnn-bridge.js', () => ({
CodeGNNBridge: vi.fn().mockImplementation(() => ({
initialized: false,
initialize: vi.fn().mockResolvedValue(undefined),
analyzeArchitecture: vi.fn().mockResolvedValue({
components: [
{ name: 'AuthModule', type: 'module', files: 5, dependencies: 3 },
{ name: 'UserService', type: 'service', files: 3, dependencies: 2 },
],
metrics: {
modularity: 0.72,
coupling: 0.35,
cohesion: 0.68,
},
issues: [
{ type: 'circular_dependency', components: ['AuthModule', 'UserService'], severity: 'medium' },
],
}),
analyzeRefactorImpact: vi.fn().mockResolvedValue({
directImpact: ['src/user.ts', 'src/auth.ts'],
indirectImpact: ['src/api/routes.ts', 'tests/user.test.ts'],
riskLevel: 'medium',
breakingChanges: ['UserService.getById signature changed'],
}),
suggestSplit: vi.fn().mockResolvedValue([
{
file: 'src/utils.ts',
reason: 'File exceeds 500 lines with multiple responsibilities',
suggestedSplits: [
{ name: 'string-utils.ts', functions: ['capitalize', 'truncate', 'slugify'] },
{ name: 'date-utils.ts', functions: ['formatDate', 'parseDate', 'addDays'] },
],
},
]),
learnPatterns: vi.fn().mockResolvedValue({
patterns: [
{ name: 'Repository Pattern', occurrences: 12, confidence: 0.88 },
{ name: 'Factory Pattern', occurrences: 5, confidence: 0.75 },
],
antiPatterns: [
{ name: 'God Object', files: ['src/app.ts'], severity: 'high' },
],
}),
})),
}));
// Mock context for testing
const createMockContext = (overrides = {}) => ({
logger: {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
},
userId: 'test-user',
...overrides,
});
describe('Code Intelligence MCP Tools', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('Tool Registry', () => {
it('should export all 5 tools', () => {
expect(codeIntelligenceTools).toHaveLength(5);
});
it('should have correct tool names', () => {
const toolNames = codeIntelligenceTools.map(t => t.name);
expect(toolNames).toContain('code/semantic-search');
expect(toolNames).toContain('code/architecture-analyze');
expect(toolNames).toContain('code/refactor-impact');
expect(toolNames).toContain('code/split-suggest');
expect(toolNames).toContain('code/learn-patterns');
});
it('should have category code-intelligence', () => {
for (const tool of codeIntelligenceTools) {
expect(tool.category).toBe('code-intelligence');
}
});
it('should have version 0.1.0', () => {
for (const tool of codeIntelligenceTools) {
expect(tool.version).toBe('0.1.0');
}
});
it('should get tool by name', () => {
const tool = getTool('code/semantic-search');
expect(tool).toBeDefined();
expect(tool?.name).toBe('code/semantic-search');
});
it('should return undefined for unknown tool', () => {
const tool = getTool('code/unknown');
expect(tool).toBeUndefined();
});
it('should get all tool names', () => {
const names = getToolNames();
expect(names).toHaveLength(5);
expect(names).toContain('code/semantic-search');
});
});
describe('code/semantic-search', () => {
it('should have correct tool definition', () => {
expect(semanticSearchTool.name).toBe('code/semantic-search');
expect(semanticSearchTool.inputSchema.required).toContain('query');
expect(semanticSearchTool.cacheable).toBe(true);
});
it('should handle valid input', async () => {
const input = {
query: 'authentication login function',
topK: 10,
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.results).toBeDefined();
expect(Array.isArray(data.results)).toBe(true);
expect(data.searchTime).toBeDefined();
});
it('should handle language filter', async () => {
const input = {
query: 'error handling',
language: 'typescript',
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle search type filter', async () => {
const types = ['function', 'class', 'interface', 'type', 'variable', 'comment', 'any'] as const;
for (const searchType of types) {
const input = {
query: 'user data',
searchType,
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
}
});
it('should handle path filter', async () => {
const input = {
query: 'API endpoint',
pathFilter: 'src/api/',
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should reject missing query', async () => {
const input = {
topK: 10,
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject query exceeding max length', async () => {
const input = {
query: 'a'.repeat(1001),
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject topK outside valid range', async () => {
const input = {
query: 'test',
topK: 0, // below min
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
});
describe('code/architecture-analyze', () => {
it('should have correct tool definition', () => {
expect(architectureAnalyzeTool.name).toBe('code/architecture-analyze');
expect(architectureAnalyzeTool.inputSchema.required).toContain('targetPath');
expect(architectureAnalyzeTool.cacheable).toBe(true);
});
it('should handle valid input', async () => {
const input = {
targetPath: 'src/',
analysisTypes: ['dependencies', 'modularity'],
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.components).toBeDefined();
expect(data.metrics).toBeDefined();
expect(data.analysisTime).toBeDefined();
});
it('should handle all analysis types', async () => {
const types = ['dependencies', 'modularity', 'complexity', 'coupling', 'cohesion', 'layers'] as const;
const input = {
targetPath: 'src/',
analysisTypes: [...types],
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle depth option', async () => {
const input = {
targetPath: 'src/',
depth: 5,
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle exclude patterns', async () => {
const input = {
targetPath: 'src/',
excludePatterns: ['node_modules', '*.test.ts'],
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should reject missing targetPath', async () => {
const input = {
analysisTypes: ['dependencies'],
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject targetPath exceeding max length', async () => {
const input = {
targetPath: 'a'.repeat(501),
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
});
describe('code/refactor-impact', () => {
it('should have correct tool definition', () => {
expect(refactorImpactTool.name).toBe('code/refactor-impact');
expect(refactorImpactTool.inputSchema.required).toContain('targetPath');
expect(refactorImpactTool.inputSchema.required).toContain('changeType');
expect(refactorImpactTool.cacheable).toBe(false);
});
it('should handle valid input', async () => {
const input = {
targetPath: 'src/services/user.ts',
changeType: 'rename',
description: 'Rename UserService to UserRepository',
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.directImpact).toBeDefined();
expect(data.indirectImpact).toBeDefined();
expect(data.riskLevel).toBeDefined();
});
it('should handle all change types', async () => {
const types = ['rename', 'move', 'delete', 'signature_change', 'type_change', 'dependency_change'] as const;
for (const changeType of types) {
const input = {
targetPath: 'src/test.ts',
changeType,
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
}
});
it('should handle include tests option', async () => {
const input = {
targetPath: 'src/service.ts',
changeType: 'delete',
includeTests: true,
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle depth option', async () => {
const input = {
targetPath: 'src/core.ts',
changeType: 'signature_change',
depth: 3,
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should reject missing targetPath', async () => {
const input = {
changeType: 'rename',
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject missing changeType', async () => {
const input = {
targetPath: 'src/test.ts',
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject invalid changeType', async () => {
const input = {
targetPath: 'src/test.ts',
changeType: 'invalid_change',
};
const result = await refactorImpactTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
});
describe('code/split-suggest', () => {
it('should have correct tool definition', () => {
expect(splitSuggestTool.name).toBe('code/split-suggest');
expect(splitSuggestTool.inputSchema.required).toContain('targetPath');
expect(splitSuggestTool.cacheable).toBe(true);
});
it('should handle valid input', async () => {
const input = {
targetPath: 'src/utils.ts',
threshold: 300,
};
const result = await splitSuggestTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.suggestions).toBeDefined();
expect(data.analysisTime).toBeDefined();
});
it('should handle all split strategies', async () => {
const strategies = ['responsibility', 'cohesion', 'size', 'complexity'] as const;
for (const strategy of strategies) {
const input = {
targetPath: 'src/',
strategy,
};
const result = await splitSuggestTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
}
});
it('should use default threshold', async () => {
const input = {
targetPath: 'src/',
};
const result = await splitSuggestTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.threshold).toBe(500); // default
});
it('should handle include patterns', async () => {
const input = {
targetPath: 'src/',
includePatterns: ['*.ts', '*.tsx'],
};
const result = await splitSuggestTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should reject missing targetPath', async () => {
const input = {
threshold: 300,
};
const result = await splitSuggestTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject threshold outside valid range', async () => {
const tooLow = {
targetPath: 'src/',
threshold: 49, // below min of 50
};
const result = await splitSuggestTool.handler(tooLow, createMockContext());
expect(result.isError).toBe(true);
});
});
describe('code/learn-patterns', () => {
it('should have correct tool definition', () => {
expect(learnPatternsTool.name).toBe('code/learn-patterns');
expect(learnPatternsTool.inputSchema.required).toContain('targetPath');
expect(learnPatternsTool.cacheable).toBe(true);
});
it('should handle valid input', async () => {
const input = {
targetPath: 'src/',
patternTypes: ['design_patterns', 'anti_patterns'],
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.patterns).toBeDefined();
expect(data.antiPatterns).toBeDefined();
expect(data.analysisTime).toBeDefined();
});
it('should handle all pattern types', async () => {
const types = ['design_patterns', 'anti_patterns', 'idioms', 'conventions', 'architecture'] as const;
const input = {
targetPath: 'src/',
patternTypes: [...types],
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle language filter', async () => {
const input = {
targetPath: 'src/',
language: 'typescript',
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should handle min confidence threshold', async () => {
const input = {
targetPath: 'src/',
minConfidence: 0.8,
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
});
it('should reject missing targetPath', async () => {
const input = {
patternTypes: ['design_patterns'],
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
it('should reject minConfidence outside valid range', async () => {
const input = {
targetPath: 'src/',
minConfidence: 1.5, // above max of 1.0
};
const result = await learnPatternsTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
});
});
describe('Security - Secret Masking', () => {
it('should mask secrets in search results', async () => {
// This tests that the tool doesn't expose secrets
const input = {
query: 'api key configuration',
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
// Results should not contain unmasked secrets
for (const r of data.results) {
expect(r.content).not.toMatch(/sk-[a-zA-Z0-9]{48}/); // OpenAI key pattern
expect(r.content).not.toMatch(/AKIA[0-9A-Z]{16}/); // AWS key pattern
}
});
});
describe('Error Handling', () => {
it('should handle validation errors gracefully', async () => {
const input = {
query: '', // Invalid empty query
};
const result = await semanticSearchTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
const data = JSON.parse(result.content[0].text!);
expect(data.error).toBe(true);
expect(data.message).toBeDefined();
});
it('should include timestamp in error response', async () => {
const input = {
targetPath: '', // Invalid
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBe(true);
const data = JSON.parse(result.content[0].text!);
expect(data.timestamp).toBeDefined();
});
});
describe('Performance Logging', () => {
it('should log duration on success', async () => {
const mockLogger = {
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
};
const input = { query: 'test function' };
await semanticSearchTool.handler(input, { logger: mockLogger });
expect(mockLogger.info).toHaveBeenCalledWith(
expect.stringContaining('completed'),
expect.objectContaining({ durationMs: expect.any(String) })
);
});
it('should include analysis time in results', async () => {
const input = {
targetPath: 'src/',
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
const data = JSON.parse(result.content[0].text!);
expect(data.analysisTime).toBeDefined();
expect(typeof data.analysisTime).toBe('number');
});
});
describe('Output Format', () => {
it('should support different output formats', async () => {
const formats = ['json', 'markdown', 'summary'] as const;
for (const outputFormat of formats) {
const input = {
targetPath: 'src/',
outputFormat,
};
const result = await architectureAnalyzeTool.handler(input, createMockContext());
expect(result.isError).toBeUndefined();
}
});
});
});
@@ -0,0 +1,543 @@
/**
* Code Intelligence Plugin - Types Tests
*
* Tests for Zod schemas and type validation
*/
import { describe, it, expect } from 'vitest';
import {
Language,
LanguageTier,
SearchType,
AnalysisType,
OutputFormat,
ChangeType,
SplitStrategy,
PatternType,
SemanticSearchInputSchema,
ArchitectureAnalyzeInputSchema,
RefactorImpactInputSchema,
SplitSuggestInputSchema,
LearnPatternsInputSchema,
DEFAULT_CONFIG,
CodeIntelligenceErrorCodes,
CodeIntelligenceError,
SECRET_PATTERNS,
maskSecrets,
} from '../src/types.js';
describe('Code Intelligence Types', () => {
describe('Language Enum', () => {
it('should validate all supported languages', () => {
const validLanguages = [
'typescript', 'javascript', 'python', 'java', 'go',
'rust', 'cpp', 'csharp', 'ruby', 'php', 'swift', 'kotlin', 'scala',
];
for (const lang of validLanguages) {
const result = Language.safeParse(lang);
expect(result.success).toBe(true);
}
});
it('should reject invalid languages', () => {
const result = Language.safeParse('cobol');
expect(result.success).toBe(false);
});
});
describe('LanguageTier', () => {
it('should classify TypeScript and JavaScript as tier1', () => {
expect(LanguageTier.typescript).toBe('tier1');
expect(LanguageTier.javascript).toBe('tier1');
});
it('should classify Python and Java as tier2', () => {
expect(LanguageTier.python).toBe('tier2');
expect(LanguageTier.java).toBe('tier2');
});
it('should classify Go and Rust as tier3', () => {
expect(LanguageTier.go).toBe('tier3');
expect(LanguageTier.rust).toBe('tier3');
});
});
describe('SearchType Enum', () => {
it('should validate all search types', () => {
const validTypes = ['semantic', 'structural', 'clone', 'api_usage'];
for (const type of validTypes) {
const result = SearchType.safeParse(type);
expect(result.success).toBe(true);
}
});
});
describe('AnalysisType Enum', () => {
it('should validate all analysis types', () => {
const validTypes = [
'dependency_graph', 'layer_violations', 'circular_deps',
'component_coupling', 'module_cohesion', 'dead_code',
'api_surface', 'architectural_drift',
];
for (const type of validTypes) {
const result = AnalysisType.safeParse(type);
expect(result.success).toBe(true);
}
});
});
describe('OutputFormat Enum', () => {
it('should validate all output formats', () => {
const validFormats = ['json', 'graphviz', 'mermaid'];
for (const format of validFormats) {
const result = OutputFormat.safeParse(format);
expect(result.success).toBe(true);
}
});
});
describe('ChangeType Enum', () => {
it('should validate all change types', () => {
const validTypes = ['rename', 'move', 'delete', 'extract', 'inline'];
for (const type of validTypes) {
const result = ChangeType.safeParse(type);
expect(result.success).toBe(true);
}
});
});
describe('SplitStrategy Enum', () => {
it('should validate all split strategies', () => {
const validStrategies = ['minimize_coupling', 'balance_size', 'feature_isolation'];
for (const strategy of validStrategies) {
const result = SplitStrategy.safeParse(strategy);
expect(result.success).toBe(true);
}
});
});
describe('PatternType Enum', () => {
it('should validate all pattern types', () => {
const validTypes = ['bug_patterns', 'refactor_patterns', 'api_patterns', 'test_patterns'];
for (const type of validTypes) {
const result = PatternType.safeParse(type);
expect(result.success).toBe(true);
}
});
});
describe('SemanticSearchInputSchema', () => {
it('should validate valid semantic search input', () => {
const validInput = {
query: 'authentication middleware',
searchType: 'semantic',
topK: 10,
};
const result = SemanticSearchInputSchema.safeParse(validInput);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.topK).toBe(10);
}
});
it('should use defaults when not provided', () => {
const input = {
query: 'find user service',
};
const result = SemanticSearchInputSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.searchType).toBe('semantic');
expect(result.data.topK).toBe(10);
}
});
it('should validate with scope options', () => {
const input = {
query: 'database connection',
scope: {
paths: ['src/', 'lib/'],
languages: ['typescript', 'javascript'],
excludeTests: true,
},
};
const result = SemanticSearchInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it('should reject empty query', () => {
const input = { query: '' };
const result = SemanticSearchInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it('should reject query exceeding max length', () => {
const input = { query: 'a'.repeat(5001) };
const result = SemanticSearchInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it('should reject topK outside valid range', () => {
const tooLow = { query: 'test', topK: 0 };
const tooHigh = { query: 'test', topK: 1001 };
expect(SemanticSearchInputSchema.safeParse(tooLow).success).toBe(false);
expect(SemanticSearchInputSchema.safeParse(tooHigh).success).toBe(false);
});
it('should validate all search types', () => {
const types = ['semantic', 'structural', 'clone', 'api_usage'] as const;
for (const searchType of types) {
const input = { query: 'test', searchType };
const result = SemanticSearchInputSchema.safeParse(input);
expect(result.success).toBe(true);
}
});
});
describe('ArchitectureAnalyzeInputSchema', () => {
it('should validate valid architecture analyze input', () => {
const validInput = {
rootPath: './src',
analysis: ['dependency_graph', 'circular_deps'],
};
const result = ArchitectureAnalyzeInputSchema.safeParse(validInput);
expect(result.success).toBe(true);
});
it('should use default root path', () => {
const input = {};
const result = ArchitectureAnalyzeInputSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.rootPath).toBe('.');
}
});
it('should validate with layers configuration', () => {
const input = {
rootPath: './src',
layers: {
presentation: ['src/ui/', 'src/components/'],
business: ['src/services/', 'src/domain/'],
data: ['src/repositories/', 'src/database/'],
},
};
const result = ArchitectureAnalyzeInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it('should validate with baseline', () => {
const input = {
rootPath: './src',
baseline: 'main',
outputFormat: 'mermaid',
};
const result = ArchitectureAnalyzeInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it('should validate all output formats', () => {
const formats = ['json', 'graphviz', 'mermaid'] as const;
for (const outputFormat of formats) {
const input = { outputFormat };
const result = ArchitectureAnalyzeInputSchema.safeParse(input);
expect(result.success).toBe(true);
}
});
});
describe('RefactorImpactInputSchema', () => {
it('should validate valid refactor impact input', () => {
const validInput = {
changes: [
{ file: 'src/service.ts', type: 'rename' },
{ file: 'src/utils.ts', type: 'move' },
],
depth: 3,
};
const result = RefactorImpactInputSchema.safeParse(validInput);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.includeTests).toBe(true); // default
}
});
it('should use default depth', () => {
const input = {
changes: [{ file: 'src/test.ts', type: 'delete' }],
};
const result = RefactorImpactInputSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.depth).toBe(3);
}
});
it('should reject empty changes array', () => {
const input = { changes: [] };
const result = RefactorImpactInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it('should reject too many changes', () => {
const changes = Array(101).fill({ file: 'test.ts', type: 'rename' });
const input = { changes };
const result = RefactorImpactInputSchema.safeParse(input);
expect(result.success).toBe(false);
});
it('should reject depth outside valid range', () => {
const tooLow = { changes: [{ file: 'test.ts', type: 'rename' }], depth: 0 };
const tooHigh = { changes: [{ file: 'test.ts', type: 'rename' }], depth: 11 };
expect(RefactorImpactInputSchema.safeParse(tooLow).success).toBe(false);
expect(RefactorImpactInputSchema.safeParse(tooHigh).success).toBe(false);
});
it('should validate all change types', () => {
const types = ['rename', 'move', 'delete', 'extract', 'inline'] as const;
for (const type of types) {
const input = { changes: [{ file: 'test.ts', type }] };
const result = RefactorImpactInputSchema.safeParse(input);
expect(result.success).toBe(true);
}
});
});
describe('SplitSuggestInputSchema', () => {
it('should validate valid split suggest input', () => {
const validInput = {
targetPath: './src/monolith',
strategy: 'minimize_coupling',
targetModules: 5,
};
const result = SplitSuggestInputSchema.safeParse(validInput);
expect(result.success).toBe(true);
});
it('should use default strategy', () => {
const input = {
targetPath: './src',
};
const result = SplitSuggestInputSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.strategy).toBe('minimize_coupling');
}
});
it('should validate with constraints', () => {
const input = {
targetPath: './src',
constraints: {
maxModuleSize: 500,
minModuleSize: 50,
preserveBoundaries: ['src/core/', 'src/api/'],
},
};
const result = SplitSuggestInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it('should reject targetModules outside valid range', () => {
const tooLow = { targetPath: './src', targetModules: 1 };
const tooHigh = { targetPath: './src', targetModules: 51 };
expect(SplitSuggestInputSchema.safeParse(tooLow).success).toBe(false);
expect(SplitSuggestInputSchema.safeParse(tooHigh).success).toBe(false);
});
it('should validate all strategies', () => {
const strategies = ['minimize_coupling', 'balance_size', 'feature_isolation'] as const;
for (const strategy of strategies) {
const input = { targetPath: './src', strategy };
const result = SplitSuggestInputSchema.safeParse(input);
expect(result.success).toBe(true);
}
});
});
describe('LearnPatternsInputSchema', () => {
it('should validate valid learn patterns input', () => {
const validInput = {
scope: {
gitRange: 'HEAD~50..HEAD',
authors: ['john@example.com'],
},
patternTypes: ['bug_patterns', 'refactor_patterns'],
minOccurrences: 5,
};
const result = LearnPatternsInputSchema.safeParse(validInput);
expect(result.success).toBe(true);
});
it('should use default values', () => {
const input = {};
const result = LearnPatternsInputSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.minOccurrences).toBe(3);
}
});
it('should validate with scope paths', () => {
const input = {
scope: {
paths: ['src/', 'lib/'],
},
};
const result = LearnPatternsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
it('should reject minOccurrences outside valid range', () => {
const tooLow = { minOccurrences: 0 };
const tooHigh = { minOccurrences: 101 };
expect(LearnPatternsInputSchema.safeParse(tooLow).success).toBe(false);
expect(LearnPatternsInputSchema.safeParse(tooHigh).success).toBe(false);
});
it('should validate all pattern types', () => {
const types = ['bug_patterns', 'refactor_patterns', 'api_patterns', 'test_patterns'] as const;
const input = { patternTypes: [...types] };
const result = LearnPatternsInputSchema.safeParse(input);
expect(result.success).toBe(true);
});
});
describe('Default Configuration', () => {
it('should have valid search configuration', () => {
expect(DEFAULT_CONFIG.search.embeddingDimension).toBe(384);
expect(DEFAULT_CONFIG.search.defaultTopK).toBe(10);
expect(DEFAULT_CONFIG.search.similarityThreshold).toBe(0.7);
});
it('should have valid architecture configuration', () => {
expect(DEFAULT_CONFIG.architecture.maxGraphDepth).toBe(10);
expect(DEFAULT_CONFIG.architecture.includeVendor).toBe(false);
});
it('should have valid refactoring configuration', () => {
expect(DEFAULT_CONFIG.refactoring.defaultDepth).toBe(3);
expect(DEFAULT_CONFIG.refactoring.includeTests).toBe(true);
});
it('should have valid security configuration', () => {
expect(DEFAULT_CONFIG.security.allowedRoots).toContain('.');
expect(DEFAULT_CONFIG.security.maskSecrets).toBe(true);
expect(DEFAULT_CONFIG.security.blockedPatterns.length).toBeGreaterThan(0);
});
});
describe('Error Codes', () => {
it('should have all expected error codes', () => {
expect(CodeIntelligenceErrorCodes.PATH_TRAVERSAL).toBe('CODE_PATH_TRAVERSAL');
expect(CodeIntelligenceErrorCodes.SENSITIVE_FILE).toBe('CODE_SENSITIVE_FILE');
expect(CodeIntelligenceErrorCodes.GRAPH_TOO_LARGE).toBe('CODE_GRAPH_TOO_LARGE');
expect(CodeIntelligenceErrorCodes.ANALYSIS_FAILED).toBe('CODE_ANALYSIS_FAILED');
expect(CodeIntelligenceErrorCodes.PARSER_ERROR).toBe('CODE_PARSER_ERROR');
expect(CodeIntelligenceErrorCodes.WASM_NOT_INITIALIZED).toBe('CODE_WASM_NOT_INITIALIZED');
expect(CodeIntelligenceErrorCodes.LANGUAGE_NOT_SUPPORTED).toBe('CODE_LANGUAGE_NOT_SUPPORTED');
expect(CodeIntelligenceErrorCodes.GIT_ERROR).toBe('CODE_GIT_ERROR');
});
});
describe('CodeIntelligenceError', () => {
it('should create error with code and message', () => {
const error = new CodeIntelligenceError(
CodeIntelligenceErrorCodes.PATH_TRAVERSAL,
'Path traversal detected'
);
expect(error.name).toBe('CodeIntelligenceError');
expect(error.code).toBe('CODE_PATH_TRAVERSAL');
expect(error.message).toBe('Path traversal detected');
});
it('should create error with details', () => {
const error = new CodeIntelligenceError(
CodeIntelligenceErrorCodes.SENSITIVE_FILE,
'Access denied',
{ filePath: '/etc/passwd' }
);
expect(error.details).toEqual({ filePath: '/etc/passwd' });
});
it('should be instance of Error', () => {
const error = new CodeIntelligenceError(
CodeIntelligenceErrorCodes.ANALYSIS_FAILED,
'Failed'
);
expect(error).toBeInstanceOf(Error);
});
});
describe('Secret Patterns', () => {
it('should have defined secret patterns', () => {
expect(SECRET_PATTERNS).toBeDefined();
expect(SECRET_PATTERNS.length).toBeGreaterThan(0);
});
it('should detect API keys in quotes', () => {
const code = 'const apiKey = "sk_live_abc123xyz"';
const pattern = SECRET_PATTERNS[0];
expect(pattern.test(code)).toBe(true);
});
});
describe('maskSecrets', () => {
it('should mask API keys', () => {
const code = 'const apiKey = "sk-live-abcdefghijklmnopqrstuvwx"';
const masked = maskSecrets(code);
expect(masked).toContain('[REDACTED]');
});
it('should mask GitHub tokens', () => {
const code = 'const token = "ghp_abcdefghijklmnopqrstuvwxyz123456"';
const masked = maskSecrets(code);
expect(masked).toContain('[REDACTED]');
});
it('should mask AWS keys', () => {
const code = 'AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"';
const masked = maskSecrets(code);
expect(masked).toContain('[REDACTED]');
});
it('should mask private key headers', () => {
const code = '-----BEGIN RSA PRIVATE KEY-----\nMIIE...';
const masked = maskSecrets(code);
expect(masked).toContain('[REDACTED]');
});
it('should leave non-secret code unchanged', () => {
const code = 'const message = "Hello, World!"';
const masked = maskSecrets(code);
expect(masked).toBe(code);
});
});
});
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM"],
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"exactOptionalPropertyTypes": false,
"noUncheckedIndexedAccess": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts"]
}
@@ -0,0 +1,22 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.d.ts', 'src/**/index.ts'],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
testTimeout: 10000,
},
});