class NeuralVFSModule { constructor() { this.commands = {}; this.terminal = null; this.index = {}; this.indexPath = '/.semantic_index/map.json'; } getHelpInfo() { return { 'nvfs-index': 'nvfs-index — Scan VFS and build semantic concept index', 'nvfs-status': 'nvfs-status — Show semantic index stats and last scan time', 'nvfs-resolve': 'nvfs-resolve — Find files related to a concept', 'nvfs-tree': 'nvfs-tree [concept] — Show concept-to-file mapping as a tree' }; } registerCommands() { this.commands['nvfs-index'] = (args) => this.buildIndex(); this.commands['nvfs-status'] = (args) => this.showStatus(); this.commands['nvfs-resolve'] = (args) => this.resolve(args); this.commands['nvfs-tree'] = (args) => this.tree(args); } // ── Core Index Builder ── buildIndex() { const fs = this.terminal.fs; if (!fs) return this.terminal.print('VFS not available', 'error'); this.terminal.print('🔍 Scanning VFS for semantic concepts...', 'info'); const start = Date.now(); const newIndex = {}; let fileCount = 0; let conceptCount = 0; // Walk VFS recursively — skip system dirs this._walk('/', fs, newIndex, (path, node) => { if (node.type !== 'file') return; if (this._isSystemFile(path)) return; fileCount++; const concepts = this._extractConcepts(path, node.content || ''); for (const concept of concepts) { if (!newIndex[concept]) { newIndex[concept] = []; conceptCount++; } // Avoid dupes if (!newIndex[concept].find(e => e.path === path)) { newIndex[concept].push({ path, summary: this._summarize(node.content) }); } } }); // Also index skills this._indexSkills(fs, newIndex); this.index = newIndex; this.lastScan = new Date().toISOString(); this.fileCount = fileCount; this.conceptCount = conceptCount; // Save to VFS this._saveIndex(fs); // Clear VFS concept cache so concept:// URIs reflect the new index if (fs._clearConceptCache) fs._clearConceptCache(); const elapsed = ((Date.now() - start) / 1000).toFixed(1); this.terminal.print(`✅ Indexed ${fileCount} files → ${conceptCount} concepts in ${elapsed}s`, 'success'); this.terminal.print(` Index saved to ${this.indexPath}`, 'info'); } // ── Resolve ── resolve(args) { if (!args[0]) return this.terminal.print('Usage: nvfs-resolve ', 'warning'); const concept = args.join(' ').toLowerCase(); const matches = this.index[concept] || []; // Fuzzy fallback — find partial matches const fuzzy = Object.keys(this.index).filter(k => k.includes(concept) || concept.includes(k) ).filter(k => k !== concept); if (matches.length === 0 && fuzzy.length === 0) { return this.terminal.print(`No files found for concept "${concept}"`, 'warning'); } if (matches.length > 0) { this.terminal.print(`📂 "${concept}" → ${matches.length} file(s):`, 'info'); for (const m of matches.slice(0, 20)) { this.terminal.print(` ${m.path}`, 'success'); if (m.summary) this.terminal.print(` ${m.summary}`, '') } if (matches.length > 20) { this.terminal.print(` ... and ${matches.length - 20} more`, 'info'); } } if (fuzzy.length > 0) { this.terminal.print(`\n🔀 Similar concepts: ${fuzzy.slice(0, 10).join(', ')}`, 'info'); } } // ── Status ── showStatus() { const count = Object.keys(this.index).length; if (count === 0) { // Try loading from VFS const fs = this.terminal.fs; const saved = fs.fsReadJSON(this.indexPath); if (saved) { this.index = saved; this.lastScan = '(loaded from disk)'; this.terminal.print(`📖 Loaded index from ${this.indexPath}`, 'info'); } else { return this.terminal.print('No semantic index found. Run nvfs-index first.', 'warning'); } } const totalFiles = new Set(); for (const entries of Object.values(this.index)) { for (const e of entries) totalFiles.add(e.path); } this.terminal.print(`🧠 Neural VFS Index`, 'info'); this.terminal.print(` Concepts: ${Object.keys(this.index).length}`, ''); this.terminal.print(` Files indexed: ${totalFiles.size}`, ''); this.terminal.print(` Last scan: ${this.lastScan || 'never'}`, ''); } // ── Tree View ── tree(args) { const concepts = Object.keys(this.index); if (concepts.length === 0) { return this.terminal.print('No index. Run nvfs-index first.', 'warning'); } const filter = args[0] ? args[0].toLowerCase() : null; const filtered = filter ? concepts.filter(c => c.includes(filter)) : concepts; this.terminal.print(`🌳 Concept Tree${filter ? ` (filter: "${filter}")` : ''}:`, 'info'); for (const concept of filtered.slice(0, 15)) { this.terminal.print(` 📁 ${concept}`, 'success'); for (const entry of this.index[concept].slice(0, 5)) { this.terminal.print(` └─ ${entry.path}`, ''); } if (this.index[concept].length > 5) { this.terminal.print(` └─ ... (+${this.index[concept].length - 5} more)`, ''); } } if (filtered.length > 15) { this.terminal.print(` ... and ${filtered.length - 15} more concepts`, 'info'); } } // ── Private Helpers ── _walk(path, fs, index, cb) { const entries = fs.readDir(path); if (!entries) return; for (const entry of entries) { const fullPath = path === '/' ? '/' + entry.name : path + '/' + entry.name; if (entry.type === 'file') { cb(fullPath, { type: 'file', content: fs.readFile(fullPath) || '' }); } else if (entry.type === 'dir') { this._walk(fullPath, fs, index, cb); } } } _isSystemFile(path) { const sysPrefixes = ['/.trash', '/.semantic_index', '/_snapshots', '/_sessions']; return sysPrefixes.some(p => path.startsWith(p) || path.includes('/node_modules')); } _extractConcepts(path, content) { const concepts = new Set(); const name = path.split('/').pop() || ''; const nameLower = name.toLowerCase(); // Extract from filename (split on camelCase, kebab, dots, underscores) const words = nameLower .replace(/([a-z])([A-Z])/g, '$1 $2') .replace(/[-_.]/g, ' ') .replace(/\.(md|js|html|json|css|txt|yaml|yml|toml|env|sh)$/i, '') .split(/\s+/) .filter(w => w.length > 2 && !/^\d+$/.test(w)); for (const word of words) { concepts.add(word); } // Extract file extension as concept const ext = path.split('.').pop(); if (ext && ['md', 'js', 'html', 'json', 'css'].includes(ext)) { concepts.add(ext + '-file'); } // Extract path-level concepts const parts = path.split('/').filter(p => p.length > 0); for (const part of parts) { if (['modules', 'skills', 'core', 'ext', 'backups', 'chat', 'downloads'].includes(part)) { concepts.add(part); } } // Extract #tags from content (markdown frontmatter or inline) if (content) { const tagMatches = content.match(/tags:\s*\[([^\]]+)\]/i) || content.match(/#[a-zA-Z]\w+/g); if (tagMatches) { const tags = tagMatches[0].replace(/tags:\s*\[/i, '').replace(/\]/, '').split(',').map(t => t.trim().replace(/^#/, '').replace(/"/g, '')); for (const tag of tags) { if (tag.length > 2) concepts.add(tag.toLowerCase()); } } // Extract first H1/H2 as concept const headerMatch = content.match(/^#\s+(.+)/m) || content.match(/^##\s+(.+)/m); if (headerMatch) { const header = headerMatch[1].toLowerCase().replace(/[^a-z0-9\s]/g, '').trim(); if (header.length > 3 && header.length < 40) { concepts.add(header); } } } return [...concepts]; } _summarize(content) { if (!content || content.length < 10) return ''; const cleaned = content.replace(/---[\s\S]*?---/, '').trim(); const firstLine = cleaned.split('\n').find(l => l.trim().length > 20); if (!firstLine) return ''; return firstLine.trim().slice(0, 100) + (firstLine.length > 100 ? '...' : ''); } _indexSkills(fs, index) { const skills = fs.readDir('/skills'); if (!skills) return; for (const skill of skills) { if (skill.type !== 'file' || !skill.name.endsWith('.md')) continue; // Already indexed by _walk — but we add a "skill" tag const base = skill.name.replace('.md', ''); if (!index['skill']) index['skill'] = []; index['skill'].push({ path: '/skills/' + skill.name, summary: 'Skill: ' + base }); } } _saveIndex(fs) { try { if (!fs.readDir('/.semantic_index')) { fs.mkdirRecursive('/.semantic_index'); } fs.fsWriteJSON(this.indexPath, this.index); } catch (e) { console.error('NeuralVFS save error:', e); } } } // Required for module system if (typeof window !== 'undefined') { window.NeuralVFSModule = NeuralVFSModule; }