mirror of
https://github.com/obra/superpowers.git
synced 2026-04-27 03:59:04 +08:00
Core skills library as Claude Code plugin: - Testing skills: TDD, async testing, anti-patterns - Debugging skills: Systematic debugging, root cause tracing - Collaboration skills: Brainstorming, planning, code review - Meta skills: Creating and testing skills Features: - SessionStart hook for context injection - Skills-search tool for discovery - Commands: /brainstorm, /write-plan, /execute-plan - Data directory at ~/.superpowers/
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import fs from 'fs';
|
|
import readline from 'readline';
|
|
import { ConversationExchange } from './types.js';
|
|
import crypto from 'crypto';
|
|
|
|
interface JSONLMessage {
|
|
type: string;
|
|
message?: {
|
|
role: 'user' | 'assistant';
|
|
content: string | Array<{ type: string; text?: string }>;
|
|
};
|
|
timestamp?: string;
|
|
uuid?: string;
|
|
}
|
|
|
|
export async function parseConversation(
|
|
filePath: string,
|
|
projectName: string,
|
|
archivePath: string
|
|
): Promise<ConversationExchange[]> {
|
|
const exchanges: ConversationExchange[] = [];
|
|
const fileStream = fs.createReadStream(filePath);
|
|
const rl = readline.createInterface({
|
|
input: fileStream,
|
|
crlfDelay: Infinity
|
|
});
|
|
|
|
let lineNumber = 0;
|
|
let currentExchange: {
|
|
userMessage: string;
|
|
userLine: number;
|
|
assistantMessages: string[];
|
|
lastAssistantLine: number;
|
|
timestamp: string;
|
|
} | null = null;
|
|
|
|
const finalizeExchange = () => {
|
|
if (currentExchange && currentExchange.assistantMessages.length > 0) {
|
|
const exchange: ConversationExchange = {
|
|
id: crypto
|
|
.createHash('md5')
|
|
.update(`${archivePath}:${currentExchange.userLine}-${currentExchange.lastAssistantLine}`)
|
|
.digest('hex'),
|
|
project: projectName,
|
|
timestamp: currentExchange.timestamp,
|
|
userMessage: currentExchange.userMessage,
|
|
assistantMessage: currentExchange.assistantMessages.join('\n\n'),
|
|
archivePath,
|
|
lineStart: currentExchange.userLine,
|
|
lineEnd: currentExchange.lastAssistantLine
|
|
};
|
|
exchanges.push(exchange);
|
|
}
|
|
};
|
|
|
|
for await (const line of rl) {
|
|
lineNumber++;
|
|
|
|
try {
|
|
const parsed: JSONLMessage = JSON.parse(line);
|
|
|
|
// Skip non-message types
|
|
if (parsed.type !== 'user' && parsed.type !== 'assistant') {
|
|
continue;
|
|
}
|
|
|
|
if (!parsed.message) {
|
|
continue;
|
|
}
|
|
|
|
// Extract text from message content
|
|
let text = '';
|
|
if (typeof parsed.message.content === 'string') {
|
|
text = parsed.message.content;
|
|
} else if (Array.isArray(parsed.message.content)) {
|
|
text = parsed.message.content
|
|
.filter(block => block.type === 'text' && block.text)
|
|
.map(block => block.text)
|
|
.join('\n');
|
|
}
|
|
|
|
// Skip empty messages
|
|
if (!text.trim()) {
|
|
continue;
|
|
}
|
|
|
|
if (parsed.message.role === 'user') {
|
|
// Finalize previous exchange before starting new one
|
|
finalizeExchange();
|
|
|
|
// Start new exchange
|
|
currentExchange = {
|
|
userMessage: text,
|
|
userLine: lineNumber,
|
|
assistantMessages: [],
|
|
lastAssistantLine: lineNumber,
|
|
timestamp: parsed.timestamp || new Date().toISOString()
|
|
};
|
|
} else if (parsed.message.role === 'assistant' && currentExchange) {
|
|
// Accumulate assistant messages
|
|
currentExchange.assistantMessages.push(text);
|
|
currentExchange.lastAssistantLine = lineNumber;
|
|
// Update timestamp to last assistant message
|
|
if (parsed.timestamp) {
|
|
currentExchange.timestamp = parsed.timestamp;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Skip malformed JSON lines
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Finalize last exchange
|
|
finalizeExchange();
|
|
|
|
return exchanges;
|
|
}
|