// DOCUMENT ORGANIZER AGENT
// Handles file naming standards and filing structure
// Works with Document Control Agent to maintain organization
import { promises as fs } from 'fs';
import path from 'path';
import { configManager } from '../../config-manager.js';
import { actionLogger } from '../../logging/agent-action-logger.js';
import { codeIntegrityChecker } from './code-integrity-checker.js';
export class DocumentOrganizer {
constructor() {
this.name = 'DocumentOrganizer';
this.role = 'File naming and organization standards';
// Naming standards
this.namingRules = {
agents: {
pattern: '{type}-{name}.js',
types: ['core', 'specialized'],
example: 'core-doc-control.js'
},
tools: {
pattern: '{service}-{function}-tool.js',
example: 'n8n-workflow-tool.js'
},
workflows: {
pattern: '{project}-{type}-workflow.json',
example: 'production-master-workflow.json'
},
logs: {
pattern: '{category}_{date}.log',
example: 'actions_2025-10-02.log'
}
};
// Filing structure
this.filingRules = {
'agents/core': 'Reusable core agents',
'agents/specialized': 'Project-specific agents',
'tools': 'Integration and utility tools',
'workflows': 'N8N and automation workflows',
'logs/agent-actions': 'Agent action logs',
'logs/system': 'System logs',
'inventory': 'Agent and tool inventories',
'backups/daily': 'Automated daily backups',
'backups/manual': 'Manual backup snapshots'
};
}
// Organize a new file
async organize(filePath, metadata = {}) {
console.log(`📋 ${this.name}: Organizing file...`);
const sessionId = await actionLogger.startSession(
this.name,
`Organize file: ${path.basename(filePath)}`
);
try {
// Step 1: Validate file exists
const exists = await this.fileExists(filePath);
if (!exists) {
throw new Error(`File not found: ${filePath}`);
}
// Step 2: Determine file type
const fileType = await this.determineFileType(filePath);
console.log(` Type: ${fileType}`);
// Step 3: Generate standard name
const standardName = await this.generateStandardName(filePath, fileType, metadata);
console.log(` Standard name: ${standardName}`);
// Step 4: Determine correct location
const targetLocation = await this.determineLocation(fileType, metadata);
console.log(` Target location: ${targetLocation}`);
// Step 5: Create full target path
const targetPath = path.join(targetLocation, standardName);
// Step 6: Check if move needed
if (filePath === targetPath) {
console.log(` ✅ File already in correct location`);
await actionLogger.endSession('completed');
return { organized: false, path: filePath };
}
// Step 7: Check code integrity before move
const integrityCheck = await codeIntegrityChecker.validateMove(filePath, targetPath);
if (!integrityCheck.safe) {
console.log(` ⚠️ Integrity issues found - applying fixes...`);
await codeIntegrityChecker.autoFix(filePath, targetPath, integrityCheck.issues);
}
// Step 8: Move file
await this.moveFile(filePath, targetPath);
// Step 9: Update any references
await this.updateReferences(filePath, targetPath);
await actionLogger.endSession('completed');
console.log(` ✅ File organized successfully`);
return {
organized: true,
oldPath: filePath,
newPath: targetPath,
standardName: standardName,
location: targetLocation
};
} catch (error) {
await actionLogger.endSession('failed');
console.error(` ❌ Organization failed:`, error);
throw error;
}
}
// Determine file type
async determineFileType(filePath) {
const fileName = path.basename(filePath).toLowerCase();
const content = await fs.readFile(filePath, 'utf-8');
// Check for agent
if (content.includes('export class') && (
content.includes('Agent') ||
fileName.includes('agent') ||
fileName.includes('orchestrator') ||
fileName.includes('controller')
)) {
return 'agent';
}
// Check for tool
if (fileName.includes('tool') || fileName.includes('client') || fileName.includes('integration')) {
return 'tool';
}
// Check for workflow
if (fileName.includes('workflow') && fileName.endsWith('.json')) {
return 'workflow';
}
// Check for config
if (fileName.includes('config') && fileName.endsWith('.json')) {
return 'config';
}
// Check for log
if (fileName.endsWith('.log') || fileName.includes('log')) {
return 'log';
}
return 'other';
}
// Generate standard name
async generateStandardName(filePath, fileType, metadata) {
const fileName = path.basename(filePath);
const ext = path.extname(fileName);
const nameWithoutExt = fileName.replace(ext, '');
switch (fileType) {
case 'agent':
// Determine if core or specialized
const agentType = metadata.core ? 'core' : 'specialized';
const cleanName = nameWithoutExt
.replace(/agent/gi, '')
.replace(/[-_]+/g, '-')
.toLowerCase()
.trim();
return `${agentType}-${cleanName}${ext}`;
case 'tool':
const cleanToolName = nameWithoutExt
.replace(/tool/gi, '')
.replace(/[-_]+/g, '-')
.toLowerCase()
.trim();
return `${cleanToolName}-tool${ext}`;
case 'workflow':
// Keep workflow names descriptive
return fileName.toLowerCase();
default:
return fileName;
}
}
// Determine correct location
async determineLocation(fileType, metadata) {
const config = await configManager.initialize();
const root = config.paths.root;
switch (fileType) {
case 'agent':
const agentSubType = metadata.core ? 'core' : 'specialized';
return path.join(root, 'src', 'agents', agentSubType);
case 'tool':
return path.join(root, 'src', 'tools');
case 'workflow':
return path.join(root, 'workflows');
case 'config':
return root;
case 'log':
return config.paths.logs.system;
default:
return path.dirname(metadata.originalPath || '');
}
}
// Move file safely
async moveFile(sourcePath, destPath) {
await actionLogger.logAction('file_move', {
description: `Moving file`,
sourcePath,
destPath
}, { path: sourcePath }, { path: destPath });
// Ensure destination directory exists
await fs.mkdir(path.dirname(destPath), { recursive: true });
// Move file
await fs.rename(sourcePath, destPath);
console.log(` 📦 Moved to: ${destPath}`);
}
// Update references in config or other files
async updateReferences(oldPath, newPath) {
const config = await configManager.initialize();
const configStr = JSON.stringify(config);
// Check if path is in config
if (configStr.includes(oldPath)) {
console.log(` 🔄 Updating config references...`);
// Update would happen here - simplified for now
}
}
// Helper: Check if file exists
async fileExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
// Batch organize directory
async organizeDirectory(dirPath) {
console.log(`📁 ${this.name}: Organizing directory: ${dirPath}`);
const files = await fs.readdir(dirPath);
const results = [];
for (const file of files) {
const filePath = path.join(dirPath, file);
const stat = await fs.stat(filePath);
if (stat.isFile() && /\.(js|ts|json)$/.test(file)) {
try {
const result = await this.organize(filePath);
results.push(result);
} catch (error) {
console.error(` ❌ Failed to organize ${file}:`, error.message);
}
}
}
console.log(`✅ ${this.name}: Organized ${results.length} files`);
return results;
}
}
export const documentOrganizer = new DocumentOrganizer();