Skip to content

Commit df534d9

Browse files
fix: backfill session archives and split roadmap home
1 parent cd437a3 commit df534d9

13 files changed

Lines changed: 309 additions & 90 deletions

File tree

PROJECT_STATUS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ CodeVetter is a local-first desktop workbench for checking agent-generated code.
2323
- Review Memory Graph has a repo-level and review-scoped first slice: Repo Unpacked now persists a deterministic local `repo_graph` artifact with package scripts, routes, Tauri commands, DB tables, tests, and decision markers; exports local graph JSON plus agent-context markdown sidecars for optional Graphify/Hunk-style interop without adding either dependency; imports graph JSON only through an explicit Repo Unpacked file action, validates CodeVetter or loose graph-shaped JSON, and renders imported graphs as non-mutating previews; CLI review results also carry a bounded local graph over changed files, evidence candidates, procedure gates, blast/history context, the review prompt includes that graph neighborhood, Review shows a compact graph panel plus selected-finding graph focus, copied reviewer proof includes both full graph and focused finding graph nodes/edges, and Review can copy a selected finding as a Hunk-style agent-context note with file/line, evidence status, local history, focused graph, and next verification actions.
2424
- Agent Verification Timeline has a first normalized spine: Review now builds a shared task/review/QA/evidence/claim-check/fix/worktree timeline contract, attaches bounded raw-session command anchors to evidence rows with transcript excerpts, adds a dedicated Claim check row for failed/stale command claims, explicit extracted agent claims, positive test/check claims contradicted by failed/stale command evidence, unchecked findings, unresolved post-fix QA, and fixes without same-flow reruns, attaches edit-origin anchors for fix changed files to worktree rows, renders timeline stages and anchors in the sidebar, exposes first-class jump targets for findings, files, QA artifacts, fix worktrees, command source anchors, and edited files, shows same-flow post-fix QA before/after deltas with artifact anchors on the QA row, can copy segment-scoped fix packets directly from Review/Evidence/QA/Fix/Worktree timeline rows, includes clicked-row timeline replay metadata and transcript snippets in those packets, and includes the same timeline plus source/event/artifact/jump/edit/transcript anchors in copied reviewer proof.
2525
- Codebase History Explainer has a first file-level slice: Review now builds bounded, cited "why this code exists" explanations from local commits, decision markers, recurring findings, agent notes, and command anchors, renders them in the sidebar, and includes them in copied reviewer proof.
26-
- AI Session Intelligence has a first local scorecard slice: indexed sessions produce a schema-versioned six-dimension scorecard with cited evidence refs, anti-gaming notes, recommendations, normalized Claude/Codex/Cursor adapter coverage summaries, production and scorecard adapter run metadata/parse warnings in `session_adapter_runs`, a compact `session_message_archive` for normalized adapter messages/tool calls, a shared raw parser adapter contract with Claude/Codex/Cursor fixtures, Claude/Codex/Cursor production indexing wired through that contract, a Tauri IPC contract, compact Home dashboard panels for scorecard and source health, visible adapter run status, per-adapter run trends, and recent-run drilldowns.
27-
- Home now has a persistent Verification Workbench launcher plus a visible latest-roadmap-build banner, so Evidence Search, Agent Timeline, Synthetic QA, Memory Graph, History Brief, AI Sessions, transcript replay, claim checks, replay packets, timeline fix packets, edit origins, and post-fix QA deltas are visible immediately after launch instead of only appearing after a Review or Repo Unpacked run.
26+
- AI Session Intelligence has a first local scorecard slice: indexed sessions produce a schema-versioned six-dimension scorecard with cited evidence refs, anti-gaming notes, recommendations, normalized Claude/Codex/Cursor adapter coverage summaries, production and scorecard adapter run metadata/parse warnings in `session_adapter_runs`, a compact `session_message_archive` for normalized adapter messages/tool calls, local backfill for previously indexed Claude/Codex sessions missing archive rows, a shared raw parser adapter contract with Claude/Codex/Cursor fixtures, Claude/Codex/Cursor production indexing wired through that contract, a Tauri IPC contract, compact Roadmap dashboard panels for scorecard and source health, visible adapter run status, per-adapter run trends, and recent-run drilldowns.
27+
- Home now opens on usage first. The Verification Workbench launcher and latest-roadmap-build banner live on the Roadmap page, so Evidence Search, Agent Timeline, Synthetic QA, Memory Graph, History Brief, AI Sessions, transcript replay, claim checks, replay packets, timeline fix packets, edit origins, and post-fix QA deltas remain visible without pushing usage down on app launch.
2828
- OSS repo-analysis engines were evaluated in `docs/oss-integration-evaluation.md`; optional `ast-grep` changed-file evidence is implemented behind PATH detection with no required runtime dependency.
2929
- Product direction has been consolidated around agent-written code verification, evidence levels, timelines, and explainable codebase history.
3030

3131
## Planned Next
3232

33-
1. Continue the AI Session Intelligence PRD in `docs/PRD-AI-SESSION-INTELLIGENCE.md`: add full-text search and live update events over the normalized session message archive; Claude, Codex, and Cursor production session rows now use the normalized raw parser adapter contract, production index passes persist adapter run metadata/parse warnings plus compact message/tool-call archive rows, and Home shows latest source-health status with per-adapter trends and recent-run drilldowns.
34-
2. Continue the Agent Verification Timeline PRD in `docs/PRD-AGENT-VERIFICATION-TIMELINE.md`: add richer claim-vs-evidence discrepancy parsing beyond literal positive test/check claim contradictions, command/QA/evidence-count signals, and fuller multi-turn transcript replay; raw session command anchors with bounded transcript excerpts, explicit agent-claim anchors, dedicated claim-check rows, edit-origin anchors, timeline-specific jump targets, timeline-segment replay packets, same-flow post-fix QA deltas, and the Home latest-roadmap-build banner are now attached to visible Review/Home actions and proof export.
33+
1. Continue the AI Session Intelligence PRD in `docs/PRD-AI-SESSION-INTELLIGENCE.md`: add full-text search and live update events over the normalized session message archive; Claude, Codex, and Cursor production session rows now use the normalized raw parser adapter contract, production index passes persist adapter run metadata/parse warnings plus compact message/tool-call archive rows, local backfill repairs older Claude/Codex sessions with missing archive rows, and Roadmap shows latest source-health status with per-adapter trends and recent-run drilldowns.
34+
2. Continue the Agent Verification Timeline PRD in `docs/PRD-AGENT-VERIFICATION-TIMELINE.md`: add richer claim-vs-evidence discrepancy parsing beyond literal positive test/check claim contradictions, command/QA/evidence-count signals, and fuller multi-turn transcript replay; raw session command anchors with bounded transcript excerpts, explicit agent-claim anchors, dedicated claim-check rows, edit-origin anchors, timeline-specific jump targets, timeline-segment replay packets, same-flow post-fix QA deltas, and the Roadmap latest-build banner are now attached to visible Review/Roadmap actions and proof export.
3535
3. Continue the Codebase History Explainer PRD in `docs/PRD-CODEBASE-HISTORY-EXPLAINER.md`: turn the persisted `history_brief` slice into a queryable local history graph; Repo Unpacked history brief integration and agent-context sidecar export are now implemented.
3636
4. Curate 20-30 real public agent-generated PR benchmark cases with hand-labeled ground truth before making external catch-rate claims.
3737
5. Add benchmark fields for unverified-fix count and time/cost impact once review artifacts capture those values consistently.

apps/desktop/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@code-reviewer/desktop",
3-
"version": "1.1.44",
3+
"version": "1.1.45",
44
"private": true,
55
"scripts": {
66
"dev": "lsof -ti:1420 | xargs kill -9 2>/dev/null; vite",

apps/desktop/src-tauri/src/commands/history.rs

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,21 @@ fn full_index_impl(conn: &rusqlite::Connection) -> Result<(u64, u64, u64), Strin
427427
indexed_messages += codex_messages;
428428

429429
// ── Phase 3: Scan Cursor AI sessions ─────────────────────
430-
let (cursor_indexed, cursor_messages, cursor_skipped) = index_cursor_sessions(&conn)?;
431-
indexed_sessions += cursor_indexed;
432-
indexed_messages += cursor_messages;
433-
skipped_sessions += cursor_skipped;
430+
match index_cursor_sessions(&conn) {
431+
Ok((cursor_indexed, cursor_messages, cursor_skipped)) => {
432+
indexed_sessions += cursor_indexed;
433+
indexed_messages += cursor_messages;
434+
skipped_sessions += cursor_skipped;
435+
}
436+
Err(error) => {
437+
log::warn!("Cursor session index failed; continuing with archive backfill: {error}");
438+
}
439+
}
440+
441+
let backfilled_archives = backfill_missing_session_archives(&conn)?;
442+
if backfilled_archives > 0 {
443+
log::info!("Backfilled normalized session archive for {backfilled_archives} sessions");
444+
}
434445

435446
Ok((indexed_sessions, indexed_messages, skipped_sessions))
436447
}
@@ -732,13 +743,38 @@ fn upsert_adapter_summary_session(
732743
let _ = queries::bump_session_day(conn, &sid, day, *n);
733744
}
734745

746+
replace_archive_messages(
747+
conn,
748+
&sid,
749+
&adapter_id,
750+
&agent_type,
751+
&source_ref,
752+
archive_messages,
753+
)?;
754+
755+
Ok(IndexedAdapterSession {
756+
session_id: sid,
757+
source_ref,
758+
messages_indexed: message_count,
759+
parse_warnings,
760+
})
761+
}
762+
763+
fn replace_archive_messages(
764+
conn: &rusqlite::Connection,
765+
session_id: &str,
766+
adapter_id: &str,
767+
agent_type: &str,
768+
source_ref: &str,
769+
archive_messages: Vec<crate::commands::session_adapters::RawSessionArchiveMessage>,
770+
) -> Result<(), String> {
735771
let archive_inputs: Vec<_> = archive_messages
736772
.into_iter()
737773
.enumerate()
738774
.map(|(idx, message)| queries::SessionMessageArchiveInput {
739-
adapter_id: adapter_id.clone(),
740-
agent_type: agent_type.clone(),
741-
source_ref: source_ref.clone(),
775+
adapter_id: adapter_id.to_string(),
776+
agent_type: agent_type.to_string(),
777+
source_ref: source_ref.to_string(),
742778
source_line: message.source_line,
743779
message_index: idx as i64,
744780
role: message.role,
@@ -750,15 +786,8 @@ fn upsert_adapter_summary_session(
750786
raw_type: message.raw_type,
751787
})
752788
.collect();
753-
queries::replace_session_message_archive(conn, &sid, &archive_inputs)
754-
.map_err(|e| e.to_string())?;
755-
756-
Ok(IndexedAdapterSession {
757-
session_id: sid,
758-
source_ref,
759-
messages_indexed: message_count,
760-
parse_warnings,
761-
})
789+
queries::replace_session_message_archive(conn, session_id, &archive_inputs)
790+
.map_err(|e| e.to_string())
762791
}
763792

764793
// ─────────────────────────────────────────────────────────────────
@@ -840,6 +869,49 @@ fn parse_codex_session(
840869
)
841870
}
842871

872+
fn backfill_missing_session_archives(conn: &rusqlite::Connection) -> Result<u64, String> {
873+
let candidates =
874+
queries::list_sessions_needing_archive_backfill(conn, 5_000).map_err(|e| e.to_string())?;
875+
let mut backfilled = 0u64;
876+
877+
for candidate in candidates {
878+
let path = std::path::Path::new(&candidate.jsonl_path);
879+
if !path.exists() {
880+
continue;
881+
}
882+
let raw = match std::fs::read_to_string(path) {
883+
Ok(raw) => raw,
884+
Err(error) => {
885+
log::warn!(
886+
"Archive backfill could not read {}: {}",
887+
candidate.jsonl_path,
888+
error
889+
);
890+
continue;
891+
}
892+
};
893+
let summary = match candidate.agent_type.as_str() {
894+
"claude-code" => ClaudeCodeAdapter.parse_raw(&candidate.jsonl_path, &raw),
895+
"codex" => CodexAdapter.parse_raw(&candidate.jsonl_path, &raw),
896+
_ => continue,
897+
};
898+
if summary.archive_messages.is_empty() {
899+
continue;
900+
}
901+
replace_archive_messages(
902+
conn,
903+
&candidate.id,
904+
&summary.adapter_id,
905+
&summary.agent_type,
906+
&summary.source_ref,
907+
summary.archive_messages,
908+
)?;
909+
backfilled += 1;
910+
}
911+
912+
Ok(backfilled)
913+
}
914+
843915
// ─────────────────────────────────────────────────────────────────
844916
// Cursor AI session detection & indexing
845917
// ─────────────────────────────────────────────────────────────────
@@ -1376,6 +1448,58 @@ mod tests {
13761448
assert_eq!(archived[1].raw_type.as_deref(), Some("response_item"));
13771449
}
13781450

1451+
#[test]
1452+
fn archive_backfill_repairs_existing_codex_session_rows() {
1453+
let conn = memory_conn_with_project();
1454+
let fixture = std::path::Path::new(concat!(
1455+
env!("CARGO_MANIFEST_DIR"),
1456+
"/tests/fixtures/session_adapters/codex.jsonl"
1457+
));
1458+
1459+
queries::upsert_session(
1460+
&conn,
1461+
&queries::SessionInput {
1462+
id: "codex-session-1".to_string(),
1463+
project_id: "project".to_string(),
1464+
agent_type: Some("codex".to_string()),
1465+
jsonl_path: Some(fixture.to_string_lossy().to_string()),
1466+
git_branch: None,
1467+
cwd: Some("/repo/codevetter".to_string()),
1468+
cli_version: None,
1469+
first_message: None,
1470+
last_message: Some("2026-06-12T16:01:00Z".to_string()),
1471+
message_count: Some(2),
1472+
total_input_tokens: Some(500),
1473+
total_output_tokens: Some(150),
1474+
model_used: Some("o3".to_string()),
1475+
slug: None,
1476+
file_size_bytes: Some(123),
1477+
indexed_at: Some("2026-06-12T16:03:00Z".to_string()),
1478+
file_mtime: Some("2026-06-12T16:03:00Z".to_string()),
1479+
cache_read_tokens: Some(100),
1480+
cache_creation_tokens: Some(0),
1481+
compaction_count: Some(0),
1482+
estimated_cost_usd: Some(0.0),
1483+
},
1484+
)
1485+
.expect("existing codex session");
1486+
1487+
let candidates =
1488+
queries::list_sessions_needing_archive_backfill(&conn, 10).expect("candidates");
1489+
assert_eq!(candidates.len(), 1);
1490+
assert_eq!(candidates[0].id, "codex-session-1");
1491+
1492+
let backfilled = backfill_missing_session_archives(&conn).expect("backfill");
1493+
assert_eq!(backfilled, 1);
1494+
1495+
let archived =
1496+
queries::list_session_message_archive(&conn, "codex-session-1", 10).expect("archive");
1497+
assert_eq!(archived.len(), 2);
1498+
assert_eq!(archived[0].adapter_id, "codex");
1499+
assert_eq!(archived[0].role.as_deref(), Some("user"));
1500+
assert_eq!(archived[1].role.as_deref(), Some("assistant"));
1501+
}
1502+
13791503
#[test]
13801504
fn cursor_adapter_summary_upserts_session_and_day_bucket() {
13811505
let conn = memory_conn_with_project();

apps/desktop/src-tauri/src/commands/session_adapters.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ fn bounded_text(raw: impl Into<String>) -> Option<String> {
118118
}
119119
const MAX_ARCHIVE_TEXT: usize = 12_000;
120120
if value.len() > MAX_ARCHIVE_TEXT {
121-
value.truncate(MAX_ARCHIVE_TEXT);
121+
let truncate_at = value
122+
.char_indices()
123+
.map(|(idx, _)| idx)
124+
.take_while(|idx| *idx <= MAX_ARCHIVE_TEXT)
125+
.last()
126+
.unwrap_or(0);
127+
value.truncate(truncate_at);
122128
value.push_str("\n[truncated]");
123129
}
124130
Some(value)
@@ -765,4 +771,13 @@ mod tests {
765771
.iter()
766772
.any(|warning| warning.contains("missing session_meta id")));
767773
}
774+
775+
#[test]
776+
fn archive_text_truncation_handles_unicode_boundaries() {
777+
let raw = "न".repeat(12_001);
778+
let text = bounded_text(raw).expect("bounded unicode text");
779+
780+
assert!(text.ends_with("\n[truncated]"));
781+
assert!(text.is_char_boundary(text.len()));
782+
}
768783
}

apps/desktop/src-tauri/src/db/queries.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,13 @@ pub struct SessionMeta {
331331
pub archived_message_count: i64,
332332
}
333333

334+
#[derive(Debug, Clone)]
335+
pub struct SessionArchiveBackfillCandidate {
336+
pub id: String,
337+
pub agent_type: String,
338+
pub jsonl_path: String,
339+
}
340+
334341
/// Look up the stored session metadata for a given `jsonl_path`.
335342
/// Returns `None` if the file has never been indexed.
336343
pub fn get_session_by_jsonl_path(
@@ -727,6 +734,35 @@ pub fn list_session_message_archive(
727734
rows.collect()
728735
}
729736

737+
pub fn list_sessions_needing_archive_backfill(
738+
conn: &Connection,
739+
limit: i64,
740+
) -> Result<Vec<SessionArchiveBackfillCandidate>, rusqlite::Error> {
741+
let limit = limit.clamp(1, 5_000);
742+
let mut stmt = conn.prepare(
743+
"SELECT s.id, s.agent_type, s.jsonl_path
744+
FROM cc_sessions s
745+
WHERE s.jsonl_path IS NOT NULL
746+
AND s.message_count > 0
747+
AND s.agent_type IN ('claude-code', 'codex')
748+
AND (
749+
SELECT COUNT(*)
750+
FROM session_message_archive a
751+
WHERE a.session_id = s.id
752+
) < s.message_count
753+
ORDER BY datetime(s.last_message) DESC NULLS LAST
754+
LIMIT ?1",
755+
)?;
756+
let rows = stmt.query_map(params![limit], |row| {
757+
Ok(SessionArchiveBackfillCandidate {
758+
id: row.get(0)?,
759+
agent_type: row.get(1)?,
760+
jsonl_path: row.get(2)?,
761+
})
762+
})?;
763+
rows.collect()
764+
}
765+
730766
// ─────────────────────────────────────────────────────────────────
731767
// Local Reviews
732768
// ─────────────────────────────────────────────────────────────────

apps/desktop/src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-utils/schema.json",
33
"identifier": "com.codevetter.desktop",
44
"productName": "CodeVetter",
5-
"version": "1.1.44",
5+
"version": "1.1.45",
66
"build": {
77
"beforeDevCommand": "npm run dev",
88
"beforeBuildCommand": "npm run build",

apps/desktop/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import IntentDebugger from "@/pages/IntentDebugger";
1515
import QaReplay from "@/pages/QaReplay";
1616
import QuickReview from "@/pages/QuickReview";
1717
import RepoUnpacked from "@/pages/RepoUnpacked";
18+
import Roadmap from "@/pages/Roadmap";
1819
import Rubrics from "@/pages/Rubrics";
1920
import Settings from "@/pages/Settings";
2021

@@ -168,6 +169,7 @@ export default function App() {
168169
<Route element={<Shell />}>
169170
<Route path="/" element={<Home />} />
170171
<Route path="/review" element={<QuickReview />} />
172+
<Route path="/roadmap" element={<Roadmap />} />
171173
<Route path="/rubrics" element={<Rubrics />} />
172174
<Route path="/unpack" element={<RepoUnpacked />} />
173175
<Route path="/intent-debugger" element={<IntentDebugger />} />

apps/desktop/src/components/command-palette.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export default function CommandPalette({ isOpen, onClose }: CommandPaletteProps)
7777
// Navigation
7878
{ id: "nav-home", label: "Go to Home", icon: "\u2302", shortcut: "g h", group: "Navigation", action: go("/") },
7979
{ id: "nav-review", label: "Go to Review", icon: "\u2714", shortcut: "g r", group: "Navigation", action: go("/review") },
80+
{ id: "nav-roadmap", label: "Go to Roadmap", icon: "\u25ce", shortcut: "g m", group: "Navigation", action: go("/roadmap") },
8081
{ id: "nav-settings", label: "Go to Settings", icon: "\u2638", shortcut: "g ,", group: "Navigation", action: go("/settings") },
8182

8283
// Actions

0 commit comments

Comments
 (0)