#!/usr/bin/env node /** * DSS Session Summary Hook * Generates a summary report at the end of each Claude Code session. * Written from scratch for DSS. */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); // Configuration const DEFAULT_CONFIG = { session_summary: { enabled: true, output_file: '.dss-session-summary.md', include_git_diff: true, include_file_list: true, max_diff_lines: 100 } }; function loadConfig() { const configPath = path.join(process.env.HOME || '', '.dss', 'hooks-config.json'); try { if (fs.existsSync(configPath)) { const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); return { ...DEFAULT_CONFIG, ...userConfig }; } } catch (e) { // Use defaults } return DEFAULT_CONFIG; } function getGitInfo() { const info = { branch: '', status: '', diff: '', modifiedFiles: [] }; try { // Check if in git repo execSync('git rev-parse --is-inside-work-tree', { stdio: 'pipe' }); // Get branch info.branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim(); // Get status info.status = execSync('git status --short', { encoding: 'utf8' }).trim(); // Get modified files const statusLines = info.status.split('\n').filter(Boolean); info.modifiedFiles = statusLines.map(line => { const parts = line.trim().split(/\s+/); return { status: parts[0], file: parts.slice(1).join(' ') }; }); // Get diff summary try { info.diff = execSync('git diff --stat', { encoding: 'utf8' }).trim(); } catch (e) { info.diff = ''; } } catch (e) { // Not a git repo or git not available } return info; } function getSessionStats() { // Try to read from session state if available const stats = { startTime: new Date().toISOString(), filesModified: 0, linesAdded: 0, linesRemoved: 0 }; try { // Get diff stats from git const diffStat = execSync('git diff --numstat', { encoding: 'utf8' }); const lines = diffStat.trim().split('\n').filter(Boolean); for (const line of lines) { const [added, removed] = line.split('\t'); stats.linesAdded += parseInt(added) || 0; stats.linesRemoved += parseInt(removed) || 0; stats.filesModified++; } } catch (e) { // Git not available } return stats; } function generateReport(config) { const summaryConfig = config.session_summary || {}; const gitInfo = getGitInfo(); const stats = getSessionStats(); const timestamp = new Date().toLocaleString(); const lines = []; lines.push('# DSS Session Summary'); lines.push(`\n**Generated:** ${timestamp}`); if (gitInfo.branch) { lines.push(`**Branch:** ${gitInfo.branch}`); } lines.push('\n## Changes Overview'); lines.push(''); lines.push(`- Files modified: ${stats.filesModified}`); lines.push(`- Lines added: +${stats.linesAdded}`); lines.push(`- Lines removed: -${stats.linesRemoved}`); if (summaryConfig.include_file_list && gitInfo.modifiedFiles.length > 0) { lines.push('\n## Modified Files'); lines.push(''); lines.push('| Status | File |'); lines.push('|--------|------|'); const statusLabels = { 'M': 'Modified', 'A': 'Added', 'D': 'Deleted', 'R': 'Renamed', '??': 'Untracked' }; for (const file of gitInfo.modifiedFiles.slice(0, 20)) { const label = statusLabels[file.status] || file.status; lines.push(`| ${label} | ${file.file} |`); } if (gitInfo.modifiedFiles.length > 20) { lines.push(`| ... | +${gitInfo.modifiedFiles.length - 20} more files |`); } } if (summaryConfig.include_git_diff && gitInfo.diff) { lines.push('\n## Diff Summary'); lines.push(''); lines.push('```'); const diffLines = gitInfo.diff.split('\n'); const maxLines = summaryConfig.max_diff_lines || 100; lines.push(diffLines.slice(0, maxLines).join('\n')); if (diffLines.length > maxLines) { lines.push(`... (${diffLines.length - maxLines} more lines)`); } lines.push('```'); } lines.push('\n---'); lines.push('*Generated by DSS Session Summary Hook*'); return lines.join('\n'); } function main() { const config = loadConfig(); if (!config.session_summary?.enabled) { process.exit(0); } try { const report = generateReport(config); const outputFile = config.session_summary.output_file || '.dss-session-summary.md'; const outputPath = path.join(process.cwd(), outputFile); fs.writeFileSync(outputPath, report, 'utf8'); // Output confirmation console.log(JSON.stringify({ systemMessage: `Session summary saved to ${outputFile}`, continue: true })); } catch (e) { // Fail silently } process.exit(0); } main();