fix(dashboard): wire duplicates/contradictions/patterns to real APIs, remove mock data

The /duplicates, /contradictions, and /patterns routes shipped hardcoded mock
data — and /duplicates rendered a "Live" badge over it. That is a launch
credibility risk: a visitor who spots the canned data distrusts every real
claim (including the CauseBench benchmark).

Replaces the three mocks (mockFetchDuplicates, MOCK_CONTRADICTIONS,
mockFetchCrossProject) with real dashboard HTTP endpoints backed by the existing
core capabilities:
- GET /api/duplicates            -> dedup cluster detection
- GET /api/contradictions        -> trust-weighted contradiction analysis
- GET /api/patterns/cross-project-> cross-project pattern transfer

Adds the handlers (dashboard/handlers.rs), route registrations (dashboard/mod.rs),
frontend api client methods + response types, and rewrites the three routes to
fetch live. The "Live" badge is now truthful.

Verified this session:
- cargo build -p vestige-mcp: compiles
- pnpm --filter @vestige/dashboard check: 905 files, 0 errors, 0 warnings
- live backend on a 1073-memory DB returns real rows from all three endpoints

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-07-02 15:44:46 -05:00
parent 9d15cce7ab
commit 898bd336cc
7 changed files with 1024 additions and 761 deletions

View file

@ -6,6 +6,10 @@ import type {
HealthCheck,
TimelineResponse,
GraphResponse,
DuplicatesResponse,
ContradictionsResponse,
CrossProjectPatternsResponse,
MemoryAuditResponse,
DreamResult,
ImportanceScore,
RetentionDistribution,
@ -111,6 +115,28 @@ export const api = {
retentionDistribution: () => fetcher<RetentionDistribution>('/retention-distribution'),
// Memory hygiene & provenance: duplicate clusters, contradiction pairs,
// cross-project pattern transfer, per-memory audit trail.
duplicates: (threshold = 0.8, limit = 20) =>
fetcher<DuplicatesResponse>(`/duplicates?threshold=${threshold}&limit=${limit}`),
contradictions: (params?: { topic?: string; min_trust?: number; limit?: number }) => {
const qs = params
? '?' +
new URLSearchParams(
Object.entries(params)
.filter(([, v]) => v !== undefined)
.map(([k, v]) => [k, String(v)])
).toString()
: '';
return fetcher<ContradictionsResponse>(`/contradictions${qs}`);
},
crossProjectPatterns: () => fetcher<CrossProjectPatternsResponse>('/patterns/cross-project'),
memoryAudit: (id: string, limit = 100) =>
fetcher<MemoryAuditResponse>(`/memories/${encodeURIComponent(id)}/audit?limit=${limit}`),
// Intentions
intentions: (status = 'active') =>
fetcher<{ intentions: IntentionItem[]; total: number; filter: string }>(`/intentions?status=${status}`),

View file

@ -302,6 +302,107 @@ export interface IntentionItem {
snoozed_until?: string | null;
}
// Memory Hygiene — GET /api/duplicates (cosine-similarity clusters)
export interface DuplicateClusterMemory {
id: string;
content: string;
nodeType: string;
tags: string[];
retention: number;
createdAt: string;
}
export interface DuplicateClusterGroup {
similarity: number;
suggestedAction: 'merge' | 'review';
memories: DuplicateClusterMemory[];
}
export interface DuplicatesResponse {
threshold: number;
total: number;
clusters: DuplicateClusterGroup[];
}
// Contradiction pairs — GET /api/contradictions. Field names mirror the
// Contradiction interface in $components/ContradictionArcs.svelte; a = older
// memory, b = newer. Sorted by similarity desc.
export interface ContradictionPair {
memory_a_id: string;
memory_b_id: string;
memory_a_preview: string;
memory_b_preview: string;
memory_a_type?: string;
memory_b_type?: string;
memory_a_created?: string;
memory_b_created?: string;
memory_a_tags?: string[];
memory_b_tags?: string[];
trust_a: number;
trust_b: number;
similarity: number;
date_diff_days: number;
topic: string;
}
export interface ContradictionsResponse {
memoriesAnalyzed: number;
total: number;
contradictions: ContradictionPair[];
}
// Cross-project pattern transfer — GET /api/patterns/cross-project.
// Only the six tracked categories ever cross the wire (backend drops others).
export type CrossProjectCategory =
| 'ErrorHandling'
| 'AsyncConcurrency'
| 'Testing'
| 'Architecture'
| 'Performance'
| 'Security';
export interface CrossProjectPattern {
name: string;
category: CrossProjectCategory;
origin_project: string;
transferred_to: string[];
transfer_count: number;
last_used: string;
confidence: number;
}
export interface CrossProjectPatternsResponse {
projects: string[];
patterns: CrossProjectPattern[];
}
// Per-memory audit trail — GET /api/memories/{id}/audit. Events arrive
// newest-first; the action union matches AuditAction in
// $components/audit-trail-helpers.ts.
export type MemoryAuditAction =
| 'created'
| 'accessed'
| 'promoted'
| 'demoted'
| 'edited'
| 'suppressed'
| 'dreamed'
| 'reconsolidated';
export interface MemoryAuditEvent {
action: MemoryAuditAction;
timestamp: string; // RFC3339
old_value?: number;
new_value?: number;
reason?: string;
triggered_by?: string;
}
export interface MemoryAuditResponse {
memoryId: string;
events: MemoryAuditEvent[];
}
// Node type colors for visualization — bioluminescent palette
export const NODE_TYPE_COLORS: Record<string, string> = {
fact: '#00A8FF', // electric blue

View file

@ -1,10 +1,12 @@
<script lang="ts">
import { onMount } from 'svelte';
import ContradictionArcs, { type Contradiction } from '$components/ContradictionArcs.svelte';
import PageHeader from '$components/PageHeader.svelte';
import Dropdown, { type DropdownOption } from '$components/Dropdown.svelte';
import Icon from '$components/Icon.svelte';
import AnimatedNumber from '$components/AnimatedNumber.svelte';
import { reveal } from '$lib/actions/reveal';
import { api } from '$stores/api';
import {
severityColor,
severityLabel,
@ -13,285 +15,33 @@
avgTrustDelta as avgTrustDeltaFn,
} from '$components/contradiction-helpers';
// TODO: swap for /api/contradictions when backend ships.
// Expected shape matches the `Contradiction` interface in
// $components/ContradictionArcs.svelte. Backend should derive pairs from the
// contradiction-analysis step of deep_reference (only flag when BOTH memories
// have >0.3 FSRS trust).
const MOCK_CONTRADICTIONS: Contradiction[] = [
{
memory_a_id: 'a1',
memory_b_id: 'b1',
memory_a_preview: 'Dev server runs on port 3000 (default Vite config)',
memory_b_preview: 'Dev server moved to port 3002 to avoid conflict',
memory_a_type: 'fact',
memory_b_type: 'decision',
memory_a_created: '2026-01-14',
memory_b_created: '2026-03-22',
memory_a_tags: ['dev', 'vite'],
memory_b_tags: ['dev', 'vite', 'decision'],
trust_a: 0.42,
trust_b: 0.91,
similarity: 0.88,
date_diff_days: 67,
topic: 'dev server port'
},
{
memory_a_id: 'a2',
memory_b_id: 'b2',
memory_a_preview: 'Prompt variation helps at higher sampling temperatures',
memory_b_preview: 'Prompt variation reduced accuracy in the latest benchmark run',
memory_a_type: 'concept',
memory_b_type: 'fact',
memory_a_created: '2026-03-30',
memory_b_created: '2026-04-03',
memory_a_tags: ['research', 'prompting'],
memory_b_tags: ['research', 'prompting', 'evidence'],
trust_a: 0.35,
trust_b: 0.88,
similarity: 0.92,
date_diff_days: 4,
topic: 'prompt diversity'
},
{
memory_a_id: 'a3',
memory_b_id: 'b3',
memory_a_preview: 'Use min_p=0.05 for long-form sampling',
memory_b_preview: 'min_p scheduling failed at high sampling temperatures',
memory_a_type: 'pattern',
memory_b_type: 'fact',
memory_a_created: '2026-04-01',
memory_b_created: '2026-04-05',
memory_a_tags: ['sampling'],
memory_b_tags: ['sampling'],
trust_a: 0.58,
trust_b: 0.74,
similarity: 0.81,
date_diff_days: 4,
topic: 'min_p sampling'
},
{
memory_a_id: 'a4',
memory_b_id: 'b4',
memory_a_preview: 'LoRA rank 16 is enough for domain adaptation',
memory_b_preview: 'LoRA rank 32 consistently outperforms rank 16 on math',
memory_a_type: 'concept',
memory_b_type: 'fact',
memory_a_created: '2026-02-10',
memory_b_created: '2026-04-12',
memory_a_tags: ['lora', 'training'],
memory_b_tags: ['lora', 'training', 'nemotron'],
trust_a: 0.48,
trust_b: 0.76,
similarity: 0.74,
date_diff_days: 61,
topic: 'LoRA rank'
},
{
memory_a_id: 'a5',
memory_b_id: 'b5',
memory_a_preview: 'Team prefers Rust for backend services',
memory_b_preview: 'Project chose Axum + Rust for the dashboard backend',
memory_a_type: 'note',
memory_b_type: 'decision',
memory_a_created: '2026-01-05',
memory_b_created: '2026-02-18',
memory_a_tags: ['preference', 'backend'],
memory_b_tags: ['backend', 'decision'],
trust_a: 0.81,
trust_b: 0.88,
similarity: 0.42,
date_diff_days: 44,
topic: 'backend language'
},
{
memory_a_id: 'a6',
memory_b_id: 'b6',
memory_a_preview: 'Warm-start from checkpoint saves 8h of training',
memory_b_preview: 'Warm-start code never loaded the PEFT adapter correctly',
memory_a_type: 'pattern',
memory_b_type: 'fact',
memory_a_created: '2026-03-11',
memory_b_created: '2026-04-16',
memory_a_tags: ['training', 'warm-start'],
memory_b_tags: ['training', 'warm-start', 'bug-fix'],
trust_a: 0.55,
trust_b: 0.93,
similarity: 0.79,
date_diff_days: 36,
topic: 'warm-start correctness'
},
{
memory_a_id: 'a7',
memory_b_id: 'b7',
memory_a_preview: 'Three.js force-directed graph runs fine at 5k nodes',
memory_b_preview: 'WebGL graph stutters above 2k nodes on M1 MacBook Air',
memory_a_type: 'fact',
memory_b_type: 'fact',
memory_a_created: '2025-12-02',
memory_b_created: '2026-03-29',
memory_a_tags: ['vestige', 'graph', 'perf'],
memory_b_tags: ['vestige', 'graph', 'perf'],
trust_a: 0.39,
trust_b: 0.72,
similarity: 0.67,
date_diff_days: 117,
topic: 'graph performance'
},
{
memory_a_id: 'a8',
memory_b_id: 'b8',
memory_a_preview: 'Submit benchmark runs with a 16384 token budget',
memory_b_preview: 'Latest baseline improved when token budget increased to 32768',
memory_a_type: 'pattern',
memory_b_type: 'event',
memory_a_created: '2026-04-04',
memory_b_created: '2026-04-10',
memory_a_tags: ['benchmark', 'tokens'],
memory_b_tags: ['benchmark', 'baseline'],
trust_a: 0.31,
trust_b: 0.85,
similarity: 0.73,
date_diff_days: 6,
topic: 'token budget'
},
{
memory_a_id: 'a9',
memory_b_id: 'b9',
memory_a_preview: 'FSRS-6 parameters require ~1k reviews to train',
memory_b_preview: 'FSRS-6 default parameters work fine out of the box',
memory_a_type: 'concept',
memory_b_type: 'concept',
memory_a_created: '2026-01-22',
memory_b_created: '2026-02-28',
memory_a_tags: ['fsrs', 'training'],
memory_b_tags: ['fsrs'],
trust_a: 0.62,
trust_b: 0.54,
similarity: 0.57,
date_diff_days: 37,
topic: 'FSRS parameter tuning'
},
{
memory_a_id: 'a10',
memory_b_id: 'b10',
memory_a_preview: 'Tailwind 4 requires explicit CSS import only',
memory_b_preview: 'Tailwind 4 config still supports tailwind.config.js',
memory_a_type: 'fact',
memory_b_type: 'fact',
memory_a_created: '2026-01-30',
memory_b_created: '2026-02-14',
memory_a_tags: ['tailwind', 'config'],
memory_b_tags: ['tailwind', 'config'],
trust_a: 0.47,
trust_b: 0.33,
similarity: 0.85,
date_diff_days: 15,
topic: 'Tailwind 4 config'
},
{
memory_a_id: 'a11',
memory_b_id: 'b11',
memory_a_preview: 'Dataset API silently ignores invalid source slugs',
memory_b_preview: 'Dataset API throws an error when source slug is invalid',
memory_a_type: 'fact',
memory_b_type: 'concept',
memory_a_created: '2026-04-07',
memory_b_created: '2026-02-20',
memory_a_tags: ['api', 'bug-fix'],
memory_b_tags: ['api'],
trust_a: 0.89,
trust_b: 0.28,
similarity: 0.91,
date_diff_days: 46,
topic: 'API validation'
},
{
memory_a_id: 'a12',
memory_b_id: 'b12',
memory_a_preview: 'USearch HNSW is 20x faster than FAISS for embeddings',
memory_b_preview: 'FAISS IVF is the fastest vector index at scale',
memory_a_type: 'fact',
memory_b_type: 'concept',
memory_a_created: '2026-02-01',
memory_b_created: '2025-11-15',
memory_a_tags: ['vectors', 'perf'],
memory_b_tags: ['vectors', 'perf'],
trust_a: 0.78,
trust_b: 0.36,
similarity: 0.69,
date_diff_days: 78,
topic: 'vector index perf'
},
{
memory_a_id: 'a13',
memory_b_id: 'b13',
memory_a_preview: 'Leaderboard scores weight by top-10 consistency',
memory_b_preview: 'Leaderboard uses single-best-episode scoring',
memory_a_type: 'fact',
memory_b_type: 'fact',
memory_a_created: '2026-04-18',
memory_b_created: '2026-04-10',
memory_a_tags: ['leaderboard', 'scoring'],
memory_b_tags: ['leaderboard', 'scoring'],
trust_a: 0.64,
trust_b: 0.52,
similarity: 0.82,
date_diff_days: 8,
topic: 'leaderboard scoring'
},
{
memory_a_id: 'a14',
memory_b_id: 'b14',
memory_a_preview: 'Release notes were planned for 8am ET',
memory_b_preview: 'Release notes moved to 9am ET after schedule review',
memory_a_type: 'decision',
memory_b_type: 'decision',
memory_a_created: '2026-03-01',
memory_b_created: '2026-04-15',
memory_a_tags: ['cadence', 'content'],
memory_b_tags: ['cadence', 'content'],
trust_a: 0.50,
trust_b: 0.81,
similarity: 0.58,
date_diff_days: 45,
topic: 'posting cadence'
},
{
memory_a_id: 'a15',
memory_b_id: 'b15',
memory_a_preview: 'Dream cycle consolidates ~50 memories per run',
memory_b_preview: 'Dream cycle replays closer to 120 memories in practice',
memory_a_type: 'fact',
memory_b_type: 'fact',
memory_a_created: '2026-02-15',
memory_b_created: '2026-04-08',
memory_a_tags: ['vestige', 'dream'],
memory_b_tags: ['vestige', 'dream'],
trust_a: 0.44,
trust_b: 0.79,
similarity: 0.76,
date_diff_days: 52,
topic: 'dream cycle count'
},
{
memory_a_id: 'a16',
memory_b_id: 'b16',
memory_a_preview: 'Never commit API keys to git; use .env files',
memory_b_preview: 'Environment secrets should live in a 1Password vault',
memory_a_type: 'pattern',
memory_b_type: 'pattern',
memory_a_created: '2025-10-11',
memory_b_created: '2026-03-20',
memory_a_tags: ['security', 'secrets'],
memory_b_tags: ['security', 'secrets'],
trust_a: 0.72,
trust_b: 0.64,
similarity: 0.48,
date_diff_days: 160,
topic: 'secret storage'
// Live pairs from /api/contradictions — the contradiction-analysis
// primitives behind deep_reference (only flagged when BOTH memories clear
// the trust floor). Sorted by similarity desc by the backend.
let contradictions = $state<Contradiction[]>([]);
// System-wide count from the backend, vs. the derived stats below which
// reflect only the pairs the page holds.
let totalDetected = $state(0);
let loading = $state(true);
let error = $state<string | null>(null);
async function load() {
loading = true;
error = null;
try {
const res = await api.contradictions();
contradictions = res.contradictions;
totalDetected = res.total;
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to load contradictions';
contradictions = [];
totalDetected = 0;
} finally {
loading = false;
}
];
}
onMount(() => load());
// --- Filters ---
type Filter = 'all' | 'recent' | 'high-trust' | 'topic';
@ -299,7 +49,7 @@
let topicFilter = $state<string>('');
const uniqueTopics = $derived(
Array.from(new Set(MOCK_CONTRADICTIONS.map((c) => c.topic))).sort()
Array.from(new Set(contradictions.map((c) => c.topic))).sort()
);
// --- Clear, labelled dropdown options replace the bare filter buttons +
@ -317,7 +67,7 @@
...uniqueTopics.map((t) => ({
value: t,
label: t,
badge: MOCK_CONTRADICTIONS.filter((c) => c.topic === t).length,
badge: contradictions.filter((c) => c.topic === t).length,
})),
]);
@ -332,30 +82,28 @@
const filtered = $derived.by<Contradiction[]>(() => {
switch (filter) {
case 'recent':
// Within 7 days of "now" — use date_diff as a proxy by keeping pairs
// where either memory was created within the last 7 days of our fixed
// mock "today" (2026-04-20). Simple approach: keep pairs whose newest
// created date is within 7 days of 2026-04-20.
// Within 7 days of now — keep pairs whose newest created date is
// within the last week.
{
const now = new Date('2026-04-20').getTime();
const now = Date.now();
const week = 7 * 24 * 60 * 60 * 1000;
return MOCK_CONTRADICTIONS.filter((c) => {
return contradictions.filter((c) => {
const aT = c.memory_a_created ? new Date(c.memory_a_created).getTime() : 0;
const bT = c.memory_b_created ? new Date(c.memory_b_created).getTime() : 0;
return now - Math.max(aT, bT) <= week;
});
}
case 'high-trust':
return MOCK_CONTRADICTIONS.filter(
return contradictions.filter(
(c) => Math.min(c.trust_a, c.trust_b) > 0.6
);
case 'topic':
return topicFilter
? MOCK_CONTRADICTIONS.filter((c) => c.topic === topicFilter)
: MOCK_CONTRADICTIONS;
? contradictions.filter((c) => c.topic === topicFilter)
: contradictions;
case 'all':
default:
return MOCK_CONTRADICTIONS;
return contradictions;
}
});
@ -366,18 +114,16 @@
focusedPairIndex = i;
}
// --- Stats. `TOTAL_CONTRADICTIONS_DETECTED` stays illustrative so the tile
// reads like a system-wide count once the backend ships; everything else
// is derived from the pairs the page actually holds so the numbers are
// --- Stats. `totalDetected` is the backend's system-wide count; everything
// else is derived from the pairs the page actually holds so the numbers are
// self-consistent with what the user sees. ---
const TOTAL_CONTRADICTIONS_DETECTED = 47;
const totalMemoriesInvolved = $derived(uniqueMemoryCount(MOCK_CONTRADICTIONS));
const avgTrustDelta = $derived(avgTrustDeltaFn(MOCK_CONTRADICTIONS));
const totalMemoriesInvolved = $derived(uniqueMemoryCount(contradictions));
const avgTrustDelta = $derived(avgTrustDeltaFn(contradictions));
// Map filtered index -> original index in MOCK_CONTRADICTIONS so the
// Map filtered index -> original index in `contradictions` so the
// constellation and sidebar stay in sync regardless of which filter is on.
const visibleList = $derived.by<{ orig: number; c: Contradiction }[]>(() => {
const byId = new Map(MOCK_CONTRADICTIONS.map((c, i) => [c.memory_a_id + '|' + c.memory_b_id, i]));
const byId = new Map(contradictions.map((c, i) => [c.memory_a_id + '|' + c.memory_b_id, i]));
return filtered.map((c) => ({
orig: byId.get(c.memory_a_id + '|' + c.memory_b_id) ?? 0,
c
@ -404,11 +150,48 @@
</span>
</PageHeader>
{#if error}
<div class="glass-panel flex flex-col items-center gap-3 rounded-2xl p-10 text-center">
<div class="text-sm text-decay">Couldn't load contradictions</div>
<div class="max-w-md text-xs text-muted">{error}</div>
<button
type="button"
onclick={load}
class="mt-2 rounded-lg bg-synapse/20 px-4 py-2 text-xs font-medium text-synapse-glow transition hover:bg-synapse/30 focus:outline-none focus-visible:ring-2 focus-visible:ring-synapse/60"
>
Retry
</button>
</div>
{:else if loading}
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
{#each Array(4) as _}
<div class="glass-subtle shimmer h-20 rounded-xl"></div>
{/each}
</div>
<div class="grid grid-cols-1 lg:grid-cols-[1fr_340px] gap-4">
<div class="glass-subtle shimmer min-h-[520px] rounded-2xl"></div>
<div class="glass-subtle shimmer h-[520px] rounded-2xl"></div>
</div>
{:else if contradictions.length === 0}
<div class="glass-panel enter flex flex-col items-center gap-3 rounded-2xl p-12 text-center">
<div
class="flex h-14 w-14 items-center justify-center rounded-2xl border border-recall/25 bg-recall/10 text-recall"
>
<Icon name="sparkle" size={26} draw />
</div>
<div class="text-sm font-medium text-bright">
No contradictions found — your memory agrees with itself.
</div>
<div class="max-w-sm text-xs text-muted">
Pairs appear here when two trusted memories about the same topic make opposing claims.
</div>
</div>
{:else}
<!-- Stats bar -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3">
<div use:reveal={{ delay: 0, y: 12 }} class="p-4 glass rounded-xl lift">
<div class="text-2xl text-bright font-bold tabular-nums">
<AnimatedNumber value={TOTAL_CONTRADICTIONS_DETECTED} />
<AnimatedNumber value={totalDetected} />
</div>
<div class="text-xs text-dim mt-1">
contradictions across {totalMemoriesInvolved.toLocaleString()} memories
@ -572,4 +355,5 @@
{/each}
</aside>
</div>
{/if}
</div>

View file

@ -8,34 +8,17 @@
<script lang="ts">
import { onMount, onDestroy } from 'svelte';
import DuplicateCluster from '$components/DuplicateCluster.svelte';
import { clusterKey, filterByThreshold } from '$components/duplicates-helpers';
import { clusterKey } from '$components/duplicates-helpers';
import PageHeader from '$lib/components/PageHeader.svelte';
import Icon from '$lib/components/Icon.svelte';
import AnimatedNumber from '$lib/components/AnimatedNumber.svelte';
import { reveal } from '$lib/actions/reveal';
import { spotlight } from '$lib/actions/interactions';
interface ClusterMemory {
id: string;
content: string;
nodeType: string;
tags: string[];
retention: number;
createdAt: string;
}
interface Cluster {
similarity: number;
memories: ClusterMemory[];
suggestedAction: 'merge' | 'review';
}
interface DuplicatesResponse {
clusters: Cluster[];
}
import { api } from '$stores/api';
import type { DuplicateClusterGroup } from '$types';
let threshold = $state(0.8);
let clusters: Cluster[] = $state([]);
let clusters: DuplicateClusterGroup[] = $state([]);
// Dismissed clusters are tracked by stable identity (sorted member ids) so
// dismissals survive a re-fetch. If the cluster membership changes, the key
// changes and the cluster is treated as fresh.
@ -44,165 +27,11 @@
let error: string | null = $state(null);
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
// Mock realistic response. Swap for real fetch when backend ships.
// TODO(backend-swap): replace `mockFetchDuplicates` with:
// const res = await fetch(`/api/duplicates?threshold=${t}`);
// return (await res.json()) as DuplicatesResponse;
// The pure `filterByThreshold` helper in duplicates-helpers.ts mirrors the
// server-side >= semantics so the UI behaves identically before and after.
async function mockFetchDuplicates(t: number): Promise<DuplicatesResponse> {
// Simulate latency so the skeleton is visible.
await new Promise((r) => setTimeout(r, 450));
const all: Cluster[] = [
{
similarity: 0.96,
suggestedAction: 'merge',
memories: [
{
id: 'm-001',
content:
'BUG FIX: Harmony parser dropped `final` channel tokens when tool call followed. Root cause: 5-layer fallback missed the final channel marker when channel switched mid-stream. Solution: added final-channel detector before tool-call pop. Files: src/parser/harmony.rs',
nodeType: 'fact',
tags: ['bug-fix', 'benchmark-suite', 'parser'],
retention: 0.91,
createdAt: '2026-04-12T14:22:00Z',
},
{
id: 'm-002',
content:
'Fixed Harmony parser final-channel bug — 5-layer fallback was missing the final channel marker when a tool call followed. Added detector before tool pop.',
nodeType: 'fact',
tags: ['bug-fix', 'benchmark-suite'],
retention: 0.64,
createdAt: '2026-04-13T09:15:00Z',
},
{
id: 'm-003',
content:
'Harmony parser: final channel dropped on tool-call. Patched the fallback stack.',
nodeType: 'note',
tags: ['parser'],
retention: 0.38,
createdAt: '2026-04-14T11:02:00Z',
},
],
},
{
similarity: 0.88,
suggestedAction: 'merge',
memories: [
{
id: 'm-004',
content:
'DECISION: Use vLLM prefix caching at 0.35 gpu_memory_utilization for benchmark suite submissions. Alternatives considered: sglang (slower cold start), TensorRT-LLM (deployment friction).',
nodeType: 'decision',
tags: ['vllm', 'benchmark-suite', 'inference'],
retention: 0.84,
createdAt: '2026-04-05T18:44:00Z',
},
{
id: 'm-005',
content:
'Chose vLLM with prefix caching (0.35 mem util) over sglang and TensorRT-LLM for benchmark suite inference.',
nodeType: 'decision',
tags: ['vllm', 'benchmark-suite'],
retention: 0.72,
createdAt: '2026-04-06T10:30:00Z',
},
],
},
{
similarity: 0.83,
suggestedAction: 'review',
memories: [
{
id: 'm-006',
content:
'Release process prefers one change per benchmark submission — stacking changes destroyed signal in a prior run.',
nodeType: 'pattern',
tags: ['methodology', 'benchmark-suite'],
retention: 0.88,
createdAt: '2026-04-04T22:10:00Z',
},
{
id: 'm-007',
content:
'One-variable-at-a-time rule: never stack multiple changes per submission. Paper 2603.27844 proves +/-2 points is noise.',
nodeType: 'pattern',
tags: ['kaggle', 'methodology'],
retention: 0.67,
createdAt: '2026-04-08T16:20:00Z',
},
{
id: 'm-008',
content: 'Lesson: stacking many changes in one benchmark run hid the causal signal. Always isolate variables.',
nodeType: 'note',
tags: ['methodology'],
retention: 0.42,
createdAt: '2026-04-15T08:55:00Z',
},
],
},
{
similarity: 0.78,
suggestedAction: 'review',
memories: [
{
id: 'm-009',
content:
'Dimensional Illusion performance: 7-minute flow poi set, LED config Parthenos overcook preset, tempo 128 BPM.',
nodeType: 'event',
tags: ['dimensional-illusion', 'poi', 'performance'],
retention: 0.76,
createdAt: '2026-03-28T19:45:00Z',
},
{
id: 'm-010',
content: 'Dimensional Illusion set: 7 min, Parthenos LED overcook, 128 BPM.',
nodeType: 'event',
tags: ['dimensional-illusion', 'poi'],
retention: 0.51,
createdAt: '2026-04-02T12:12:00Z',
},
],
},
{
similarity: 0.76,
suggestedAction: 'review',
memories: [
{
id: 'm-011',
content:
'Vestige v2.0.7 shipped active forgetting via Anderson 2025 top-down inhibition + Davis Rac1 cascade. Suppress compounds, reversible 24h.',
nodeType: 'fact',
tags: ['vestige', 'release', 'active-forgetting'],
retention: 0.93,
createdAt: '2026-04-17T03:22:00Z',
},
{
id: 'm-012',
content:
'Active Forgetting feature: compounds on each suppress, 24h reversible labile window, violet implosion animation in graph view.',
nodeType: 'concept',
tags: ['vestige', 'active-forgetting'],
retention: 0.81,
createdAt: '2026-04-18T09:07:00Z',
},
],
},
];
return { clusters: filterByThreshold(all, t) };
}
async function detect() {
loading = true;
error = null;
try {
// TODO: swap for real endpoint /api/duplicates when backend ships.
// See comment on mockFetchDuplicates for the exact replacement.
const res = await mockFetchDuplicates(threshold);
const res = await api.duplicates(threshold);
clusters = res.clusters;
// Prune dismissals whose clusters no longer exist — prevents
// unbounded growth across sessions and keeps the set honest.

View file

@ -15,29 +15,11 @@
import Icon from '$components/Icon.svelte';
import AnimatedNumber from '$components/AnimatedNumber.svelte';
import { reveal } from '$lib/actions/reveal';
type Category =
| 'ErrorHandling'
| 'AsyncConcurrency'
| 'Testing'
| 'Architecture'
| 'Performance'
| 'Security';
interface Pattern {
name: string;
category: Category;
origin_project: string;
transferred_to: string[];
transfer_count: number;
last_used: string;
confidence: number;
}
interface CrossProjectResponse {
projects: string[];
patterns: Pattern[];
}
import { api } from '$stores/api';
import type {
CrossProjectCategory as Category,
CrossProjectPatternsResponse
} from '$types';
const CATEGORIES: readonly Category[] = [
'ErrorHandling',
@ -58,277 +40,16 @@
};
let activeCategory = $state<'All' | Category>('All');
let data = $state<CrossProjectResponse>({ projects: [], patterns: [] });
let data = $state<CrossProjectPatternsResponse>({ projects: [], patterns: [] });
let loading = $state(true);
let error: string | null = $state(null);
let selectedCell = $state<{ from: string; to: string } | null>(null);
// TODO: swap for real fetch to /api/patterns/cross-project when backend ships.
// The CrossProjectLearner already tracks these categories in Rust — exposing
// it over HTTP is a straightforward map-to-DTO. Matching shape below so the
// swap is a one-liner.
async function mockFetchCrossProject(): Promise<CrossProjectResponse> {
await new Promise((r) => setTimeout(r, 420));
const projects = [
'vestige',
'api-gateway',
'desktop-app',
'model-runner',
'game-sim',
'security-dashboard',
'benchmark-suite'
];
const patterns: Pattern[] = [
// ErrorHandling — widely transferred
{
name: 'Result<T, E> with thiserror context',
category: 'ErrorHandling',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app', 'model-runner', 'security-dashboard'],
transfer_count: 4,
last_used: '2026-04-18T14:22:00Z',
confidence: 0.94
},
{
name: 'Axum error middleware with tower-http',
category: 'ErrorHandling',
origin_project: 'api-gateway',
transferred_to: ['vestige', 'security-dashboard'],
transfer_count: 2,
last_used: '2026-04-17T09:10:00Z',
confidence: 0.88
},
{
name: 'Graceful shutdown on SIGINT/SIGTERM',
category: 'ErrorHandling',
origin_project: 'vestige',
transferred_to: ['vestige', 'desktop-app', 'security-dashboard'],
transfer_count: 3,
last_used: '2026-04-15T22:01:00Z',
confidence: 0.82
},
{
name: 'Python try/except with contextual re-raise',
category: 'ErrorHandling',
origin_project: 'benchmark-suite',
transferred_to: ['model-runner'],
transfer_count: 1,
last_used: '2026-04-10T11:30:00Z',
confidence: 0.7
},
// AsyncConcurrency
{
name: 'Arc<Mutex<Connection>> reader/writer split',
category: 'AsyncConcurrency',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app'],
transfer_count: 2,
last_used: '2026-04-14T16:42:00Z',
confidence: 0.91
},
{
name: 'tokio::select! for cancellation propagation',
category: 'AsyncConcurrency',
origin_project: 'desktop-app',
transferred_to: ['vestige', 'security-dashboard'],
transfer_count: 2,
last_used: '2026-04-19T08:05:00Z',
confidence: 0.86
},
{
name: 'Bounded mpsc channel with backpressure',
category: 'AsyncConcurrency',
origin_project: 'desktop-app',
transferred_to: ['vestige', 'api-gateway'],
transfer_count: 2,
last_used: '2026-04-12T13:18:00Z',
confidence: 0.83
},
{
name: 'asyncio.gather with return_exceptions',
category: 'AsyncConcurrency',
origin_project: 'model-runner',
transferred_to: ['benchmark-suite'],
transfer_count: 1,
last_used: '2026-04-08T20:45:00Z',
confidence: 0.72
},
// Testing
{
name: 'Property-based tests with proptest',
category: 'Testing',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app'],
transfer_count: 2,
last_used: '2026-04-11T10:22:00Z',
confidence: 0.89
},
{
name: 'Snapshot testing with insta',
category: 'Testing',
origin_project: 'api-gateway',
transferred_to: ['vestige'],
transfer_count: 1,
last_used: '2026-04-16T14:00:00Z',
confidence: 0.81
},
{
name: 'Vitest + Playwright dashboard harness',
category: 'Testing',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app'],
transfer_count: 2,
last_used: '2026-04-19T18:30:00Z',
confidence: 0.87
},
{
name: 'One-variable-at-a-time Kaggle submission',
category: 'Testing',
origin_project: 'benchmark-suite',
transferred_to: ['model-runner', 'game-sim'],
transfer_count: 2,
last_used: '2026-04-20T07:15:00Z',
confidence: 0.95
},
{
name: 'Kaggle pre-flight Input-panel screenshot',
category: 'Testing',
origin_project: 'benchmark-suite',
transferred_to: ['model-runner', 'game-sim'],
transfer_count: 2,
last_used: '2026-04-20T06:50:00Z',
confidence: 0.98
},
// Architecture
{
name: 'SvelteKit 2 + Svelte 5 runes dashboard',
category: 'Architecture',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'security-dashboard'],
transfer_count: 2,
last_used: '2026-04-19T12:10:00Z',
confidence: 0.92
},
{
name: 'glass-panel + cosmic-dark design system',
category: 'Architecture',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'security-dashboard', 'desktop-app'],
transfer_count: 3,
last_used: '2026-04-20T09:00:00Z',
confidence: 0.9
},
{
name: 'Tauri 2 + Rust/Axum sidecar',
category: 'Architecture',
origin_project: 'desktop-app',
transferred_to: ['security-dashboard'],
transfer_count: 1,
last_used: '2026-04-13T19:44:00Z',
confidence: 0.78
},
{
name: 'MCP server with 23 stateful tools',
category: 'Architecture',
origin_project: 'vestige',
transferred_to: ['desktop-app'],
transfer_count: 1,
last_used: '2026-04-17T11:05:00Z',
confidence: 0.85
},
// Performance
{
name: 'USearch HNSW index for vector search',
category: 'Performance',
origin_project: 'vestige',
transferred_to: ['api-gateway'],
transfer_count: 1,
last_used: '2026-04-09T15:20:00Z',
confidence: 0.88
},
{
name: 'SQLite WAL mode for concurrent reads',
category: 'Performance',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app', 'security-dashboard'],
transfer_count: 3,
last_used: '2026-04-18T21:33:00Z',
confidence: 0.93
},
{
name: 'vLLM prefix caching at 0.35 mem util',
category: 'Performance',
origin_project: 'benchmark-suite',
transferred_to: ['model-runner'],
transfer_count: 1,
last_used: '2026-04-11T08:00:00Z',
confidence: 0.84
},
{
name: 'Cross-encoder rerank at k=30',
category: 'Performance',
origin_project: 'vestige',
transferred_to: ['api-gateway'],
transfer_count: 1,
last_used: '2026-04-14T17:55:00Z',
confidence: 0.79
},
// Security
{
name: 'Rotated auth token in env var',
category: 'Security',
origin_project: 'vestige',
transferred_to: ['api-gateway', 'desktop-app', 'security-dashboard'],
transfer_count: 3,
last_used: '2026-04-16T20:12:00Z',
confidence: 0.96
},
{
name: 'Parameterized SQL via rusqlite params!',
category: 'Security',
origin_project: 'vestige',
transferred_to: ['api-gateway'],
transfer_count: 1,
last_used: '2026-04-10T13:40:00Z',
confidence: 0.89
},
{
name: '664-pattern secret scanner',
category: 'Security',
origin_project: 'api-gateway',
transferred_to: ['vestige', 'security-dashboard', 'desktop-app'],
transfer_count: 3,
last_used: '2026-04-20T05:30:00Z',
confidence: 0.97
},
{
name: 'CSP header with nonce-based script allow',
category: 'Security',
origin_project: 'api-gateway',
transferred_to: ['security-dashboard'],
transfer_count: 1,
last_used: '2026-04-05T16:08:00Z',
confidence: 0.8
}
];
return { projects, patterns };
}
async function load() {
loading = true;
error = null;
try {
// TODO: const res = await fetch('/api/patterns/cross-project');
// data = await res.json();
data = await mockFetchCrossProject();
data = await api.crossProjectPatterns();
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to load pattern transfers';
data = { projects: [], patterns: [] };