mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
Deletes files and symbols with zero importers or self-declared deprecation.
No behavior change — structural cleanup only.
Removed:
- hook-response.ts, utils/bun-path.ts (zero importers)
- cli/handlers/user-message.ts (not wired in hooks.json)
- services/Context.ts + context-generator.ts (deprecated stubs)
- sqlite/migrations.ts (645 lines, pre-SDK schema, unused)
- DatabaseManager singleton + getDatabase + initializeDatabase
- 6 sqlite re-export shells (Observations/Sessions/Summaries/Prompts/Timeline/Import)
- worker/search/{strategies,filters}/ dirs (dead via unused SearchOrchestrator)
- SearchOrchestrator, TimelineBuilder, ResultFormatter
- TimelineService.formatTimeline (137 lines, unused)
- ProcessManager.cleanupOrphanedProcesses + createSignalHandler
- Duplicate php: key in smart-file-read/parser.ts
Rewired:
- SearchRoutes dynamic imports (services/Context → services/context)
- CorpusBuilder: SearchOrchestrator → SearchManager.search({format:'json'})
- build-hooks.js entry: context-generator.ts → services/context/index.ts
- scripts/ imports for moved transcript-parser.ts
Moved:
- utils/transcript-parser.ts → scripts/transcript-parser.ts (only callers)
Skipped (plan was wrong):
- consecutiveRestarts: live backoff math at SessionRoutes.ts:348
- AgentFormatter stubs: wired to HeaderRenderer with passing tests
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
241 lines
7.0 KiB
TypeScript
241 lines
7.0 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* Format Transcript Context
|
|
*
|
|
* Parses a Claude Code transcript and formats it to show rich contextual data
|
|
* that could be used for improved observation generation.
|
|
*/
|
|
|
|
import { TranscriptParser } from './transcript-parser.js';
|
|
import { writeFileSync } from 'fs';
|
|
import { basename } from 'path';
|
|
|
|
interface ConversationTurn {
|
|
turnNumber: number;
|
|
userMessage?: {
|
|
content: string;
|
|
timestamp: string;
|
|
};
|
|
assistantMessage?: {
|
|
textContent: string;
|
|
thinkingContent?: string;
|
|
toolUses: Array<{
|
|
name: string;
|
|
input: any;
|
|
timestamp: string;
|
|
}>;
|
|
timestamp: string;
|
|
};
|
|
toolResults?: Array<{
|
|
toolName: string;
|
|
result: any;
|
|
timestamp: string;
|
|
}>;
|
|
}
|
|
|
|
function extractConversationTurns(parser: TranscriptParser): ConversationTurn[] {
|
|
const entries = parser.getAllEntries();
|
|
const turns: ConversationTurn[] = [];
|
|
let currentTurn: ConversationTurn | null = null;
|
|
let turnNumber = 0;
|
|
|
|
for (const entry of entries) {
|
|
// User messages start a new turn
|
|
if (entry.type === 'user') {
|
|
// If previous turn exists, push it
|
|
if (currentTurn) {
|
|
turns.push(currentTurn);
|
|
}
|
|
|
|
// Start new turn
|
|
turnNumber++;
|
|
currentTurn = {
|
|
turnNumber,
|
|
toolResults: []
|
|
};
|
|
|
|
// Extract user text (skip tool results)
|
|
if (typeof entry.content === 'string') {
|
|
currentTurn.userMessage = {
|
|
content: entry.content,
|
|
timestamp: entry.timestamp
|
|
};
|
|
} else if (Array.isArray(entry.content)) {
|
|
const textContent = entry.content
|
|
.filter((c: any) => c.type === 'text')
|
|
.map((c: any) => c.text)
|
|
.join('\n');
|
|
|
|
if (textContent.trim()) {
|
|
currentTurn.userMessage = {
|
|
content: textContent,
|
|
timestamp: entry.timestamp
|
|
};
|
|
}
|
|
|
|
// Extract tool results
|
|
const toolResults = entry.content.filter((c: any) => c.type === 'tool_result');
|
|
for (const result of toolResults) {
|
|
currentTurn.toolResults!.push({
|
|
toolName: result.tool_use_id || 'unknown',
|
|
result: result.content,
|
|
timestamp: entry.timestamp
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assistant messages
|
|
if (entry.type === 'assistant' && currentTurn) {
|
|
if (!Array.isArray(entry.content)) continue;
|
|
|
|
const textBlocks = entry.content.filter((c: any) => c.type === 'text');
|
|
const thinkingBlocks = entry.content.filter((c: any) => c.type === 'thinking');
|
|
const toolUseBlocks = entry.content.filter((c: any) => c.type === 'tool_use');
|
|
|
|
currentTurn.assistantMessage = {
|
|
textContent: textBlocks.map((c: any) => c.text).join('\n'),
|
|
thinkingContent: thinkingBlocks.map((c: any) => c.thinking).join('\n'),
|
|
toolUses: toolUseBlocks.map((t: any) => ({
|
|
name: t.name,
|
|
input: t.input,
|
|
timestamp: entry.timestamp
|
|
})),
|
|
timestamp: entry.timestamp
|
|
};
|
|
}
|
|
}
|
|
|
|
// Push last turn
|
|
if (currentTurn) {
|
|
turns.push(currentTurn);
|
|
}
|
|
|
|
return turns;
|
|
}
|
|
|
|
function formatTurnToMarkdown(turn: ConversationTurn): string {
|
|
let md = '';
|
|
|
|
md += `## Turn ${turn.turnNumber}\n\n`;
|
|
|
|
// User message
|
|
if (turn.userMessage) {
|
|
md += `### 👤 User Request\n`;
|
|
md += `**Time:** ${new Date(turn.userMessage.timestamp).toLocaleString()}\n\n`;
|
|
md += '```\n';
|
|
md += turn.userMessage.content.substring(0, 500);
|
|
if (turn.userMessage.content.length > 500) {
|
|
md += '\n... (truncated)';
|
|
}
|
|
md += '\n```\n\n';
|
|
}
|
|
|
|
// Assistant response
|
|
if (turn.assistantMessage) {
|
|
md += `### 🤖 Assistant Response\n`;
|
|
md += `**Time:** ${new Date(turn.assistantMessage.timestamp).toLocaleString()}\n\n`;
|
|
|
|
// Text content
|
|
if (turn.assistantMessage.textContent.trim()) {
|
|
md += '**Response:**\n```\n';
|
|
md += turn.assistantMessage.textContent.substring(0, 500);
|
|
if (turn.assistantMessage.textContent.length > 500) {
|
|
md += '\n... (truncated)';
|
|
}
|
|
md += '\n```\n\n';
|
|
}
|
|
|
|
// Thinking
|
|
if (turn.assistantMessage.thinkingContent?.trim()) {
|
|
md += '**Thinking:**\n```\n';
|
|
md += turn.assistantMessage.thinkingContent.substring(0, 300);
|
|
if (turn.assistantMessage.thinkingContent.length > 300) {
|
|
md += '\n... (truncated)';
|
|
}
|
|
md += '\n```\n\n';
|
|
}
|
|
|
|
// Tool uses
|
|
if (turn.assistantMessage.toolUses.length > 0) {
|
|
md += `**Tools Used:** ${turn.assistantMessage.toolUses.length}\n\n`;
|
|
for (const tool of turn.assistantMessage.toolUses) {
|
|
md += `- **${tool.name}**\n`;
|
|
md += ` \`\`\`json\n`;
|
|
const inputStr = JSON.stringify(tool.input, null, 2);
|
|
md += inputStr.substring(0, 200);
|
|
if (inputStr.length > 200) {
|
|
md += '\n ... (truncated)';
|
|
}
|
|
md += '\n ```\n';
|
|
}
|
|
md += '\n';
|
|
}
|
|
}
|
|
|
|
// Tool results summary
|
|
if (turn.toolResults && turn.toolResults.length > 0) {
|
|
md += `**Tool Results:** ${turn.toolResults.length} results received\n\n`;
|
|
}
|
|
|
|
md += '---\n\n';
|
|
return md;
|
|
}
|
|
|
|
function formatTranscriptToMarkdown(transcriptPath: string): string {
|
|
const parser = new TranscriptParser(transcriptPath);
|
|
const turns = extractConversationTurns(parser);
|
|
const stats = parser.getParseStats();
|
|
const tokens = parser.getTotalTokenUsage();
|
|
|
|
let md = `# Transcript Context Analysis\n\n`;
|
|
md += `**File:** ${basename(transcriptPath)}\n`;
|
|
md += `**Parsed:** ${new Date().toLocaleString()}\n\n`;
|
|
|
|
md += `## Statistics\n\n`;
|
|
md += `- Total entries: ${stats.totalLines}\n`;
|
|
md += `- Successfully parsed: ${stats.parsedEntries}\n`;
|
|
md += `- Failed lines: ${stats.failedLines}\n`;
|
|
md += `- Conversation turns: ${turns.length}\n\n`;
|
|
|
|
md += `## Token Usage\n\n`;
|
|
md += `- Input tokens: ${tokens.inputTokens.toLocaleString()}\n`;
|
|
md += `- Output tokens: ${tokens.outputTokens.toLocaleString()}\n`;
|
|
md += `- Cache creation: ${tokens.cacheCreationTokens.toLocaleString()}\n`;
|
|
md += `- Cache read: ${tokens.cacheReadTokens.toLocaleString()}\n`;
|
|
const totalTokens = tokens.inputTokens + tokens.outputTokens;
|
|
md += `- Total: ${totalTokens.toLocaleString()}\n\n`;
|
|
|
|
md += `---\n\n`;
|
|
md += `# Conversation Turns\n\n`;
|
|
|
|
// Format each turn
|
|
for (const turn of turns.slice(0, 20)) { // Limit to first 20 turns for readability
|
|
md += formatTurnToMarkdown(turn);
|
|
}
|
|
|
|
if (turns.length > 20) {
|
|
md += `\n_... ${turns.length - 20} more turns omitted for brevity_\n`;
|
|
}
|
|
|
|
return md;
|
|
}
|
|
|
|
// Main execution
|
|
const transcriptPath = process.argv[2];
|
|
|
|
if (!transcriptPath) {
|
|
console.error('Usage: tsx scripts/format-transcript-context.ts <path-to-transcript.jsonl>');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Parsing transcript: ${transcriptPath}`);
|
|
|
|
const markdown = formatTranscriptToMarkdown(transcriptPath);
|
|
const outputPath = transcriptPath.replace('.jsonl', '-formatted.md');
|
|
|
|
writeFileSync(outputPath, markdown, 'utf-8');
|
|
|
|
console.log(`\nFormatted transcript written to: ${outputPath}`);
|
|
console.log(`\nOpen with: cat "${outputPath}"\n`);
|