ai-dev-diary
Version:
Intelligent development diary system for AI-assisted projects
370 lines (342 loc) • 13 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DiaryManager = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const dayjs_1 = __importDefault(require("dayjs"));
const github_1 = require("../utils/github");
class DiaryManager {
config;
basePath;
userPath;
constructor(basePath = '.ai-diary', author) {
this.basePath = basePath;
this.config = this.loadConfig();
const effectiveAuthor = author || this.config.author || this.config.githubUsername || 'unknown';
this.userPath = (0, github_1.getAuthorPath)(this.basePath, effectiveAuthor, this.config.teamMode || false);
}
async initialize(config) {
const detectedUsername = await (0, github_1.detectGitHubUsername)();
const defaultConfig = {
author: config?.author || detectedUsername || 'Unknown',
githubUsername: detectedUsername || undefined,
projectName: path.basename(process.cwd()),
teamMode: config?.teamMode || false,
ai: {
model: 'claude-3',
autoContext: true,
},
git: {
autoEntries: true,
linkCommits: true,
},
sharing: {
team: false,
public: false,
},
...config,
};
this.config = defaultConfig;
this.userPath = (0, github_1.getAuthorPath)(this.basePath, defaultConfig.author, defaultConfig.teamMode || false);
const dirs = [
this.basePath,
path.join(this.basePath, 'context'),
path.join(this.basePath, 'shared'),
path.join(this.basePath, 'shared', 'knowledge'),
path.join(this.basePath, 'shared', 'insights'),
path.join(this.basePath, 'reports'),
];
if (defaultConfig.teamMode) {
dirs.push(path.join(this.basePath, 'team'), path.join(this.userPath), path.join(this.userPath, 'entries'), path.join(this.userPath, 'insights'), path.join(this.userPath, 'context'));
}
else {
dirs.push(path.join(this.basePath, 'entries'), path.join(this.basePath, 'knowledge'), path.join(this.basePath, 'insights'));
}
for (const dir of dirs) {
await fs.ensureDir(dir);
}
await fs.writeJson(path.join(this.basePath, 'config.json'), defaultConfig, { spaces: 2 });
await this.createInitialContext();
await this.createKnowledgeBase();
}
isInitialized() {
return fs.existsSync(path.join(this.basePath, 'config.json'));
}
loadConfig() {
const configPath = path.join(this.basePath, 'config.json');
if (fs.existsSync(configPath)) {
return fs.readJsonSync(configPath);
}
return {
author: 'Unknown',
};
}
async saveEntry(entry) {
const date = (0, dayjs_1.default)(entry.timestamp);
const entriesPath = this.config.teamMode ? path.join(this.userPath, 'entries') : path.join(this.basePath, 'entries');
const entryDir = path.join(entriesPath, date.format('YYYY'), date.format('MM'), date.format('DD'));
await fs.ensureDir(entryDir);
const entryCount = (await fs.readdir(entryDir)).length;
const entryNumber = String(entryCount + 1).padStart(3, '0');
const filename = `${entryNumber}-${entry.type}-${this.sanitizeFilename(entry.title)}.md`;
const content = this.formatEntry(entry);
await fs.writeFile(path.join(entryDir, filename), content);
}
async getContext(author) {
if (this.config.teamMode && author) {
const personalPath = path.join((0, github_1.getAuthorPath)(this.basePath, author, true), 'context', 'AGENT_CONTEXT.md');
if (await fs.pathExists(personalPath)) {
const content = await fs.readFile(personalPath, 'utf-8');
return this.parseContext(content);
}
}
const contextPath = path.join(this.basePath, 'context', 'AGENT_CONTEXT.md');
if (await fs.pathExists(contextPath)) {
const content = await fs.readFile(contextPath, 'utf-8');
return this.parseContext(content);
}
return {
projectState: '',
currentFocus: '',
recentChanges: [],
importantNotes: [],
nextSteps: [],
lastUpdated: new Date(),
};
}
async updateContext(updates, author) {
const current = await this.getContext(author);
const updated = { ...current, ...updates, lastUpdated: new Date() };
const content = this.formatContext(updated);
if (this.config.teamMode && (author || this.config.author)) {
const effectiveAuthor = author || this.config.author;
const personalPath = path.join((0, github_1.getAuthorPath)(this.basePath, effectiveAuthor, true), 'context', 'AGENT_CONTEXT.md');
await fs.ensureDir(path.dirname(personalPath));
await fs.writeFile(personalPath, content);
}
else {
await fs.writeFile(path.join(this.basePath, 'context', 'AGENT_CONTEXT.md'), content);
}
}
async createInitialContext() {
const contextFiles = {
'AGENT_CONTEXT.md': `# AI Agent Context
## Current Project State
- **Phase**: Initial setup
- **Status**: Diary initialized
## Important Context
- Project uses ai-dev-diary for knowledge management
- All significant events should be logged
## Recent Changes
- Initialized development diary
## Next Steps
- Begin logging development activities
- Build knowledge base from experiences
Last Updated: ${new Date().toISOString()}
`,
'PROJECT_STATE.md': `# Project State
## Overview
Development diary initialized for ${this.config.projectName || 'this project'}.
## Current Status
- Diary system: Active
- Knowledge capture: Enabled
## Key Metrics
- Entries: 0
- Patterns identified: 0
- Mistakes logged: 0
`,
'MEMORY.md': `# Important Things to Remember
## Critical Information
- Always log significant decisions
- Document mistakes and their fixes
- Capture breakthrough moments
- Update context regularly
## Project-Specific Notes
(Add project-specific notes here)
`,
};
for (const [filename, content] of Object.entries(contextFiles)) {
await fs.writeFile(path.join(this.basePath, 'context', filename), content);
}
}
async createKnowledgeBase() {
const knowledgePath = this.config.teamMode
? path.join(this.basePath, 'shared', 'knowledge')
: path.join(this.basePath, 'knowledge');
const knowledgeFiles = {
'patterns.md': `# Successful Patterns
## Overview
Document patterns that work well in this project.
## Pattern Template
### Pattern Name
- **Context**: When to use this pattern
- **Solution**: How to implement it
- **Benefits**: Why it works
- **Examples**: Real usage examples
---
`,
'antipatterns.md': `# Anti-patterns to Avoid
## Overview
Document patterns that should be avoided.
## Anti-pattern Template
### Anti-pattern Name
- **Context**: When this might be tempting
- **Problem**: Why it's problematic
- **Alternative**: Better approach
- **Examples**: What to do instead
---
`,
'mistakes.md': `# Mistakes Log
## Overview
Learn from past mistakes to avoid repeating them.
## Entry Format
Date | Mistake | Impact | Correction | Prevention
---
`,
'breakthroughs.md': `# Breakthrough Moments
## Overview
Capture major discoveries and solutions.
## Entry Format
### Date - Title
- **Challenge**: What problem were we solving?
- **Discovery**: What did we realize?
- **Solution**: How did we solve it?
- **Impact**: What changed as a result?
---
`,
};
for (const [filename, content] of Object.entries(knowledgeFiles)) {
await fs.writeFile(path.join(knowledgePath, filename), content);
}
}
formatEntry(entry) {
const frontmatter = [
'---',
`id: ${entry.id}`,
`type: ${entry.type}`,
`timestamp: ${entry.timestamp.toISOString()}`,
`author: ${entry.metadata.author}`,
];
if (entry.metadata.aiAgent) {
frontmatter.push(`ai_agent: ${entry.metadata.aiAgent}`);
}
if (entry.tags.length > 0) {
frontmatter.push(`tags: [${entry.tags.join(', ')}]`);
}
frontmatter.push('---', '');
const content = [
`# ${entry.title}`,
'',
`Date: ${(0, dayjs_1.default)(entry.timestamp).format('YYYY-MM-DD HH:mm:ss')}`,
`Type: ${entry.type}`,
`Author: ${entry.metadata.author}`,
'',
'## Content',
entry.content,
];
if (entry.metadata.correction) {
content.push('', '## Correction', entry.metadata.correction);
}
if (entry.metadata.impact) {
content.push('', '## Impact', entry.metadata.impact);
}
if (entry.metadata.relatedFiles && entry.metadata.relatedFiles.length > 0) {
content.push('', '## Related Files', ...entry.metadata.relatedFiles.map(f => `- ${f}`));
}
return [...frontmatter, ...content].join('\n');
}
formatContext(context) {
return `# AI Agent Context
## Current Project State
${context.projectState}
## Current Focus
${context.currentFocus}
## Recent Changes
${context.recentChanges.map(change => `- ${change}`).join('\n')}
## Important Notes
${context.importantNotes.map(note => `- ${note}`).join('\n')}
## Next Steps
${context.nextSteps.map(step => `- ${step}`).join('\n')}
Last Updated: ${context.lastUpdated.toISOString()}
`;
}
parseContext(content) {
const sections = content.split('##').slice(1);
const context = {
projectState: '',
currentFocus: '',
recentChanges: [],
importantNotes: [],
nextSteps: [],
lastUpdated: new Date(),
};
sections.forEach(section => {
const lines = section.trim().split('\n');
const title = lines[0].trim();
const content = lines.slice(1).join('\n').trim();
switch (title) {
case 'Current Project State':
context.projectState = content;
break;
case 'Current Focus':
context.currentFocus = content;
break;
case 'Recent Changes':
context.recentChanges = content.split('\n').map(l => l.replace(/^- /, '').trim()).filter(Boolean);
break;
case 'Important Notes':
context.importantNotes = content.split('\n').map(l => l.replace(/^- /, '').trim()).filter(Boolean);
break;
case 'Next Steps':
context.nextSteps = content.split('\n').map(l => l.replace(/^- /, '').trim()).filter(Boolean);
break;
}
});
return context;
}
sanitizeFilename(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9-]/g, '-')
.replace(/-+/g, '-')
.slice(0, 50);
}
}
exports.DiaryManager = DiaryManager;
//# sourceMappingURL=diary-manager.js.map