claude-code-ultimate-guide/quiz/src/session.js
Florian BRUNIAUX 5b526f5e53 feat(audit): enhanced stack detection with integrations
- Detailed stack breakdown: runtime, framework, test, bundler, database
- Generic integration detection (25+ packages: Clerk, Stripe, OpenAI, Sentry...)
- jq fallback: grep-based JSON parsing when jq not installed
- Stack recap at top of human output + full stack object in JSON
- README prompt updated: Stack Recap first, CLAUDE.md template ~100 lines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 14:52:55 +01:00

169 lines
4.6 KiB
JavaScript

/**
* Session persistence - save quiz history to ~/.claude-quiz/
*/
import { mkdir, writeFile, readFile, readdir } from 'fs/promises';
import { join } from 'path';
import { homedir } from 'os';
const QUIZ_DIR = join(homedir(), '.claude-quiz');
const SESSIONS_DIR = join(QUIZ_DIR, 'sessions');
const STATS_FILE = join(QUIZ_DIR, 'stats.json');
/**
* Ensure quiz directories exist
*/
async function ensureDirs() {
try {
await mkdir(QUIZ_DIR, { recursive: true });
await mkdir(SESSIONS_DIR, { recursive: true });
} catch (err) {
// Ignore if already exists
}
}
/**
* Generate session ID from current timestamp
*/
function generateSessionId() {
const now = new Date();
const date = now.toISOString().split('T')[0];
const time = now.toTimeString().split(' ')[0].replace(/:/g, '');
return `${date}_${time}`;
}
/**
* Save quiz session to file
*/
export async function saveSession(results) {
try {
await ensureDirs();
const sessionId = generateSessionId();
const sessionData = {
session_id: sessionId,
profile: results.profile,
topics: results.topics,
questions_total: results.totalQuestions,
questions_correct: results.correctCount,
questions_skipped: results.skippedCount,
percentage: results.questions.filter(q => !q.skipped).length > 0
? Math.round((results.correctCount / results.questions.filter(q => !q.skipped).length) * 100)
: 0,
duration_seconds: Math.round((results.endTime - results.startTime) / 1000),
by_category: results.byCategory,
wrong_questions: results.questions
.filter(q => !q.correct && !q.skipped)
.map(q => ({
id: q.question.id,
category: q.question.category,
category_id: q.question.category_id,
question: q.question.question,
user_answer: q.userAnswer,
correct_answer: q.question.correct,
doc_reference: q.question.doc_reference
})),
timestamp: new Date().toISOString()
};
// Save session file
const sessionFile = join(SESSIONS_DIR, `${sessionId}.json`);
await writeFile(sessionFile, JSON.stringify(sessionData, null, 2));
// Update aggregate stats
await updateStats(sessionData);
return sessionId;
} catch (err) {
// Silent fail - don't break quiz if persistence fails
console.error('Warning: Could not save session:', err.message);
return null;
}
}
/**
* Update aggregate statistics
*/
async function updateStats(sessionData) {
let stats = {
total_sessions: 0,
total_questions: 0,
total_correct: 0,
by_category: {},
by_profile: {},
last_session: null
};
// Load existing stats
try {
const existing = await readFile(STATS_FILE, 'utf-8');
stats = JSON.parse(existing);
} catch {
// File doesn't exist yet
}
// Update totals
stats.total_sessions++;
stats.total_questions += sessionData.questions_total;
stats.total_correct += sessionData.questions_correct;
stats.last_session = sessionData.timestamp;
// Update by category
for (const [categoryId, categoryStats] of Object.entries(sessionData.by_category)) {
if (!stats.by_category[categoryId]) {
stats.by_category[categoryId] = { name: categoryStats.name, correct: 0, total: 0 };
}
stats.by_category[categoryId].correct += categoryStats.correct;
stats.by_category[categoryId].total += categoryStats.total;
}
// Update by profile
if (!stats.by_profile[sessionData.profile]) {
stats.by_profile[sessionData.profile] = { sessions: 0, correct: 0, total: 0 };
}
stats.by_profile[sessionData.profile].sessions++;
stats.by_profile[sessionData.profile].correct += sessionData.questions_correct;
stats.by_profile[sessionData.profile].total += sessionData.questions_total;
// Save updated stats
await writeFile(STATS_FILE, JSON.stringify(stats, null, 2));
}
/**
* Load previous session (for retry)
*/
export async function loadSession(sessionId) {
try {
const sessionFile = join(SESSIONS_DIR, `${sessionId}.json`);
const content = await readFile(sessionFile, 'utf-8');
return JSON.parse(content);
} catch {
return null;
}
}
/**
* List recent sessions
*/
export async function listSessions(limit = 10) {
try {
await ensureDirs();
const files = await readdir(SESSIONS_DIR);
const jsonFiles = files.filter(f => f.endsWith('.json')).sort().reverse();
return jsonFiles.slice(0, limit).map(f => f.replace('.json', ''));
} catch {
return [];
}
}
/**
* Get aggregate stats
*/
export async function getStats() {
try {
const content = await readFile(STATS_FILE, 'utf-8');
return JSON.parse(content);
} catch {
return null;
}
}