/**
* Service for formatting session data into markdown notes
*/
import type { SessionNote, CommandEntry, FileChange, CodeSnippet } from '../types/session.js';
/**
* Format a session note as markdown
*/
export function formatNoteAsMarkdown(note: SessionNote): string {
const lines: string[] = [];
// Header
lines.push(`# ${note.projectName || 'Claude Code Session'}`);
if (note.topic) {
lines.push(`## ${note.topic}`);
}
lines.push('');
// Metadata
lines.push('---');
lines.push(`**Date**: ${new Date(note.timestamp).toLocaleString()}`);
if (note.workingDirectory) {
lines.push(`**Working Directory**: \`${note.workingDirectory}\``);
}
if (note.tags && note.tags.length > 0) {
lines.push(`**Tags**: ${note.tags.map(t => `#${t}`).join(', ')}`);
}
if (note.analysis) {
if (note.analysis.pattern) {
lines.push(`**Pattern**: ${formatPattern(note.analysis.pattern)}`);
}
if (note.analysis.complexity) {
const fileCountText = note.analysis.fileCount ? ` (${note.analysis.fileCount} file${note.analysis.fileCount !== 1 ? 's' : ''})` : '';
lines.push(`**Complexity**: ${formatComplexity(note.analysis.complexity)}${fileCountText}`);
}
}
lines.push('---');
lines.push('');
// Summary
lines.push('## Summary');
lines.push('');
lines.push(note.summary);
lines.push('');
// Key Changes (concise bullets from file descriptions)
if (note.fileChanges && note.fileChanges.length > 0) {
const changes = note.fileChanges
.filter(f => f.description && f.description.trim().length > 0)
.map(f => f.description!.trim())
.slice(0, 3);
if (changes.length > 0) {
lines.push('## Key Changes');
lines.push('');
changes.forEach(change => lines.push(`- ${change}`));
lines.push('');
} else {
// Fallback: basic file count summary
const created = note.fileChanges.filter(f => f.type === 'created').length;
const modified = note.fileChanges.filter(f => f.type === 'modified').length;
const deleted = note.fileChanges.filter(f => f.type === 'deleted').length;
if (created + modified + deleted > 0) {
lines.push('## Key Changes');
lines.push('');
if (created > 0) lines.push(`- Created ${created} file${created > 1 ? 's' : ''}`);
if (modified > 0) lines.push(`- Modified ${modified} file${modified > 1 ? 's' : ''}`);
if (deleted > 0) lines.push(`- Deleted ${deleted} file${deleted > 1 ? 's' : ''}`);
lines.push('');
}
}
}
// Commands executed
if (note.commands && note.commands.length > 0) {
lines.push('## Commands Executed');
lines.push('');
for (const cmd of note.commands) {
lines.push(`### ${cmd.description || 'Command'}`);
lines.push('```bash');
lines.push(cmd.command);
lines.push('```');
if (cmd.output) {
lines.push('');
lines.push('<details>');
lines.push('<summary>Output</summary>');
lines.push('');
lines.push('```');
lines.push(cmd.output);
lines.push('```');
lines.push('</details>');
}
lines.push('');
}
}
// File changes
if (note.fileChanges && note.fileChanges.length > 0) {
lines.push('## File Changes');
lines.push('');
const created = note.fileChanges.filter(f => f.type === 'created');
const modified = note.fileChanges.filter(f => f.type === 'modified');
const deleted = note.fileChanges.filter(f => f.type === 'deleted');
if (created.length > 0) {
lines.push('### Created Files');
for (const file of created) {
lines.push(`- \`${file.path}\`${file.description ? ` - ${file.description}` : ''}`);
}
lines.push('');
}
if (modified.length > 0) {
lines.push('### Modified Files');
for (const file of modified) {
lines.push(`- \`${file.path}\`${file.description ? ` - ${file.description}` : ''}`);
if (file.diff) {
lines.push(' <details>');
lines.push(' <summary>Changes</summary>');
lines.push(' ');
lines.push(' ```diff');
lines.push(file.diff);
lines.push(' ```');
lines.push(' </details>');
}
}
lines.push('');
}
if (deleted.length > 0) {
lines.push('### Deleted Files');
for (const file of deleted) {
lines.push(`- \`${file.path}\`${file.description ? ` - ${file.description}` : ''}`);
}
lines.push('');
}
}
// Code snippets
if (note.codeSnippets && note.codeSnippets.length > 0) {
lines.push('## Code Snippets');
lines.push('');
for (const snippet of note.codeSnippets) {
if (snippet.description) {
lines.push(`### ${snippet.description}`);
}
if (snippet.filePath) {
lines.push(`*From: \`${snippet.filePath}\`*`);
lines.push('');
}
lines.push('```' + snippet.language);
lines.push(snippet.code);
lines.push('```');
lines.push('');
}
}
return lines.join('\n');
}
/**
* Generate a filename for the note based on project and timestamp
*/
export function generateFilename(note: SessionNote): string {
const date = new Date(note.timestamp);
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
const timeStr = date.toTimeString().split(' ')[0].replace(/:/g, '-'); // HH-MM-SS
const projectSlug = (note.projectName || 'session')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
const topicSlug = note.topic
? '-' + note.topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
: '';
return `${dateStr}_${timeStr}_${projectSlug}${topicSlug}.md`;
}
/**
* Format pattern for display
*/
function formatPattern(pattern: string): string {
const patterns: Record<string, string> = {
'new-feature': '🆕 New Feature',
'bug-fix': '🐛 Bug Fix',
'refactoring': '♻️ Refactoring',
'documentation': '📝 Documentation',
'configuration': '⚙️ Configuration',
'testing': '✅ Testing',
'mixed': '🔀 Mixed',
};
return patterns[pattern] || pattern;
}
/**
* Format complexity for display
*/
function formatComplexity(complexity: string): string {
const complexities: Record<string, string> = {
'simple': '🟢 Simple',
'moderate': '🟡 Moderate',
'complex': '🔴 Complex',
};
return complexities[complexity] || complexity;
}