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
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:
@@ -0,0 +1,381 @@
|
||||
# @claude-flow/plugin-code-intelligence
|
||||
|
||||
[](https://www.npmjs.com/package/@claude-flow/plugin-code-intelligence)
|
||||
[](https://www.npmjs.com/package/@claude-flow/plugin-code-intelligence)
|
||||
[](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.
|
||||
@@ -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;
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user