boot_report
Generate a session orientation report by reading identity, memory, and transcripts. Detects gaps, calculates an orientation score, and surfaces open loops and PreCompact captures to establish context before work.
Instructions
Generate a session orientation report. Read-only — does not modify any stored data. Reads the identity document from disk, scans the memory database for statistics and the latest checkpoint, finds the most recent transcript file, detects structural gaps (missing identity, stale memories, no checkpoint, etc.), and calculates a 0-100 orientation score across 6 criteria. Also surfaces open loops from prior sessions and any PreCompact captures that preserve context from compacted sessions. If PreCompact captures exist, call list_captures and read_capture to recover pre-compaction context before proceeding with work. Call this first thing every session to establish context before doing any work.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| identity_path | Yes | Absolute or relative path to the identity document (e.g., '.rekindle/identity.md'). This file describes who the user is and how to work with them. If the file does not exist, a critical gap is reported. | |
| transcript_dir | Yes | Absolute or relative path to the transcripts directory (e.g., '.rekindle/transcripts'). The most recent .md file in this directory is read and included in the report. If the directory is empty or missing, an info-level gap is reported. | |
| project | No | Active project name for scoped orientation. When provided, the orientation score includes a project-specific criterion and memory statistics are filtered to this project. |
Implementation Reference
- src/tools/boot-report.ts:7-74 (handler)The main tool registration function for 'boot_report'. Defines the tool with its description, three input params (identity_path, transcript_dir, optional project), and the async handler that calls OrientationService.generate(), appends open loops from storage, appends PreCompact captures from captureManager, and returns markdown text.
export function registerBootReport( server: McpServer, storage: RekindleStorage, captureManager: CaptureManager ): void { const orientationService = new OrientationService(storage); server.tool( "boot_report", "Generate a session orientation report. Read-only — does not modify any stored data. Reads the identity document from disk, scans the memory database for statistics and the latest checkpoint, finds the most recent transcript file, detects structural gaps (missing identity, stale memories, no checkpoint, etc.), and calculates a 0-100 orientation score across 6 criteria. Also surfaces open loops from prior sessions and any PreCompact captures that preserve context from compacted sessions. If PreCompact captures exist, call list_captures and read_capture to recover pre-compaction context before proceeding with work. Call this first thing every session to establish context before doing any work.", { identity_path: z .string() .describe("Absolute or relative path to the identity document (e.g., '.rekindle/identity.md'). This file describes who the user is and how to work with them. If the file does not exist, a critical gap is reported."), transcript_dir: z .string() .describe( "Absolute or relative path to the transcripts directory (e.g., '.rekindle/transcripts'). The most recent .md file in this directory is read and included in the report. If the directory is empty or missing, an info-level gap is reported." ), project: z .string() .optional() .describe("Active project name for scoped orientation. When provided, the orientation score includes a project-specific criterion and memory statistics are filtered to this project."), }, async ({ identity_path, transcript_dir, project }) => { const result = orientationService.generate({ identityPath: identity_path, transcriptDir: transcript_dir, project, }); let markdown = OrientationRenderer.toMarkdown(result); const openLoops = storage.list({ category: "context" }) .filter((m) => m.type === "open_loop") .filter((m) => !project || m.project === project) .slice(0, 10); if (openLoops.length > 0) { markdown += "\n\n## Open Loops\n\n"; for (const loop of openLoops) { markdown += `- ${loop.content}\n`; } } const allCaptures = captureManager.listCaptures(); const projectCaptures = project ? allCaptures.filter((c) => c.project === project) : allCaptures; const recentCaptures = projectCaptures .sort((a, b) => new Date(b.captured_at).getTime() - new Date(a.captured_at).getTime()) .slice(0, 5); if (recentCaptures.length > 0) { markdown += "\n\n## PreCompact Captures\n\n"; markdown += "Context from prior compaction events is available for recovery:\n\n"; for (const cap of recentCaptures) { const reviewed = cap.reviewed_at ? " ✓ reviewed" : ""; markdown += `- **${cap.id}** (${cap.captured_at}, ${cap.message_count} messages, ${cap.source}${reviewed})\n`; } markdown += "\nCall `list_captures` / `read_capture` to recover pre-compaction context.\n"; } return { content: [{ type: "text" as const, text: markdown }], }; } ); } - OrientationService.generate() reads identity from disk, fetches memory stats and latest checkpoint from storage, finds latest transcript, calls detectGaps() and calculateScore(), and returns an OrientationResult.
export class OrientationService { constructor(private storage: RekindleStorage) {} generate(config: OrientationConfig): OrientationResult { let identityLoaded = false; let identityContent: string | undefined; if (existsSync(config.identityPath)) { const raw = readFileSync(config.identityPath, "utf-8"); identityLoaded = raw.trim().length > 0; if (identityLoaded) identityContent = raw; } const stats = this.storage.stats(); const checkpoint = this.storage.getLatestCheckpoint(config.project); const transcript = findLatestTranscript(config.transcriptDir); const hasTranscript = transcript !== null; const hasCheckpoint = checkpoint !== null; const gaps = detectGaps(stats, identityLoaded, hasTranscript, hasCheckpoint); const { score, breakdown } = calculateScore( identityLoaded, hasCheckpoint, hasTranscript, stats, config.project ); const preview = transcript && transcript.content.length > 1500 ? transcript.content.slice(0, 1500) + "\n\n[...truncated]" : transcript?.content; return { identity: { loaded: identityLoaded, path: config.identityPath, content: identityContent, }, memoryStats: stats, checkpoint: { exists: hasCheckpoint, content: checkpoint?.content, created_at: checkpoint?.created_at, }, transcript: { exists: hasTranscript, name: transcript?.name, preview, }, gaps, score, scoreBreakdown: breakdown, }; } } - OrientationRenderer.toMarkdown() formats the OrientationResult into a structured markdown report including identity, memories, checkpoint, transcript, gaps, and score.
static toMarkdown(result: OrientationResult): string { const sections: string[] = []; if (result.identity.loaded && result.identity.content) { sections.push( `## Identity\nLoaded from ${result.identity.path}\n\n${result.identity.content}` ); } else { sections.push(`## Identity\nNot found at ${result.identity.path}`); } const catBreakdown = Object.entries(result.memoryStats.byCategory) .map(([cat, count]) => ` ${cat}: ${count}`) .join("\n"); const projBreakdown = Object.entries(result.memoryStats.byProject) .map(([proj, count]) => ` ${proj}: ${count}`) .join("\n"); sections.push( `## Memories\n${result.memoryStats.total} total (${result.memoryStats.recentCount} in last 7 days)\n\nBy category:\n${catBreakdown || " (none)"}\n\nBy project:\n${projBreakdown || " (none)"}` ); if (result.checkpoint.exists && result.checkpoint.content) { sections.push( `## Last Checkpoint\n${result.checkpoint.created_at}\n\n${result.checkpoint.content}` ); } else { sections.push(`## Last Checkpoint\nNone found`); } if (result.transcript.exists && result.transcript.preview) { sections.push( `## Last Session Transcript\n${result.transcript.name}\n\n${result.transcript.preview}` ); } else { sections.push( `## Last Session Transcript\nNo transcripts found in transcript directory` ); } if (result.gaps.length > 0) { sections.push( `## Gaps Detected\n${result.gaps.map((g) => `- [${g.severity}] ${g.code}: ${g.message}`).join("\n")}` ); } else { sections.push(`## Gaps Detected\nNone. Orientation looks complete.`); } const scoreLines = result.scoreBreakdown .map((item) => { if (item.earned) { return `+${String(item.points).padStart(2)} ${item.label}`; } return ` ✗ ${item.label} (${item.points}pts)`; }) .join("\n"); sections.push( `## Orientation Score\n${result.score}/100\n\n${scoreLines}\n\nThis score is a structural checklist, not a guarantee that all relevant context was loaded.` ); return sections.join("\n\n---\n\n"); } static toJSON(result: OrientationResult): string { return JSON.stringify(result); } } - src/orientation/types.ts:1-35 (helper)Type definitions: OrientationConfig, Gap, ScoreItem, ScoreResult, and OrientationResult used throughout the orientation system.
import type { MemoryStats } from "../storage/sqlite.js"; export interface OrientationConfig { identityPath: string; transcriptDir: string; project?: string; } export interface Gap { code: string; severity: "info" | "warning" | "critical"; message: string; } export interface ScoreItem { label: string; points: number; earned: boolean; } export interface ScoreResult { score: number; breakdown: ScoreItem[]; } export interface OrientationResult { identity: { loaded: boolean; path: string; content?: string }; memoryStats: MemoryStats; checkpoint: { exists: boolean; content?: string; created_at?: string }; transcript: { exists: boolean; name?: string; preview?: string }; gaps: Gap[]; score: number; scoreBreakdown: ScoreItem[]; } - src/orientation/GapDetector.ts:4-72 (helper)Detects structural gaps: missing identity, empty memories, missing categories, stale memories, missing checkpoint, missing transcript.
export function detectGaps( stats: MemoryStats, hasIdentity: boolean, hasTranscript: boolean, hasCheckpoint: boolean ): Gap[] { const gaps: Gap[] = []; if (!hasIdentity) { gaps.push({ code: "identity_missing", severity: "critical", message: "No identity document found. Run 'npx rekindle init' or create .rekindle/identity.md", }); } if (stats.total === 0) { gaps.push({ code: "memories_empty", severity: "warning", message: "No memories stored yet. Start storing memories during your session.", }); } const expectedCategories = [ "preference", "lesson", "context", "relationship", ] as const; for (const cat of expectedCategories) { if (!stats.byCategory[cat]) { gaps.push({ code: `category_empty_${cat}`, severity: "info", message: `No ${cat} memories stored`, }); } } if (stats.recentCount === 0 && stats.total > 0) { gaps.push({ code: "recent_memory_stale", severity: "warning", message: "No memories stored in the last 7 days. Consider storing a session checkpoint.", }); } if (!hasCheckpoint) { gaps.push({ code: "checkpoint_missing", severity: "warning", message: "No checkpoint found. Use end_session to capture session state.", }); } if (!hasTranscript) { gaps.push({ code: "transcript_missing", severity: "info", message: "No session transcripts found. Configure the session capture hook for richer orientation.", }); } return gaps; } - src/orientation/Scorer.ts:4-58 (helper)Calculates a 0-100 orientation score based on 6 criteria: identity, checkpoint, transcript, recent memories, relationship/preference memories, and project memories.
export function calculateScore( hasIdentity: boolean, hasCheckpoint: boolean, hasTranscript: boolean, stats: MemoryStats, project?: string ): ScoreResult { const hasProjectMemories = project ? (stats.byProject[project] ?? 0) > 0 : Object.keys(stats.byProject).some((k) => k !== "(none)"); const breakdown = [ { label: "Identity document loaded", points: 20, earned: hasIdentity, }, { label: "Recent checkpoint exists", points: 20, earned: hasCheckpoint, }, { label: "Session transcript found", points: 20, earned: hasTranscript, }, { label: "Recent memories exist (last 7 days)", points: 20, earned: stats.recentCount > 0, }, { label: "Relationship/preference memories populated", points: 10, earned: (stats.byCategory["relationship"] ?? 0) > 0 || (stats.byCategory["preference"] ?? 0) > 0, }, { label: project ? `Memories found for project "${project}"` : "Project-scoped memories found", points: 10, earned: hasProjectMemories, }, ]; const score = breakdown.reduce( (sum, item) => sum + (item.earned ? item.points : 0), 0 ); return { score, breakdown }; } - src/server.ts:27-34 (registration)Registration of boot_report tool: calls registerBootReport(server, storage, captureManager) in the server setup.
registerBootReport(server, storage, captureManager); registerEndSession(server, storage, captureManager); registerListCaptures(server, captureManager); registerReadCapture(server, captureManager); registerCaptureNow(server, captureManager); return server; }