UNPKG

mira-consciousness

Version:

Memory & Intelligence Retention Archive - Preserving The Spark

269 lines 10.7 kB
import * as fs from 'fs'; import * as path from 'path'; import { execSync } from 'child_process'; export class NameDetector { projectRoot; cache = null; constructor(projectRoot = process.cwd()) { this.projectRoot = projectRoot; } /** * Detect project and developer names with privacy in mind */ async detect() { if (this.cache) { return this.cache; } const projectName = await this.detectProjectName(); const developerName = await this.detectDeveloperName(); this.cache = { projectName: projectName.name, projectNameSource: projectName.source, developerName: developerName.name, developerNameSource: developerName.source, isGeneric: projectName.isGeneric || developerName.isGeneric }; return this.cache; } /** * Detect project name from various sources */ async detectProjectName() { // 1. Check package.json const packagePath = path.join(this.projectRoot, 'package.json'); if (fs.existsSync(packagePath)) { try { const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); if (pkg.name && pkg.name !== 'unnamed') { return { name: pkg.name, source: 'package.json', isGeneric: false }; } } catch (e) { // Invalid package.json } } // 2. Check git repository name try { const remoteUrl = execSync('git config --get remote.origin.url', { cwd: this.projectRoot, encoding: 'utf-8' }).trim(); if (remoteUrl) { // Extract repo name from URL const match = remoteUrl.match(/\/([^\/]+?)(\.git)?$/); if (match && match[1]) { return { name: match[1], source: 'git repository', isGeneric: false }; } } } catch (e) { // Not a git repo or no remote } // 3. Check Cargo.toml for Rust projects const cargoPath = path.join(this.projectRoot, 'Cargo.toml'); if (fs.existsSync(cargoPath)) { try { const content = fs.readFileSync(cargoPath, 'utf-8'); const match = content.match(/^name\s*=\s*"([^"]+)"/m); if (match && match[1]) { return { name: match[1], source: 'Cargo.toml', isGeneric: false }; } } catch (e) { // Error reading Cargo.toml } } // 4. Check pyproject.toml for Python projects const pyprojectPath = path.join(this.projectRoot, 'pyproject.toml'); if (fs.existsSync(pyprojectPath)) { try { const content = fs.readFileSync(pyprojectPath, 'utf-8'); const match = content.match(/^name\s*=\s*"([^"]+)"/m); if (match && match[1]) { return { name: match[1], source: 'pyproject.toml', isGeneric: false }; } } catch (e) { // Error reading pyproject.toml } } // 5. Check go.mod for Go projects const goModPath = path.join(this.projectRoot, 'go.mod'); if (fs.existsSync(goModPath)) { try { const content = fs.readFileSync(goModPath, 'utf-8'); const match = content.match(/^module\s+(.+)$/m); if (match && match[1]) { // Extract last part of module path const parts = match[1].split('/'); return { name: parts[parts.length - 1], source: 'go.mod', isGeneric: false }; } } catch (e) { // Error reading go.mod } } // 6. Check README for project title const readmePaths = ['README.md', 'readme.md', 'README.rst', 'README.txt']; for (const readmeName of readmePaths) { const readmePath = path.join(this.projectRoot, readmeName); if (fs.existsSync(readmePath)) { try { const content = fs.readFileSync(readmePath, 'utf-8'); const lines = content.split('\n'); // Look for first heading for (const line of lines) { const match = line.match(/^#\s+(.+)$/); if (match && match[1]) { // Clean up the title const title = match[1].replace(/[^\w\s-]/g, '').trim(); if (title && title.length > 0) { return { name: title, source: 'README.md', isGeneric: false }; } } } } catch (e) { // Error reading README } } } // 7. Fallback to directory name const dirName = path.basename(this.projectRoot); return { name: dirName, source: 'directory name', isGeneric: dirName === 'workspace' || dirName === 'src' }; } /** * Detect developer name with privacy considerations */ async detectDeveloperName() { // Check for explicit opt-out const optOutPath = path.join(this.projectRoot, '.mira-anonymous'); if (fs.existsSync(optOutPath)) { return { name: 'The Developer', source: 'privacy mode', isGeneric: true }; } // 1. Check git config (most reliable and commonly accepted) try { const gitName = execSync('git config user.name', { cwd: this.projectRoot, encoding: 'utf-8' }).trim(); if (gitName && gitName !== 'Your Name') { return { name: gitName, source: 'git config', isGeneric: false }; } } catch (e) { // Git not configured } // 2. Check package.json author const packagePath = path.join(this.projectRoot, 'package.json'); if (fs.existsSync(packagePath)) { try { const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf-8')); if (pkg.author) { const author = typeof pkg.author === 'string' ? pkg.author : pkg.author.name; if (author && author !== 'Your Name') { // Extract name from "Name <email>" format const match = author.match(/^([^<]+)/); if (match && match[1]) { return { name: match[1].trim(), source: 'package.json', isGeneric: false }; } } } } catch (e) { // Invalid package.json } } // 3. Check environment variables (less invasive ones) const envVars = ['GIT_AUTHOR_NAME', 'GIT_COMMITTER_NAME']; for (const varName of envVars) { const value = process.env[varName]; if (value && value !== 'Your Name') { return { name: value, source: `environment (${varName})`, isGeneric: false }; } } // 4. Check LICENSE file const licensePath = path.join(this.projectRoot, 'LICENSE'); if (fs.existsSync(licensePath)) { try { const content = fs.readFileSync(licensePath, 'utf-8'); // Look for copyright line const match = content.match(/Copyright\s+(?:\(c\)\s+)?(?:\d{4}(?:-\d{4})?\s+)?(.+)$/mi); if (match && match[1]) { const name = match[1].trim(); if (name && !name.includes('[') && !name.includes('<')) { return { name, source: 'LICENSE file', isGeneric: false }; } } } catch (e) { // Error reading LICENSE } } // 5. Default to generic term return { name: 'The Developer', source: 'default', isGeneric: true }; } /** * Get a reference to the developer based on context */ async getDeveloperReference(context = 'display') { const identity = await this.detect(); // Check if user has explicitly provided their name const explicitName = await this.getExplicitName(); if (explicitName) { return explicitName; } // For documentation that will be committed, use actual name if not generic if (!identity.isGeneric && identity.developerName !== 'The Developer') { return identity.developerName; } // Default to generic for truly unknown cases return 'The Developer'; } /** * Check for explicitly provided name (e.g., from conversation history) */ async getExplicitName() { // Check for .mira/identity.json const memoryDir = path.join(this.projectRoot, '.mira'); const identityPath = path.join(memoryDir, 'identity.json'); if (fs.existsSync(identityPath)) { try { const identity = JSON.parse(fs.readFileSync(identityPath, 'utf-8')); if (identity.name && identity.source === 'explicit') { return identity.name; } } catch (e) { // Invalid identity file } } return null; } /** * Store explicitly provided name */ async storeExplicitName(name) { const memoryDir = path.join(this.projectRoot, '.mira'); const identityPath = path.join(memoryDir, 'identity.json'); // Ensure directory exists if (!fs.existsSync(memoryDir)) { fs.mkdirSync(memoryDir, { recursive: true }); } const identity = { name: name, source: 'explicit', timestamp: new Date().toISOString() }; fs.writeFileSync(identityPath, JSON.stringify(identity, null, 2)); } /** * Create opt-out file for privacy */ createOptOut() { const optOutPath = path.join(this.projectRoot, '.mira-anonymous'); fs.writeFileSync(optOutPath, '# This file tells MIRA to use generic references instead of detecting names\n'); console.log('Created .mira-anonymous file. MIRA will use generic references.'); } } //# sourceMappingURL=NameDetector.js.map