Skip to content

Commit a9ceef8

Browse files
feat: add source health trends
1 parent 79abd97 commit a9ceef8

6 files changed

Lines changed: 135 additions & 28 deletions

File tree

PROJECT_STATUS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ 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 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, and visible adapter run status.
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 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.
2727
- 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.
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`: extend source-health from the current latest-run Home panel into trend views and adapter-specific drilldowns; Claude, Codex, and Cursor production session rows now use the normalized raw parser adapter contract, production index passes persist adapter run metadata/parse warnings, and Home shows latest source-health status.
33+
1. Continue the AI Session Intelligence PRD in `docs/PRD-AI-SESSION-INTELLIGENCE.md`: normalize full message/tool-call archives into the SQLite evidence 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, and Home shows latest source-health status with per-adapter trends and recent-run drilldowns.
3434
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.
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.

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.42",
3+
"version": "1.1.43",
44
"private": true,
55
"scripts": {
66
"dev": "lsof -ti:1420 | xargs kill -9 2>/dev/null; vite",

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.42",
5+
"version": "1.1.43",
66
"build": {
77
"beforeDevCommand": "npm run dev",
88
"beforeBuildCommand": "npm run build",

apps/desktop/src/pages/Home.tsx

Lines changed: 128 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -971,12 +971,12 @@ function scoreTone(score: number): string {
971971
return "text-red-300";
972972
}
973973

974-
const ROADMAP_RELEASE_VERSION = "1.1.42";
974+
const ROADMAP_RELEASE_VERSION = "1.1.43";
975975

976976
const ROADMAP_RELEASE_ITEMS = [
977977
{
978978
label: "AI session adapters",
979-
detail: "Home now shows production source-health runs and parse warnings.",
979+
detail: "Home now shows source-health trends and recent-run drilldowns.",
980980
href: "/review",
981981
},
982982
{
@@ -1223,24 +1223,46 @@ function SessionScorecardPanel({ scorecard }: { scorecard: SessionScorecard | nu
12231223
);
12241224
}
12251225

1226-
function latestRunsByAdapter(runs: SessionAdapterRun[]): SessionAdapterRun[] {
1227-
const byAdapter = new Map<string, SessionAdapterRun>();
1226+
function formatSignedDelta(value: number): string {
1227+
if (value > 0) return `+${formatTokens(value)}`;
1228+
if (value < 0) return `-${formatTokens(Math.abs(value))}`;
1229+
return "0";
1230+
}
1231+
1232+
function adapterRunTimestamp(run: SessionAdapterRun): string {
1233+
return run.last_indexed_at ?? run.created_at;
1234+
}
1235+
1236+
function adapterRunHistories(runs: SessionAdapterRun[]): Array<{
1237+
adapterId: string;
1238+
latest: SessionAdapterRun;
1239+
history: SessionAdapterRun[];
1240+
}> {
1241+
const byAdapter = new Map<string, SessionAdapterRun[]>();
12281242
for (const run of runs) {
1229-
const current = byAdapter.get(run.adapter_id);
1230-
if (!current || run.created_at > current.created_at) {
1231-
byAdapter.set(run.adapter_id, run);
1232-
}
1243+
byAdapter.set(run.adapter_id, [...(byAdapter.get(run.adapter_id) ?? []), run]);
12331244
}
1234-
return [...byAdapter.values()].sort((a, b) => a.adapter_id.localeCompare(b.adapter_id));
1245+
return [...byAdapter.entries()]
1246+
.flatMap(([adapterId, history]) => {
1247+
const sorted = [...history].sort((a, b) =>
1248+
adapterRunTimestamp(b).localeCompare(adapterRunTimestamp(a)),
1249+
);
1250+
const latest = sorted[0];
1251+
if (!latest) return [];
1252+
return [{ adapterId, latest, history: sorted }];
1253+
})
1254+
.sort((a, b) => a.adapterId.localeCompare(b.adapterId));
12351255
}
12361256

12371257
function AdapterSourceHealthPanel({ runs }: { runs: SessionAdapterRun[] }) {
1238-
const latestRuns = latestRunsByAdapter(runs);
1239-
if (latestRuns.length === 0) return null;
1258+
const histories = adapterRunHistories(runs);
1259+
if (histories.length === 0) return null;
12401260

1261+
const latestRuns = histories.map((entry) => entry.latest);
12411262
const totalWarnings = latestRuns.reduce((sum, run) => sum + run.parse_warnings.length, 0);
12421263
const totalSessions = latestRuns.reduce((sum, run) => sum + run.sessions_indexed, 0);
12431264
const totalMessages = latestRuns.reduce((sum, run) => sum + run.messages_indexed, 0);
1265+
const trackedRuns = histories.reduce((sum, entry) => sum + entry.history.length, 0);
12441266

12451267
return (
12461268
<div className="cv-panel overflow-hidden">
@@ -1264,6 +1286,9 @@ function AdapterSourceHealthPanel({ runs }: { runs: SessionAdapterRun[] }) {
12641286
<div className="text-[10px] text-slate-600">messages</div>
12651287
</div>
12661288
</div>
1289+
<div className="mt-2 text-[10px] text-slate-600">
1290+
{trackedRuns} recent run{trackedRuns === 1 ? "" : "s"} tracked for trend checks
1291+
</div>
12671292
{totalWarnings > 0 && (
12681293
<div className="mt-2 text-[10px] text-amber-300/80">
12691294
{totalWarnings} parse warning{totalWarnings === 1 ? "" : "s"}
@@ -1272,18 +1297,34 @@ function AdapterSourceHealthPanel({ runs }: { runs: SessionAdapterRun[] }) {
12721297
</div>
12731298

12741299
<div className="grid gap-px bg-[#151515] md:grid-cols-3">
1275-
{latestRuns.map((run) => {
1276-
const firstWarning = run.parse_warnings[0];
1277-
const samplePath = run.sample_source_paths[0] ?? run.source_roots[0] ?? "";
1300+
{histories.map(({ adapterId, latest, history }) => {
1301+
const previous = history[1];
1302+
const firstWarning = latest.parse_warnings[0];
1303+
const samplePath = latest.sample_source_paths[0] ?? latest.source_roots[0] ?? "";
1304+
const recentRuns = history.slice(0, 4);
1305+
const maxMessages = Math.max(1, ...recentRuns.map((run) => run.messages_indexed));
1306+
const warningDelta = previous
1307+
? latest.parse_warnings.length - previous.parse_warnings.length
1308+
: latest.parse_warnings.length;
1309+
const sessionsDelta = previous
1310+
? latest.sessions_indexed - previous.sessions_indexed
1311+
: latest.sessions_indexed;
1312+
const messagesDelta = previous
1313+
? latest.messages_indexed - previous.messages_indexed
1314+
: latest.messages_indexed;
1315+
let healthLabel = "ok";
1316+
if (firstWarning) {
1317+
healthLabel = warningDelta > 0 ? "watch" : "warn";
1318+
}
12781319
return (
1279-
<div key={run.id} className="min-w-0 bg-[#08090a] px-3 py-3">
1320+
<div key={latest.id} className="min-w-0 bg-[#08090a] px-3 py-3">
12801321
<div className="flex items-start justify-between gap-2">
12811322
<div className="min-w-0">
12821323
<div className="truncate text-sm font-medium text-slate-200">
1283-
{run.adapter_id}
1324+
{adapterId}
12841325
</div>
12851326
<div className="mt-0.5 truncate text-[10px] text-slate-600">
1286-
{formatShortDateTime(run.last_indexed_at ?? run.created_at)}
1327+
{formatShortDateTime(adapterRunTimestamp(latest))}
12871328
</div>
12881329
</div>
12891330
<Badge
@@ -1294,13 +1335,40 @@ function AdapterSourceHealthPanel({ runs }: { runs: SessionAdapterRun[] }) {
12941335
: "border-emerald-500/25 text-emerald-300/80"
12951336
}`}
12961337
>
1297-
{firstWarning ? "watch" : "ok"}
1338+
{healthLabel}
12981339
</Badge>
12991340
</div>
13001341
<div className="mt-3 flex flex-wrap gap-1.5 text-[10px] text-slate-500">
1301-
<span>{run.sessions_indexed} sessions</span>
1302-
<span>{formatTokens(run.messages_indexed)} messages</span>
1303-
<span>{run.supports_incremental ? "incremental" : "full scan"}</span>
1342+
<span>{latest.sessions_indexed} sessions</span>
1343+
<span>{formatTokens(latest.messages_indexed)} messages</span>
1344+
<span>{latest.supports_incremental ? "incremental" : "full scan"}</span>
1345+
</div>
1346+
<div className="mt-2 flex flex-wrap gap-1.5 text-[10px] text-slate-600">
1347+
<span title="Latest run compared with the previous adapter run">
1348+
{formatSignedDelta(sessionsDelta)} sessions
1349+
</span>
1350+
<span>{formatSignedDelta(messagesDelta)} messages</span>
1351+
<span
1352+
className={warningDelta > 0 ? "text-amber-300/80" : "text-emerald-300/70"}
1353+
>
1354+
{formatSignedDelta(warningDelta)} warnings
1355+
</span>
1356+
</div>
1357+
<div className="mt-3 flex h-10 items-end gap-1" aria-label={`${adapterId} recent runs`}>
1358+
{recentRuns.map((run) => {
1359+
const height = 10 + Math.round((run.messages_indexed / maxMessages) * 30);
1360+
const hasWarnings = run.parse_warnings.length > 0;
1361+
return (
1362+
<div
1363+
key={run.id}
1364+
className={`min-w-0 flex-1 rounded-sm ${
1365+
hasWarnings ? "bg-amber-300/45" : "bg-emerald-300/45"
1366+
}`}
1367+
style={{ height }}
1368+
title={`${formatShortDateTime(adapterRunTimestamp(run))}: ${run.sessions_indexed} sessions, ${formatTokens(run.messages_indexed)} messages, ${run.parse_warnings.length} warnings`}
1369+
/>
1370+
);
1371+
})}
13041372
</div>
13051373
{samplePath && (
13061374
<div className="mt-2 truncate font-mono text-[10px] text-slate-600" title={samplePath}>
@@ -1312,6 +1380,45 @@ function AdapterSourceHealthPanel({ runs }: { runs: SessionAdapterRun[] }) {
13121380
{firstWarning}
13131381
</div>
13141382
)}
1383+
<details className="mt-2 border-t border-[#171717] pt-2">
1384+
<summary className="cursor-pointer list-none text-[10px] uppercase text-slate-500 hover:text-slate-300">
1385+
recent runs
1386+
</summary>
1387+
<div className="mt-2 space-y-1.5">
1388+
{history.slice(0, 3).map((run) => {
1389+
const detailPath = run.sample_source_paths[0] ?? run.source_roots[0] ?? "";
1390+
return (
1391+
<div
1392+
key={run.id}
1393+
className="min-w-0 rounded border border-[#171717] bg-[#050505] px-2 py-1.5"
1394+
>
1395+
<div className="flex items-center justify-between gap-2 text-[10px]">
1396+
<span className="truncate text-slate-400">
1397+
{formatShortDateTime(adapterRunTimestamp(run))}
1398+
</span>
1399+
<span className="shrink-0 text-slate-600">
1400+
{run.parse_warnings.length} warn
1401+
</span>
1402+
</div>
1403+
<div className="mt-1 flex flex-wrap gap-1.5 text-[10px] text-slate-600">
1404+
<span>{run.sessions_indexed} sessions</span>
1405+
<span>{formatTokens(run.messages_indexed)} messages</span>
1406+
{run.sample_session_ids[0] && (
1407+
<span className="max-w-full truncate font-mono">
1408+
{run.sample_session_ids[0]}
1409+
</span>
1410+
)}
1411+
</div>
1412+
{detailPath && (
1413+
<div className="mt-1 truncate font-mono text-[9px] text-slate-700" title={detailPath}>
1414+
{detailPath}
1415+
</div>
1416+
)}
1417+
</div>
1418+
);
1419+
})}
1420+
</div>
1421+
</details>
13151422
</div>
13161423
);
13171424
})}

docs/PRD-AI-SESSION-INTELLIGENCE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,14 +228,14 @@ Create a small adapter interface for local agent session sources.
228228
Acceptance:
229229

230230
- Claude, Codex, and Cursor indexed sessions are normalized into a shared adapter summary contract in the scorecard API. Implemented for `SessionSourceAdapterSummary`.
231-
- Adapter output feeds one local SQLite evidence archive. Implemented for production and scorecard adapter run metadata through `session_adapter_runs` and Claude/Codex/Cursor session upserts through the raw adapter contract, while full message/tool-call archive normalization is still pending.
231+
- Adapter output feeds one local SQLite evidence archive. Implemented for production and scorecard adapter run metadata through `session_adapter_runs`, Home source-health trend/drilldown views over those persisted runs, and Claude/Codex/Cursor session upserts through the raw adapter contract, while full message/tool-call archive normalization is still pending.
232232
- Adapter output includes source paths, stable IDs, agent name, timestamps, message totals, evidence archive name, incremental support, and parse warnings. Implemented for scorecard adapter summaries, production adapter run rows, the shared raw parser adapter contract, and Claude/Codex/Cursor production DB writes; project/cwd hints, tool calls, and command events remain on existing session/evidence records until full message archive normalization lands.
233233
- Tests cover at least one fixture per adapter. Implemented for raw Claude Code JSONL, Codex JSONL, and Cursor composer/bubble JSON fixtures.
234234
- Unsupported or malformed sessions degrade to parse warnings, not crashes. Implemented for missing transcript paths, zero-message rows in adapter summary tests, and malformed raw adapter input.
235235

236236
Remaining:
237237

238-
- Extend source-health into trend views and adapter-specific drilldowns. Production index passes now persist adapter roots, sample paths/session IDs, counts, incremental support, and bounded parse warnings into `session_adapter_runs`, and Home shows the latest source-health status per adapter.
238+
- Normalize full adapter message/tool-call archives into the local SQLite evidence archive. Production index passes already persist adapter roots, sample paths/session IDs, counts, incremental support, and bounded parse warnings into `session_adapter_runs`, and Home shows latest source-health status with per-adapter trends and recent-run drilldowns.
239239

240240
### Phase 2: Usage And Stats Contracts
241241

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)