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
JavaScript
/**
* 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;