Add support for index view in context hook and refactor summary retrieval method

This commit is contained in:
Alex Newman
2025-10-21 17:28:39 -04:00
parent ef572ec032
commit 86214b93a9
3 changed files with 232 additions and 159 deletions
+5 -2
View File
@@ -8,9 +8,12 @@ import { contextHook } from '../../hooks/context.js';
import { stdin } from 'process'; import { stdin } from 'process';
try { try {
// Check for --index flag
const useIndexView = process.argv.includes('--index');
if (stdin.isTTY) { if (stdin.isTTY) {
// Running manually from terminal - print formatted output with colors // Running manually from terminal - print formatted output with colors
const contextOutput = contextHook(undefined, true); const contextOutput = contextHook(undefined, true, useIndexView);
console.log(contextOutput); console.log(contextOutput);
process.exit(0); process.exit(0);
} else { } else {
@@ -19,7 +22,7 @@ try {
stdin.on('data', (chunk) => input += chunk); stdin.on('data', (chunk) => input += chunk);
stdin.on('end', () => { stdin.on('end', () => {
const parsed = input.trim() ? JSON.parse(input) : undefined; const parsed = input.trim() ? JSON.parse(input) : undefined;
const contextOutput = contextHook(parsed, false); const contextOutput = contextHook(parsed, false, useIndexView);
const result = { const result = {
hookSpecificOutput: { hookSpecificOutput: {
hookEventName: "SessionStart", hookEventName: "SessionStart",
+159 -114
View File
@@ -30,7 +30,7 @@ const colors = {
* *
* Output: Returns formatted context string to be wrapped in hookSpecificOutput * Output: Returns formatted context string to be wrapped in hookSpecificOutput
*/ */
export function contextHook(input?: SessionStartInput, useColors: boolean = false): string { export function contextHook(input?: SessionStartInput, useColors: boolean = false, useIndexView: boolean = false): string {
// v4.0.0: Ensure worker is running before loading context // v4.0.0: Ensure worker is running before loading context
ensureWorkerRunning(); ensureWorkerRunning();
const cwd = input?.cwd ?? process.cwd(); const cwd = input?.cwd ?? process.cwd();
@@ -39,33 +39,168 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
const db = new SessionStore(); const db = new SessionStore();
try { try {
const sessions = db.getRecentSessionsWithStatus(project, 3); const summaries = db.getRecentSummariesWithSessionInfo(project, 3);
if (sessions.length === 0) { if (summaries.length === 0) {
if (useColors) { if (useColors) {
return `\n${colors.bright}${colors.cyan}📝 Recent Session Context${colors.reset}\n\n${colors.dim}No previous sessions found for this project yet.${colors.reset}\n`; return `\n${colors.bright}${colors.cyan}📝 [${project}] recent context${colors.reset}\n${colors.gray}${'─'.repeat(60)}${colors.reset}\n\n${colors.dim}No previous summaries found for this project yet.${colors.reset}\n`;
} }
return '# Recent Session Context\n\nNo previous sessions found for this project yet.'; return `# [${project}] recent context\n\nNo previous summaries found for this project yet.`;
} }
const output: string[] = []; const output: string[] = [];
// Index view: Show previous as index, latest in full at bottom (chat-style)
if (useIndexView) {
if (useColors) { if (useColors) {
output.push(''); output.push('');
output.push(`${colors.bright}${colors.cyan}📝 Recent Session Context${colors.reset}`); output.push(`${colors.bright}${colors.cyan}📝 [${project}] recent context${colors.reset}`);
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
output.push(`${colors.dim}Showing last ${sessions.length} session(s) for ${colors.reset}${colors.bright}${project}${colors.reset}`);
output.push(''); output.push('');
} else { } else {
output.push('# Recent Session Context'); output.push(`# [${project}] recent context`);
output.push('');
output.push(`Showing last ${sessions.length} session(s) for **${project}**:`);
output.push(''); output.push('');
} }
for (const session of sessions) { // Show index of previous summaries (oldest to newest)
if (!session.sdk_session_id) continue; if (summaries.length > 1) {
if (useColors) {
output.push(`${colors.bright}${colors.dim}Previous Requests:${colors.reset}`);
output.push('');
} else {
output.push('**Previous Requests:**');
output.push('');
}
// Iterate backwards through array (skip first which is most recent)
for (let i = summaries.length - 1; i >= 1; i--) {
const prev = summaries[i];
const prevDate = new Date(prev.created_at);
const dateTimeStr = prevDate.toLocaleString();
if (useColors) {
output.push(`${colors.dim}${dateTimeStr}:${colors.reset} ${prev.request || '(no request)'}`);
} else {
output.push(`- ${dateTimeStr}: ${prev.request || '(no request)'}`);
}
}
if (useColors) {
output.push('');
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
output.push('');
} else {
output.push('');
output.push('---');
output.push('');
}
}
// Show most recent summary in full at the bottom
const latest = summaries[0];
if (latest.request) {
if (useColors) {
output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${latest.request}`);
output.push('');
} else {
output.push(`**Request:** ${latest.request}`);
output.push('');
}
}
if (latest.learned) {
if (useColors) {
output.push(`${colors.bright}${colors.blue}Learned:${colors.reset} ${latest.learned}`);
output.push('');
} else {
output.push(`**Learned:** ${latest.learned}`);
output.push('');
}
}
if (latest.completed) {
if (useColors) {
output.push(`${colors.bright}${colors.green}Completed:${colors.reset} ${latest.completed}`);
output.push('');
} else {
output.push(`**Completed:** ${latest.completed}`);
output.push('');
}
}
if (latest.next_steps) {
if (useColors) {
output.push(`${colors.bright}${colors.magenta}Next Steps:${colors.reset} ${latest.next_steps}`);
output.push('');
} else {
output.push(`**Next Steps:** ${latest.next_steps}`);
output.push('');
}
}
// Get files for latest summary
const latestFiles = db.getFilesForSession(latest.sdk_session_id);
if (latestFiles.filesRead.length > 0) {
if (useColors) {
output.push(`${colors.dim}Files Read: ${latestFiles.filesRead.join(', ')}${colors.reset}`);
} else {
output.push(`**Files Read:** ${latestFiles.filesRead.join(', ')}`);
}
}
if (latestFiles.filesModified.length > 0) {
if (useColors) {
output.push(`${colors.dim}Files Modified: ${latestFiles.filesModified.join(', ')}${colors.reset}`);
} else {
output.push(`**Files Modified:** ${latestFiles.filesModified.join(', ')}`);
}
}
const latestDate = new Date(latest.created_at).toLocaleString();
if (useColors) {
output.push(`${colors.dim}Date: ${latestDate}${colors.reset}`);
} else {
output.push(`**Date:** ${latestDate}`);
}
if (useColors) {
output.push('');
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
}
return output.join('\n');
}
if (useColors) {
output.push('');
output.push(`${colors.bright}${colors.cyan}📝 [${project}] recent context${colors.reset}`);
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
} else {
output.push(`# [${project}] recent context`);
output.push('');
}
let previousSessionId: string | null = null;
let isFirstSummary = true;
for (const summary of summaries) {
// Add session break indicator if this is a different session
const isNewSession = previousSessionId !== null && summary.sdk_session_id !== previousSessionId;
if (isNewSession) {
if (useColors) {
output.push('');
output.push(`${colors.dim}${'─'.repeat(23)} New Session ${'─'.repeat(24)}${colors.reset}`);
output.push('');
} else {
output.push('');
output.push('--- New Session ---');
output.push('');
}
} else if (!isFirstSummary) {
// Only show regular separator if not first summary and not showing "New Session"
if (useColors) { if (useColors) {
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
output.push(''); output.push('');
@@ -73,21 +208,14 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
output.push('---'); output.push('---');
output.push(''); output.push('');
} }
// Check if session has a summary
if (session.has_summary) {
const summary = db.getSummaryForSession(session.sdk_session_id);
if (summary) {
const promptLabel = summary.prompt_number ? ` (Prompt #${summary.prompt_number})` : '';
if (useColors) {
output.push(`${colors.bright}${colors.green}✓ Summary${promptLabel}${colors.reset}`);
output.push('');
} else { } else {
output.push(`**Summary${promptLabel}**`); // First summary - just add a blank line after header
if (useColors) {
output.push(''); output.push('');
} }
}
isFirstSummary = false;
if (summary.request) { if (summary.request) {
if (useColors) { if (useColors) {
@@ -95,6 +223,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
output.push(''); output.push('');
} else { } else {
output.push(`**Request:** ${summary.request}`); output.push(`**Request:** ${summary.request}`);
output.push('');
} }
} }
@@ -104,6 +233,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
output.push(''); output.push('');
} else { } else {
output.push(`**Learned:** ${summary.learned}`); output.push(`**Learned:** ${summary.learned}`);
output.push('');
} }
} }
@@ -113,6 +243,7 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
output.push(''); output.push('');
} else { } else {
output.push(`**Completed:** ${summary.completed}`); output.push(`**Completed:** ${summary.completed}`);
output.push('');
} }
} }
@@ -122,11 +253,12 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
output.push(''); output.push('');
} else { } else {
output.push(`**Next Steps:** ${summary.next_steps}`); output.push(`**Next Steps:** ${summary.next_steps}`);
output.push('');
} }
} }
// Get files from observations (not from summary which is never populated) // Get files from observations (not from summary which is never populated)
const sessionFiles = db.getFilesForSession(session.sdk_session_id); const sessionFiles = db.getFilesForSession(summary.sdk_session_id);
if (sessionFiles.filesRead.length > 0) { if (sessionFiles.filesRead.length > 0) {
if (useColors) { if (useColors) {
@@ -150,104 +282,17 @@ export function contextHook(input?: SessionStartInput, useColors: boolean = fals
} else { } else {
output.push(`**Date:** ${dateTime}`); output.push(`**Date:** ${dateTime}`);
} }
}
} else if (session.status === 'active') {
// Active session without summary - show observation titles
if (useColors) {
output.push(`${colors.bright}${colors.yellow}⏳ In Progress${colors.reset}`);
output.push('');
} else {
output.push(`**In Progress**`);
output.push('');
}
if (session.user_prompt) {
if (useColors) {
output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${session.user_prompt}`);
output.push('');
} else {
output.push(`**Request:** ${session.user_prompt}`);
}
}
const observations = db.getObservationsForSession(session.sdk_session_id);
if (observations.length > 0) {
output.push('');
if (useColors) {
output.push(`${colors.bright}Observations (${observations.length}):${colors.reset}`);
for (const obs of observations) {
output.push(` ${colors.dim}${colors.reset} ${obs.title}`);
}
output.push('');
} else {
output.push(`**Observations (${observations.length}):**`);
for (const obs of observations) {
output.push(`- ${obs.title}`);
}
}
} else {
output.push('');
if (useColors) {
output.push(`${colors.dim}No observations yet${colors.reset}`);
output.push('');
} else {
output.push('*No observations yet*');
}
}
output.push('');
const activeDateTime = new Date(session.started_at).toLocaleString();
if (useColors) {
output.push(`${colors.dim}Status: Active - summary pending${colors.reset}`);
output.push(`${colors.dim}Date: ${activeDateTime}${colors.reset}`);
} else {
output.push(`**Status:** Active - summary pending`);
output.push(`**Date:** ${activeDateTime}`);
}
} else {
// Failed or completed session without summary
const displayStatus = session.status === 'failed' ? 'stopped' : session.status;
const statusIcon = session.status === 'failed' ? '⚠️' : '○';
if (useColors) {
const statusColor = session.status === 'failed' ? colors.yellow : colors.gray;
output.push(`${colors.bright}${statusColor}${statusIcon} ${displayStatus.charAt(0).toUpperCase() + displayStatus.slice(1)}${colors.reset}`);
output.push('');
} else {
output.push(`**${displayStatus.charAt(0).toUpperCase() + displayStatus.slice(1)}**`);
output.push('');
}
if (session.user_prompt) {
if (useColors) {
output.push(`${colors.bright}${colors.yellow}Request:${colors.reset} ${session.user_prompt}`);
output.push('');
} else {
output.push(`**Request:** ${session.user_prompt}`);
}
}
output.push('');
const failedDateTime = new Date(session.started_at).toLocaleString();
if (useColors) {
output.push(`${colors.dim}Status: ${displayStatus} - no summary available${colors.reset}`);
output.push(`${colors.dim}Date: ${failedDateTime}${colors.reset}`);
} else {
output.push(`**Status:** ${displayStatus} - no summary available`);
output.push(`**Date:** ${failedDateTime}`);
}
}
if (!useColors) { if (!useColors) {
output.push(''); output.push('');
} }
previousSessionId = summary.sdk_session_id;
} }
if (useColors) { if (useColors) {
output.push(''); output.push('');
output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); output.push(`${colors.gray}${'─'.repeat(60)}${colors.reset}`);
output.push('');
} }
return output.join('\n'); return output.join('\n');
+25
View File
@@ -433,6 +433,31 @@ export class SessionStore {
return stmt.all(project, limit) as any[]; return stmt.all(project, limit) as any[];
} }
/**
* Get recent summaries with session info for context display
*/
getRecentSummariesWithSessionInfo(project: string, limit: number = 3): Array<{
sdk_session_id: string;
request: string | null;
learned: string | null;
completed: string | null;
next_steps: string | null;
prompt_number: number | null;
created_at: string;
}> {
const stmt = this.db.prepare(`
SELECT
sdk_session_id, request, learned, completed, next_steps,
prompt_number, created_at
FROM session_summaries
WHERE project = ?
ORDER BY created_at_epoch DESC
LIMIT ?
`);
return stmt.all(project, limit) as any[];
}
/** /**
* Get recent observations for a project * Get recent observations for a project
*/ */