mira-consciousness
Version:
Memory & Intelligence Retention Archive - Preserving The Spark
269 lines • 10.7 kB
JavaScript
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