Skip to content

Commit 8c62338

Browse files
authored
Merge pull request #4 from O-Labz/token-reduction-v0.2
Token reduction v0.2
2 parents f2adf20 + 856d226 commit 8c62338

82 files changed

Lines changed: 10663 additions & 347 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@ RUN npm run build
4242
# Production stage
4343
FROM node:22-slim
4444

45-
# Install runtime dependencies
45+
# Install runtime dependencies.
46+
# `git` is required at runtime by simple-git for the EML diff observer and
47+
# git-authorship ingestion; without it those features silently no-op.
4648
RUN apt-get update && apt-get install -y \
4749
curl \
50+
git \
4851
&& rm -rf /var/lib/apt/lists/*
4952

5053
WORKDIR /app

README.md

Lines changed: 57 additions & 321 deletions
Large diffs are not rendered by default.

dashboard/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Explorer from './pages/Explorer';
66
import Search from './pages/Search';
77
import McpSetup from './pages/McpSetup';
88
import Metrics from './pages/Metrics';
9+
import Memory from './pages/Memory/Memory';
910
import { ErrorBoundary } from './components/ErrorBoundary';
1011

1112
function App() {
@@ -19,6 +20,7 @@ function App() {
1920
<Route path="/setup" element={<Setup />} />
2021
<Route path="/search" element={<Search />} />
2122
<Route path="/explorer" element={<Explorer />} />
23+
<Route path="/memory" element={<Memory />} />
2224
<Route path="/mcp-setup" element={<McpSetup />} />
2325
<Route path="/metrics" element={<Metrics />} />
2426
</Routes>

dashboard/src/components/Navigation.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export default function Navigation() {
2828
{ path: '/', label: 'Repositories' },
2929
{ path: '/search', label: 'Search' },
3030
{ path: '/explorer', label: 'Explorer' },
31+
{ path: '/memory', label: 'Memory' },
3132
{ path: '/mcp-setup', label: 'MCP Setup' },
3233
{ path: '/metrics', label: 'Metrics' },
3334
];
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useEmlResource } from './useEmlResource';
2+
import { PanelCard, StateBlock } from './Panel';
3+
4+
interface DriftViolation {
5+
ruleId: string;
6+
ruleType: string;
7+
fromRef: string;
8+
toRef?: string;
9+
explanation: string;
10+
}
11+
12+
export function DriftPanel({ repositoryId }: { repositoryId: string | null }) {
13+
const url = repositoryId ? `/api/eml/drift?repositoryId=${repositoryId}` : null;
14+
const { data, loading, error, disabled, reload } = useEmlResource<{ violations: DriftViolation[] }>(url);
15+
const violations = data?.violations ?? [];
16+
17+
return (
18+
<PanelCard
19+
title="Architecture Drift"
20+
icon="rule"
21+
actions={
22+
violations.length > 0 ? (
23+
<span className="text-xs font-semibold text-error bg-error/10 px-2.5 py-1 rounded-full">
24+
{violations.length} violation{violations.length === 1 ? '' : 's'}
25+
</span>
26+
) : undefined
27+
}
28+
>
29+
<StateBlock
30+
loading={loading}
31+
error={error}
32+
disabled={disabled}
33+
empty={violations.length === 0}
34+
onRetry={reload}
35+
emptyLabel="No drift detected. Architecture matches declared rules."
36+
/>
37+
{!loading && !error && !disabled && violations.length > 0 && (
38+
<ul className="space-y-2 max-h-[28rem] overflow-y-auto pr-1">
39+
{violations.map((v, i) => (
40+
<li key={`${v.ruleId}-${i}`} className="p-3 rounded-xl bg-error/5 border border-error/20">
41+
<div className="flex items-center gap-2 mb-1">
42+
<span className="material-symbols-outlined text-base text-error" aria-hidden="true">
43+
warning
44+
</span>
45+
<span className="text-xs font-semibold uppercase tracking-wider text-error">{v.ruleType}</span>
46+
</div>
47+
<p className="text-sm text-on-surface">{v.explanation}</p>
48+
</li>
49+
))}
50+
</ul>
51+
)}
52+
</PanelCard>
53+
);
54+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useEmlResource } from './useEmlResource';
2+
import { PanelCard, StateBlock } from './Panel';
3+
4+
interface KnowledgeGap {
5+
entityRef: string;
6+
entityType: string;
7+
riskScore: number;
8+
reasons: string[];
9+
}
10+
11+
function riskColor(score: number): string {
12+
if (score >= 0.66) return 'text-error';
13+
if (score >= 0.33) return 'text-primary';
14+
return 'text-green-600';
15+
}
16+
17+
export function GapsPanel({ repositoryId }: { repositoryId: string | null }) {
18+
const url = repositoryId ? `/api/eml/gaps?repositoryId=${repositoryId}&limit=20` : null;
19+
const { data, loading, error, disabled, reload } = useEmlResource<{ gaps: KnowledgeGap[] }>(url);
20+
const gaps = data?.gaps ?? [];
21+
22+
return (
23+
<PanelCard title="Knowledge Gaps" icon="troubleshoot">
24+
<StateBlock
25+
loading={loading}
26+
error={error}
27+
disabled={disabled}
28+
empty={gaps.length === 0}
29+
onRetry={reload}
30+
emptyLabel="No knowledge gaps detected."
31+
/>
32+
{!loading && !error && !disabled && gaps.length > 0 && (
33+
<ul className="space-y-2 max-h-[28rem] overflow-y-auto pr-1">
34+
{gaps.map((g) => (
35+
<li key={g.entityRef} className="p-3 rounded-xl border border-outline-variant/15">
36+
<div className="flex items-center justify-between gap-3">
37+
<span className="font-mono text-sm text-on-surface truncate" title={g.entityRef}>
38+
{g.entityRef}
39+
</span>
40+
<span className={`text-sm font-bold tabular-nums ${riskColor(g.riskScore)}`}>
41+
{(g.riskScore * 100).toFixed(0)}%
42+
</span>
43+
</div>
44+
{g.reasons.length > 0 && (
45+
<div className="flex flex-wrap gap-1.5 mt-1.5">
46+
{g.reasons.map((r) => (
47+
<span
48+
key={r}
49+
className="text-xs text-on-surface-variant bg-surface-container px-2 py-0.5 rounded-full"
50+
>
51+
{r}
52+
</span>
53+
))}
54+
</div>
55+
)}
56+
</li>
57+
))}
58+
</ul>
59+
)}
60+
</PanelCard>
61+
);
62+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useEmlResource } from './useEmlResource';
2+
import { PanelCard, StateBlock } from './Panel';
3+
4+
interface IntentRecord {
5+
memoryId: string;
6+
goal: string;
7+
category: string;
8+
status: string;
9+
priority: number;
10+
targetDate?: string | null;
11+
}
12+
13+
export function IntentsPanel({ repositoryId }: { repositoryId: string | null }) {
14+
const url = repositoryId ? `/api/eml/intents?repositoryId=${repositoryId}&limit=50` : null;
15+
const { data, loading, error, disabled, reload } = useEmlResource<{ results: IntentRecord[] }>(url);
16+
const intents = data?.results ?? [];
17+
18+
return (
19+
<PanelCard title="Active Goals" icon="flag">
20+
<StateBlock
21+
loading={loading}
22+
error={error}
23+
disabled={disabled}
24+
empty={intents.length === 0}
25+
onRetry={reload}
26+
emptyLabel="No active goals tracked."
27+
/>
28+
{!loading && !error && !disabled && intents.length > 0 && (
29+
<ul className="space-y-2 max-h-[28rem] overflow-y-auto pr-1">
30+
{intents.map((it) => (
31+
<li key={it.memoryId} className="p-3 rounded-xl border border-outline-variant/15">
32+
<div className="flex items-center justify-between gap-3">
33+
<span className="text-sm font-semibold text-on-surface">{it.goal}</span>
34+
<span className="text-xs font-semibold text-tertiary bg-tertiary/10 px-2 py-0.5 rounded-full">
35+
P{it.priority}
36+
</span>
37+
</div>
38+
<div className="flex items-center gap-2 mt-1 text-xs text-on-surface-variant">
39+
<span className="uppercase tracking-wider">{it.category}</span>
40+
<span aria-hidden="true">·</span>
41+
<span>{it.status}</span>
42+
{it.targetDate && (
43+
<>
44+
<span aria-hidden="true">·</span>
45+
<span>due {new Date(it.targetDate).toLocaleDateString()}</span>
46+
</>
47+
)}
48+
</div>
49+
</li>
50+
))}
51+
</ul>
52+
)}
53+
</PanelCard>
54+
);
55+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { useState } from 'react';
2+
import { useEmlResource } from './useEmlResource';
3+
import { PanelCard, StateBlock, ConfidenceBar } from './Panel';
4+
5+
interface MemoryView {
6+
id: string;
7+
kind: string;
8+
title: string;
9+
summary: string;
10+
confidence: number;
11+
freshness: number;
12+
contradictionScore?: number;
13+
}
14+
15+
const KINDS = ['all', 'decision', 'failure', 'intent', 'gap', 'ownership', 'note'] as const;
16+
17+
const KIND_ICON: Record<string, string> = {
18+
decision: 'gavel',
19+
failure: 'warning',
20+
intent: 'flag',
21+
gap: 'help',
22+
ownership: 'group',
23+
note: 'sticky_note_2',
24+
};
25+
26+
export function MemoryList({ repositoryId }: { repositoryId: string | null }) {
27+
const [kind, setKind] = useState<(typeof KINDS)[number]>('all');
28+
const url = repositoryId
29+
? `/api/eml/memories?repositoryId=${repositoryId}${kind === 'all' ? '' : `&kind=${kind}`}`
30+
: null;
31+
const { data, loading, error, disabled, reload } = useEmlResource<{ results: MemoryView[] }>(url);
32+
const results = data?.results ?? [];
33+
34+
return (
35+
<PanelCard
36+
title="Memories"
37+
icon="psychology"
38+
actions={
39+
<div className="flex items-center gap-2">
40+
<label htmlFor="mem-kind" className="sr-only">
41+
Filter by kind
42+
</label>
43+
<select
44+
id="mem-kind"
45+
value={kind}
46+
onChange={(e) => setKind(e.target.value as (typeof KINDS)[number])}
47+
className="px-2 py-1.5 rounded-lg bg-surface-container border border-outline-variant/30 text-xs text-on-surface focus:outline-none focus:ring-2 focus:ring-tertiary"
48+
>
49+
{KINDS.map((k) => (
50+
<option key={k} value={k}>
51+
{k}
52+
</option>
53+
))}
54+
</select>
55+
</div>
56+
}
57+
>
58+
<StateBlock
59+
loading={loading}
60+
error={error}
61+
disabled={disabled}
62+
empty={results.length === 0}
63+
onRetry={reload}
64+
emptyLabel="No memories captured yet."
65+
/>
66+
{!loading && !error && !disabled && results.length > 0 && (
67+
<ul className="space-y-3 max-h-[28rem] overflow-y-auto pr-1">
68+
{results.map((m) => (
69+
<li
70+
key={m.id}
71+
className="p-3 rounded-xl border border-outline-variant/15 hover:bg-surface-container/50 transition-colors"
72+
>
73+
<div className="flex items-start justify-between gap-3">
74+
<div className="flex items-center gap-2 min-w-0">
75+
<span className="material-symbols-outlined text-base text-tertiary" aria-hidden="true">
76+
{KIND_ICON[m.kind] ?? 'memory'}
77+
</span>
78+
<span className="text-xs font-semibold uppercase tracking-wider text-on-surface-variant">
79+
{m.kind}
80+
</span>
81+
</div>
82+
<ConfidenceBar value={m.confidence} />
83+
</div>
84+
<h3 className="text-sm font-semibold text-on-surface mt-1.5">{m.title}</h3>
85+
{m.summary && <p className="text-sm text-on-surface-variant mt-0.5 line-clamp-2">{m.summary}</p>}
86+
{m.contradictionScore && m.contradictionScore > 0 ? (
87+
<span className="inline-block mt-1.5 text-xs font-semibold text-error">
88+
contradiction risk {(m.contradictionScore * 100).toFixed(0)}%
89+
</span>
90+
) : null}
91+
</li>
92+
))}
93+
</ul>
94+
)}
95+
</PanelCard>
96+
);
97+
}

0 commit comments

Comments
 (0)