remem-ai 0.6.62

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use axum::{
    extract::{Path, Query, State},
    response::Response,
};
use rusqlite::{params, Connection, OptionalExtension};
use serde::Serialize;

use super::super::cursor::CursorKind;
use super::super::read_resources::{
    detail_resource, list_resource, redact_bounded, redact_optional, PolicyRelation,
    ReadResourceParams, ReadResourceSpec, ResourceProjectionPolicy, SafeResourceRef,
};
use super::super::types::DbState;

pub(in crate::api) async fn handle_list_workstreams(
    State(_state): State<DbState>,
    Query(params): Query<ReadResourceParams>,
) -> Response {
    list_resource::<Workstreams>(params)
}

pub(in crate::api) async fn handle_workstream_detail(
    State(_state): State<DbState>,
    Path(id): Path<String>,
) -> Response {
    detail_resource::<Workstreams>(id)
}

struct Workstreams;

struct WorkstreamRow {
    id: i64,
    project: String,
    title: String,
    description: Option<String>,
    status: String,
    progress: Option<String>,
    next_action: Option<String>,
    blockers: Option<String>,
    topic_domain: Option<String>,
    created_at_epoch: i64,
    updated_at_epoch: i64,
    completed_at_epoch: Option<i64>,
}

#[derive(Serialize)]
struct WorkstreamItem {
    id: i64,
    project: String,
    title: String,
    description: Option<String>,
    status: String,
    progress: Option<String>,
    next_action: Option<String>,
    blockers: Option<String>,
    created_at_epoch: i64,
    updated_at_epoch: i64,
    completed_at_epoch: Option<i64>,
    references: Vec<SafeResourceRef>,
}

const SELECT_WORKSTREAM: &str =
    "SELECT w.id, w.project, w.title, w.description, w.status, w.progress,
            w.next_action, w.blockers, w.topic_domain, w.created_at_epoch,
            w.updated_at_epoch, w.completed_at_epoch
     FROM workstreams w";

impl ReadResourceSpec for Workstreams {
    type Row = WorkstreamRow;
    type Item = WorkstreamItem;

    const KIND: CursorKind = CursorKind::Workstreams;
    fn row_id(row: &Self::Row) -> i64 {
        row.id
    }

    fn load_batch(
        conn: &Connection,
        resume_before_id: Option<i64>,
        project: Option<&str>,
        status: Option<&str>,
        limit: usize,
    ) -> anyhow::Result<Vec<Self::Row>> {
        let sql = format!(
            "{SELECT_WORKSTREAM}
             WHERE w.merged_into_workstream_id IS NULL
               AND (?1 IS NULL OR w.id < ?1)
               AND (?2 IS NULL OR w.project = ?2)
               AND (?3 IS NULL OR w.status = ?3)
             ORDER BY w.id DESC LIMIT ?4"
        );
        let mut stmt = conn.prepare(&sql)?;
        let rows = stmt.query_map(
            params![resume_before_id, project, status, limit as i64],
            map_row,
        )?;
        rows.collect::<Result<Vec<_>, _>>().map_err(Into::into)
    }

    fn load_one(conn: &Connection, id: i64) -> anyhow::Result<Option<Self::Row>> {
        conn.query_row(
            &format!(
                "{SELECT_WORKSTREAM}
                 WHERE w.id = ?1 AND w.merged_into_workstream_id IS NULL"
            ),
            params![id],
            map_row,
        )
        .optional()
        .map_err(Into::into)
    }

    fn project(
        row: Self::Row,
        policy: &ResourceProjectionPolicy,
    ) -> anyhow::Result<Option<Self::Item>> {
        let mut visible = vec![
            row.project.as_str(),
            row.title.as_str(),
            row.status.as_str(),
        ];
        visible.extend(row.description.as_deref());
        visible.extend(row.progress.as_deref());
        visible.extend(row.next_action.as_deref());
        visible.extend(row.blockers.as_deref());
        visible.extend(row.topic_domain.as_deref());
        let relations = row
            .topic_domain
            .as_deref()
            .map(PolicyRelation::Topic)
            .into_iter()
            .collect::<Vec<_>>();
        if policy.suppresses(&visible, &relations) {
            return Ok(None);
        }
        Ok(Some(WorkstreamItem {
            id: row.id,
            project: redact_bounded(&row.project),
            title: redact_bounded(&row.title),
            description: redact_optional(row.description),
            status: redact_bounded(&row.status),
            progress: redact_optional(row.progress),
            next_action: redact_optional(row.next_action),
            blockers: redact_optional(row.blockers),
            created_at_epoch: row.created_at_epoch,
            updated_at_epoch: row.updated_at_epoch,
            completed_at_epoch: row.completed_at_epoch,
            references: Vec::new(),
        }))
    }
}

fn map_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<WorkstreamRow> {
    Ok(WorkstreamRow {
        id: row.get(0)?,
        project: row.get(1)?,
        title: row.get(2)?,
        description: row.get(3)?,
        status: row.get(4)?,
        progress: row.get(5)?,
        next_action: row.get(6)?,
        blockers: row.get(7)?,
        topic_domain: row.get(8)?,
        created_at_epoch: row.get(9)?,
        updated_at_epoch: row.get(10)?,
        completed_at_epoch: row.get(11)?,
    })
}