-- Migration 016: Session Documentation System -- Purpose: Migrate from file-based documentation (CLAUDE.md, plan files) to database -- Dependencies: 010_sessions.sql (sessions table), 001_base_schema.sql (pgvector) -- ============================================================================ -- SESSION NOTES: Structured notes within sessions -- ============================================================================ CREATE TABLE IF NOT EXISTS session_notes ( id SERIAL PRIMARY KEY, session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, note_type TEXT NOT NULL CHECK (note_type IN ( 'accomplishment', 'decision', 'gotcha', 'next_steps', 'context' )), content TEXT NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), embedding vector(1024) -- For semantic search of notes ); -- Indexes for efficient querying CREATE INDEX idx_session_notes_session ON session_notes(session_id); CREATE INDEX idx_session_notes_type ON session_notes(note_type); CREATE INDEX idx_session_notes_created ON session_notes(created_at DESC); -- HNSW index for semantic similarity search CREATE INDEX idx_session_notes_embedding ON session_notes USING hnsw (embedding vector_cosine_ops); COMMENT ON TABLE session_notes IS 'Structured notes within sessions (replaces ad-hoc note files)'; COMMENT ON COLUMN session_notes.note_type IS 'Category of note: accomplishment, decision, gotcha, next_steps, context'; COMMENT ON COLUMN session_notes.embedding IS 'Vector embedding for semantic search across notes'; -- ============================================================================ -- SESSION PLANS: Plan mode plans with lifecycle tracking -- ============================================================================ CREATE TABLE IF NOT EXISTS session_plans ( id SERIAL PRIMARY KEY, session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE, plan_file_name TEXT, -- Original filename (e.g., "transient-forging-reddy.md") plan_content TEXT NOT NULL, status TEXT DEFAULT 'draft' CHECK (status IN ( 'draft', 'approved', 'executed', 'abandoned' )), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), approved_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE, embedding vector(1024) -- For semantic search of plans ); -- Indexes for efficient querying CREATE INDEX idx_session_plans_session ON session_plans(session_id); CREATE INDEX idx_session_plans_status ON session_plans(status); CREATE INDEX idx_session_plans_created ON session_plans(created_at DESC); -- HNSW index for semantic similarity search CREATE INDEX idx_session_plans_embedding ON session_plans USING hnsw (embedding vector_cosine_ops); COMMENT ON TABLE session_plans IS 'Plan mode plans with lifecycle tracking (replaces ~/.claude/plans/)'; COMMENT ON COLUMN session_plans.plan_file_name IS 'Original filename from plan mode (e.g., eloquent-yellow-cat.md)'; COMMENT ON COLUMN session_plans.status IS 'Plan lifecycle: draft → approved → executed/abandoned'; COMMENT ON COLUMN session_plans.embedding IS 'Vector embedding for semantic search across plans'; -- ============================================================================ -- PROJECT DOCUMENTATION: Persistent project-level docs -- ============================================================================ CREATE TABLE IF NOT EXISTS project_documentation ( id SERIAL PRIMARY KEY, project TEXT NOT NULL REFERENCES projects(key) ON DELETE CASCADE, doc_type TEXT NOT NULL CHECK (doc_type IN ( 'overview', 'architecture', 'guidelines', 'history' )), title TEXT NOT NULL, content TEXT NOT NULL, last_updated_session TEXT REFERENCES sessions(id) ON DELETE SET NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(project, doc_type) -- One doc of each type per project ); -- Indexes for efficient querying CREATE INDEX idx_project_docs_project ON project_documentation(project); CREATE INDEX idx_project_docs_type ON project_documentation(doc_type); CREATE INDEX idx_project_docs_updated ON project_documentation(updated_at DESC); COMMENT ON TABLE project_documentation IS 'Persistent project documentation (replaces CLAUDE.md sections)'; COMMENT ON COLUMN project_documentation.doc_type IS 'Type of documentation: overview, architecture, guidelines, history'; COMMENT ON COLUMN project_documentation.last_updated_session IS 'Session that last updated this documentation'; -- ============================================================================ -- SESSIONS TABLE ENHANCEMENTS -- ============================================================================ -- Add documentation column for full markdown documentation ALTER TABLE sessions ADD COLUMN IF NOT EXISTS documentation TEXT; -- Add migration flag to track CLAUDE.md migration status ALTER TABLE sessions ADD COLUMN IF NOT EXISTS claude_md_migrated BOOLEAN DEFAULT FALSE; COMMENT ON COLUMN sessions.documentation IS 'Full markdown documentation for session (auto-generated at end)'; COMMENT ON COLUMN sessions.claude_md_migrated IS 'Flag indicating this session migrated from CLAUDE.md file'; -- ============================================================================ -- HELPER VIEWS -- ============================================================================ -- View: Session with note counts CREATE OR REPLACE VIEW session_documentation_summary AS SELECT s.id, s.project, s.session_number, s.started_at, s.ended_at, s.status, s.summary, COUNT(DISTINCT sn.id) as note_count, COUNT(DISTINCT sp.id) as plan_count, ARRAY_AGG(DISTINCT sn.note_type) FILTER (WHERE sn.note_type IS NOT NULL) as note_types, MAX(sn.created_at) as last_note_at, MAX(sp.created_at) as last_plan_at FROM sessions s LEFT JOIN session_notes sn ON s.id = sn.session_id LEFT JOIN session_plans sp ON s.id = sp.session_id GROUP BY s.id, s.project, s.session_number, s.started_at, s.ended_at, s.status, s.summary; COMMENT ON VIEW session_documentation_summary IS 'Sessions with note and plan counts for quick overview'; -- View: Latest project documentation CREATE OR REPLACE VIEW latest_project_docs AS SELECT pd.project, pd.doc_type, pd.title, LEFT(pd.content, 200) as content_preview, pd.updated_at, s.session_number as last_updated_session_number, s.started_at as last_updated_session_date FROM project_documentation pd LEFT JOIN sessions s ON pd.last_updated_session = s.id ORDER BY pd.project, pd.doc_type; COMMENT ON VIEW latest_project_docs IS 'Latest project documentation with session context'; -- ============================================================================ -- MAINTENANCE FUNCTIONS -- ============================================================================ -- Function to clean up old session notes (optional retention policy) CREATE OR REPLACE FUNCTION cleanup_old_session_notes(retention_days INTEGER DEFAULT 180) RETURNS INTEGER AS $$ DECLARE deleted_count INTEGER; BEGIN DELETE FROM session_notes WHERE created_at < NOW() - (retention_days || ' days')::INTERVAL AND session_id IN ( SELECT id FROM sessions WHERE status = 'completed' ); GET DIAGNOSTICS deleted_count = ROW_COUNT; RETURN deleted_count; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION cleanup_old_session_notes IS 'Clean up notes from completed sessions older than retention period (default 180 days)'; -- Function to generate session documentation stats CREATE OR REPLACE FUNCTION session_documentation_stats(p_project TEXT DEFAULT NULL) RETURNS TABLE( total_sessions BIGINT, sessions_with_notes BIGINT, sessions_with_plans BIGINT, total_notes BIGINT, total_plans BIGINT, avg_notes_per_session NUMERIC, avg_plans_per_session NUMERIC ) AS $$ BEGIN RETURN QUERY SELECT COUNT(DISTINCT s.id) as total_sessions, COUNT(DISTINCT CASE WHEN sn.id IS NOT NULL THEN s.id END) as sessions_with_notes, COUNT(DISTINCT CASE WHEN sp.id IS NOT NULL THEN s.id END) as sessions_with_plans, COUNT(sn.id) as total_notes, COUNT(sp.id) as total_plans, ROUND(COUNT(sn.id)::NUMERIC / NULLIF(COUNT(DISTINCT s.id), 0), 2) as avg_notes_per_session, ROUND(COUNT(sp.id)::NUMERIC / NULLIF(COUNT(DISTINCT s.id), 0), 2) as avg_plans_per_session FROM sessions s LEFT JOIN session_notes sn ON s.id = sn.session_id LEFT JOIN session_plans sp ON s.id = sp.session_id WHERE (p_project IS NULL OR s.project = p_project) AND s.status = 'completed'; END; $$ LANGUAGE plpgsql; COMMENT ON FUNCTION session_documentation_stats IS 'Generate statistics about session documentation usage';