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,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;
|
||||
}
|
||||
Reference in New Issue
Block a user