UNPKG

oneie

Version:

๐Ÿค ONE Personal Collaborative Intelligence - Creates personalized AI workspace from your me.md profile. Simple: npx oneie โ†’ edit me.md โ†’ generate personalized agents, workflows & missions. From students to enterprises, ONE adapts to your context.

409 lines (340 loc) โ€ข 11.2 kB
#!/usr/bin/env node /** * File Organization Monitor * * Watches for misplaced files and automatically organizes them * according to ONE ontology structure */ const fs = require('fs').promises; const path = require('path'); const yaml = require('js-yaml'); const chokidar = require('chokidar'); class FileOrganizationMonitor { constructor(projectRoot = process.cwd()) { this.projectRoot = projectRoot; this.oneDir = path.join(projectRoot, '.one'); this.meDir = path.join(projectRoot, 'me'); // Ontology structure mapping this.ontologyStructure = { // Content types 'mission': 'missions', 'story': 'stories', 'task': 'tasks', 'agent': 'agents', 'team': 'teams', 'workflow': 'workflows', 'template': 'templates', 'checklist': 'checklists', // Data types 'person': 'people', 'organization': 'organizations', 'playbook': 'playbooks', 'data': 'data', // System types 'hook': 'hooks', 'tool': 'tools', 'app': 'apps', 'package': 'packages' }; this.isMonitoring = false; } /** * Start monitoring for file changes */ async startMonitoring() { if (this.isMonitoring) return; console.log('๐Ÿ” Starting file organization monitor...'); // Watch for new files and moves this.watcher = chokidar.watch(this.projectRoot, { ignored: [ '**/node_modules/**', '**/.git/**', '**/.next/**', '**/dist/**', '**/build/**' ], persistent: true, ignoreInitial: false }); this.watcher .on('add', filePath => this.handleFileEvent('add', filePath)) .on('change', filePath => this.handleFileEvent('change', filePath)) .on('unlink', filePath => this.handleFileEvent('unlink', filePath)); this.isMonitoring = true; console.log('โœ… File organization monitor active'); } /** * Stop monitoring */ async stopMonitoring() { if (this.watcher) { await this.watcher.close(); this.isMonitoring = false; console.log('๐Ÿ›‘ File organization monitor stopped'); } } /** * Handle file system events */ async handleFileEvent(event, filePath) { try { // Skip if file is already in correct location if (this.isInCorrectLocation(filePath)) return; // Skip system files if (this.isSystemFile(filePath)) return; // Analyze and potentially move file const correctLocation = await this.determineCorrectLocation(filePath); if (correctLocation && correctLocation !== filePath) { await this.moveFileToCorrectLocation(filePath, correctLocation); } } catch (error) { console.error(`โŒ Error handling file event for ${filePath}:`, error.message); } } /** * Determine if file is in correct location according to ontology */ isInCorrectLocation(filePath) { const relativePath = path.relative(this.projectRoot, filePath); // Special case: /me/me.md is always correct if (relativePath === 'me/me.md') return true; // Files in .one/ should follow ontology structure if (relativePath.startsWith('.one/')) { return this.validateOntologyPath(relativePath); } // Other files are correct if not ONE content return !this.isOneContentFile(filePath); } /** * Check if this is a system file that shouldn't be moved */ isSystemFile(filePath) { const systemPaths = [ 'package.json', 'node_modules', '.git', '.next', 'dist', 'build', '.env', 'README.md', 'LICENSE' ]; return systemPaths.some(sysPath => filePath.includes(sysPath)); } /** * Determine if this is a ONE content file that needs organization */ async isOneContentFile(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); const ext = path.extname(filePath); // Check file extension if (['.md', '.yaml', '.yml'].includes(ext)) { // Check for ONE content indicators return this.hasOneContentIndicators(content, filePath); } return false; } catch (error) { return false; } } /** * Check content for ONE-specific indicators */ hasOneContentIndicators(content, filePath) { const indicators = [ // YAML frontmatter indicators 'mission:', 'story:', 'task:', 'agent:', 'team:', 'workflow:', 'checklist:', // Content indicators '# Mission', '# Story', '# Task', '# Agent', '# Team', // File naming patterns /mission-\d+/, /story-\d+/, /task-\w+/, /agent-\w+/, /team-\w+/ ]; const fileName = path.basename(filePath); return indicators.some(indicator => { if (typeof indicator === 'string') { return content.includes(indicator) || fileName.includes(indicator); } else { return indicator.test(content) || indicator.test(fileName); } }); } /** * Determine correct location for file based on content and naming */ async determineCorrectLocation(filePath) { try { const fileName = path.basename(filePath); const content = await fs.readFile(filePath, 'utf8'); // Analyze file type const fileType = this.analyzeFileType(fileName, content); if (!fileType) return null; // Determine if this should be in a space or global const spaceId = this.determineSpace(content); // Build correct path const baseDir = spaceId ? path.join(this.oneDir, 'spaces', spaceId, this.ontologyStructure[fileType]) : path.join(this.oneDir, this.ontologyStructure[fileType]); return path.join(baseDir, fileName); } catch (error) { console.error(`Error determining correct location for ${filePath}:`, error.message); return null; } } /** * Analyze file to determine its type */ analyzeFileType(fileName, content) { // Check filename patterns first for (const [type, _] of Object.entries(this.ontologyStructure)) { if (fileName.includes(type)) { return type; } } // Check content patterns if (content.includes('mission:') || content.includes('# Mission')) return 'mission'; if (content.includes('story:') || content.includes('# Story')) return 'story'; if (content.includes('task:') || content.includes('# Task')) return 'task'; if (content.includes('agent:') || content.includes('# Agent')) return 'agent'; if (content.includes('team:') || content.includes('# Team')) return 'team'; if (content.includes('workflow:') || content.includes('# Workflow')) return 'workflow'; if (content.includes('checklist:') || content.includes('# Checklist')) return 'checklist'; return null; } /** * Determine which space this content belongs to */ determineSpace(content) { // Check for space-specific indicators // For now, default to 'default' space // Could be enhanced to analyze content for space hints return 'default'; } /** * Move file to correct location */ async moveFileToCorrectLocation(sourcePath, targetPath) { try { // Ensure target directory exists const targetDir = path.dirname(targetPath); await fs.mkdir(targetDir, { recursive: true }); // Check if target already exists try { await fs.access(targetPath); console.log(`โš ๏ธ Target already exists: ${targetPath}`); return; } catch { // Target doesn't exist, proceed with move } // Move file await fs.rename(sourcePath, targetPath); console.log(`๐Ÿ“ Organized: ${path.relative(this.projectRoot, sourcePath)} โ†’ ${path.relative(this.projectRoot, targetPath)}`); } catch (error) { console.error(`โŒ Failed to move ${sourcePath} to ${targetPath}:`, error.message); } } /** * Validate that a path follows ontology structure */ validateOntologyPath(relativePath) { const pathParts = relativePath.split('/'); // Should be .one/{category}/{filename} or .one/spaces/{space}/{category}/{filename} if (pathParts[0] !== '.one') return false; if (pathParts[1] === 'spaces') { // Space-specific path: .one/spaces/{space}/{category}/{filename} if (pathParts.length < 4) return false; const category = pathParts[3]; return Object.values(this.ontologyStructure).includes(category); } else { // Global path: .one/{category}/{filename} if (pathParts.length < 3) return false; const category = pathParts[1]; return Object.values(this.ontologyStructure).includes(category) || ['apps', 'packages', 'tools', 'hooks'].includes(category); } } /** * Manual organization scan */ async organizeAll() { console.log('๐Ÿงน Running full organization scan...'); const files = await this.getAllFiles(this.projectRoot); for (const filePath of files) { if (!this.isInCorrectLocation(filePath) && !this.isSystemFile(filePath)) { const correctLocation = await this.determineCorrectLocation(filePath); if (correctLocation && correctLocation !== filePath) { await this.moveFileToCorrectLocation(filePath, correctLocation); } } } console.log('โœ… Organization scan complete'); } /** * Get all files recursively */ async getAllFiles(dir) { const files = []; try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory() && !this.shouldSkipDirectory(entry.name)) { files.push(...await this.getAllFiles(fullPath)); } else if (entry.isFile()) { files.push(fullPath); } } } catch (error) { // Skip directories we can't read } return files; } /** * Check if directory should be skipped */ shouldSkipDirectory(dirName) { const skipDirs = ['node_modules', '.git', '.next', 'dist', 'build']; return skipDirs.includes(dirName); } } // CLI usage if (require.main === module) { const monitor = new FileOrganizationMonitor(); const command = process.argv[2]; switch (command) { case 'start': monitor.startMonitoring(); break; case 'organize': monitor.organizeAll().then(() => process.exit(0)); break; default: console.log(` ๐Ÿ—‚๏ธ ONE File Organization Monitor Usage: node file-organization-monitor.js start # Start monitoring node file-organization-monitor.js organize # Run one-time organization Features: โ€ข Automatically organizes files according to ONE ontology โ€ข Moves misplaced content to correct directories โ€ข Maintains ontology compliance โ€ข Preserves file integrity `); } } module.exports = FileOrganizationMonitor;