Skip to content

Commit 5987170

Browse files
authored
Merge pull request #2 from dreadnode/fix/gitignore-fix
fix: gitignore bugfix
2 parents 785c4ad + 2a6f064 commit 5987170

23 files changed

Lines changed: 2028 additions & 4 deletions

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
runs/
2-
runs_old/
3-
repos/
4-
experiments/
1+
/runs/
2+
/runs_old/
3+
/repos/
4+
/experiments/
55
__pycache__/
66
*.pyc
77
.venv/
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { error } from "@sveltejs/kit";
2+
import type { LayoutServerLoad } from "./$types";
3+
import { loadRunMeta } from "$lib/server/runs";
4+
import { readTextFile, runPath, fileExists } from "$lib/server/fs";
5+
import { join } from "node:path";
6+
7+
export const load: LayoutServerLoad = async ({ params }) => {
8+
let meta;
9+
try {
10+
meta = await loadRunMeta(params.runName);
11+
} catch {
12+
error(404, `Run not found: ${params.runName}`);
13+
}
14+
let configYaml = "";
15+
try {
16+
configYaml = await readTextFile(join(runPath(params.runName), "config.yaml"));
17+
} catch {
18+
// config may not exist
19+
}
20+
21+
const analysisPath = join(runPath(params.runName), "analysis.md");
22+
const hasAnalysis = await fileExists(analysisPath);
23+
24+
return { meta, configYaml, runName: params.runName, hasAnalysis };
25+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
<script lang="ts">
2+
import { page } from "$app/state";
3+
import { formatCost, formatDuration } from "$lib/utils/format";
4+
import { sessionKey } from "$lib/types/run";
5+
6+
let { data, children } = $props();
7+
let meta = $derived(data.meta);
8+
9+
let activeSessionIdx = $derived.by(() => {
10+
const m = page.url.pathname.match(/\/sessions\/([^/]+)/);
11+
return m ? m[1] : null;
12+
});
13+
14+
let tabs = $derived([
15+
{ href: `/runs/${meta.run_name}`, label: "overview", exact: true },
16+
...(data.hasAnalysis ? [{ href: `/runs/${meta.run_name}/analysis`, label: "analysis", exact: false }] : []),
17+
{ href: `/runs/${meta.run_name}/memory`, label: "memory", exact: false },
18+
{ href: `/runs/${meta.run_name}/changelog`, label: "changelog", exact: false },
19+
{ href: `/runs/${meta.run_name}/config`, label: "config", exact: false },
20+
]);
21+
</script>
22+
23+
<!-- Breadcrumb + run name -->
24+
<div style="margin-bottom: 1.25rem;">
25+
<div style="display: flex; align-items: center; gap: 0.375rem; font-size: 12px; color: var(--muted-foreground); margin-bottom: 0.75rem;">
26+
<a href="/" style="color: var(--muted-foreground);">runs/</a>
27+
<span style="color: var(--term-dim);">/</span>
28+
<span style="color: var(--foreground);">{meta.run_name}</span>
29+
</div>
30+
31+
<!-- Run metadata line -->
32+
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; font-size: 12px; color: var(--muted-foreground);">
33+
<span class="meta-pill" style="color: var(--foreground);">{meta.model}</span>
34+
<span class="meta-pill">{meta.provider}</span>
35+
<span class="meta-pill">{meta.session_mode}</span>
36+
<span class="meta-pill">{meta.total_steps} steps</span>
37+
<span class="meta-pill" style="color: var(--term-amber);">{formatCost(meta.total_cost_usd)}</span>
38+
<span class="meta-pill">{formatDuration(meta.started_at, meta.finished_at)}</span>
39+
{#each meta.tags as tag}
40+
<span class="meta-pill" style="color: var(--term-cyan);">#{tag}</span>
41+
{/each}
42+
</div>
43+
44+
{#if meta.hypothesis}
45+
<p style="color: var(--muted-foreground); font-size: 12px; margin-top: 0.5rem; font-style: italic;">// {meta.hypothesis}</p>
46+
{/if}
47+
</div>
48+
49+
<!-- Tab nav -->
50+
<nav style="display: flex; align-items: center; gap: 0.25rem; margin-bottom: 0.25rem; flex-wrap: wrap;">
51+
{#each tabs as tab}
52+
{@const isActive = tab.exact ? page.url.pathname === tab.href : page.url.pathname.startsWith(tab.href)}
53+
<a
54+
href={tab.href}
55+
class="term-tab {isActive ? 'active' : ''}"
56+
>
57+
[{tab.label}]
58+
</a>
59+
{/each}
60+
</nav>
61+
62+
<!-- Session pills (only shown on run-level pages) -->
63+
{#if activeSessionIdx === null}
64+
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 0.25rem; padding: 0.625rem 0 1.25rem; border-top: 1px solid var(--border);">
65+
<span style="font-size: 11px; color: var(--muted-foreground); margin-right: 0.25rem;">sessions:</span>
66+
{#each meta.sessions as session}
67+
{@const key = sessionKey(session)}
68+
<a
69+
href="/runs/{meta.run_name}/sessions/{key}"
70+
style="font-size: 11px; padding: 0.125rem 0.375rem; border: 1px solid var(--border); color: var(--muted-foreground);"
71+
class="session-pill-inactive"
72+
>
73+
[{session.session_index}{#if session.replicate != null}r{session.replicate}{/if}{#if session.fork_from}↑{session.fork_from}{/if}]
74+
{#if session.error}
75+
<span style="color: var(--term-red);">!</span>
76+
{/if}
77+
<span style="color: var(--term-dim);">({session.step_count})</span>
78+
</a>
79+
{/each}
80+
</div>
81+
{:else}
82+
<div style="border-top: 1px solid var(--border); margin-bottom: 1.25rem;"></div>
83+
{/if}
84+
85+
{@render children()}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script lang="ts">
2+
import { formatCost, formatDuration } from "$lib/utils/format";
3+
import { sessionKey, type SessionMeta } from "$lib/types/run";
4+
5+
let { data } = $props();
6+
let meta = $derived(data.meta);
7+
8+
let hasForks = $derived(meta.sessions.some((s: any) => s.fork_from));
9+
10+
/** Group sessions: base sessions with their replicates nested underneath. */
11+
interface SessionGroup {
12+
base: SessionMeta;
13+
replicates: SessionMeta[];
14+
}
15+
16+
let sessionGroups = $derived.by(() => {
17+
const groups: SessionGroup[] = [];
18+
const byIndex = new Map<number, { base: SessionMeta | null; reps: SessionMeta[] }>();
19+
20+
for (const s of meta.sessions) {
21+
const entry = byIndex.get(s.session_index) ?? { base: null, reps: [] };
22+
if (s.replicate != null) {
23+
entry.reps.push(s);
24+
} else {
25+
entry.base = s;
26+
}
27+
byIndex.set(s.session_index, entry);
28+
}
29+
30+
// Sort by session_index
31+
const indices = [...byIndex.keys()].sort((a, b) => a - b);
32+
for (const idx of indices) {
33+
const entry = byIndex.get(idx)!;
34+
const reps = entry.reps.sort((a, b) => (a.replicate ?? 0) - (b.replicate ?? 0));
35+
if (entry.base) {
36+
groups.push({ base: entry.base, replicates: reps });
37+
} else if (reps.length > 0) {
38+
// No plain session, first replicate is the "base"
39+
groups.push({ base: reps[0], replicates: reps.slice(1) });
40+
}
41+
}
42+
return groups;
43+
});
44+
45+
let metrics = $derived([
46+
{ label: "sessions", value: meta.session_count },
47+
{ label: "steps", value: meta.total_steps },
48+
{ label: "tool_calls", value: meta.total_tool_calls },
49+
{ label: "file_writes", value: meta.total_file_writes },
50+
{ label: "compactions", value: meta.total_compaction_events },
51+
{ label: "subagents", value: meta.total_subagent_invocations },
52+
]);
53+
</script>
54+
55+
{#snippet sessionRow(session: SessionMeta, isReplicate: boolean, showBorder: boolean)}
56+
{@const key = sessionKey(session)}
57+
{@const indent = isReplicate || (hasForks && session.fork_from)}
58+
<a
59+
href="/runs/{meta.run_name}/sessions/{key}"
60+
style="display: flex; align-items: center; justify-content: space-between; padding: 0.625rem 0.875rem; font-size: 12px; {showBorder ? 'border-top: 1px solid var(--border);' : ''} {indent ? 'padding-left: 2rem;' : ''}"
61+
class="session-row"
62+
>
63+
<div style="display: flex; align-items: center; gap: 0.75rem;">
64+
{#if isReplicate}
65+
<span style="color: var(--muted-foreground); font-size: 11px;">↳</span>
66+
{:else if session.fork_from}
67+
<span style="color: var(--muted-foreground);">↳</span>
68+
{/if}
69+
<span style="color: {isReplicate ? 'var(--muted-foreground)' : 'var(--foreground)'};">
70+
{#if isReplicate}
71+
r{String(session.replicate).padStart(2, "0")}
72+
{:else}
73+
session_{session.session_index}{#if session.replicate != null}<span style="color: var(--muted-foreground);"> r{String(session.replicate).padStart(2, "0")}</span>{/if}
74+
{/if}
75+
</span>
76+
{#if !isReplicate && session.fork_from}
77+
<span style="color: var(--muted-foreground); font-size: 11px;">fork↑{session.fork_from}</span>
78+
{/if}
79+
{#if session.error}
80+
<span style="color: var(--term-red); font-size: 11px;">[error]</span>
81+
{/if}
82+
{#if session.compaction_count > 0}
83+
<span style="color: var(--term-dim); font-size: 11px;">{session.compaction_count} compact</span>
84+
{/if}
85+
{#if session.subagent_count > 0}
86+
<span style="color: var(--term-dim); font-size: 11px;">{session.subagent_count} subagent{session.subagent_count > 1 ? "s" : ""}</span>
87+
{/if}
88+
</div>
89+
<div style="display: flex; align-items: center; gap: 1.5rem; color: var(--muted-foreground);">
90+
<span>{session.step_count} steps</span>
91+
<span>{session.tool_call_count} tools</span>
92+
<span>{session.num_turns} turns</span>
93+
<span style="color: var(--term-amber);">{formatCost(session.total_cost_usd)}</span>
94+
<span>{formatDuration(session.started_at, session.finished_at)}</span>
95+
<span class="arrow" style="opacity: 0.3;">→</span>
96+
</div>
97+
</a>
98+
{/snippet}
99+
100+
<!-- Metrics -->
101+
<div style="font-size: 12px; color: var(--muted-foreground); margin-bottom: 1.5rem; display: flex; flex-wrap: wrap; gap: 1.5rem;">
102+
{#each metrics as m}
103+
<span>
104+
<span style="color: var(--muted-foreground);">{m.label}=</span><span style="color: var(--foreground);">{m.value}</span>
105+
</span>
106+
{/each}
107+
</div>
108+
109+
<!-- Sessions list -->
110+
<div style="font-size: 11px; color: var(--muted-foreground); margin-bottom: 0.5rem; letter-spacing: 0.08em;">SESSIONS</div>
111+
<div style="display: flex; flex-direction: column; border: 1px solid var(--border);">
112+
{#each sessionGroups as group, gi}
113+
{@render sessionRow(group.base, false, gi > 0)}
114+
{#each group.replicates as rep}
115+
{@render sessionRow(rep, true, true)}
116+
{/each}
117+
{/each}
118+
</div>
119+
120+
<style>
121+
.session-row:hover {
122+
background: var(--card);
123+
}
124+
.session-row:hover .arrow {
125+
opacity: 1;
126+
color: var(--term-green);
127+
}
128+
</style>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { PageServerLoad } from "./$types";
2+
import { readTextFile, runPath } from "$lib/server/fs";
3+
import { join } from "node:path";
4+
import { error } from "@sveltejs/kit";
5+
6+
export const load: PageServerLoad = async ({ params }) => {
7+
try {
8+
const content = await readTextFile(join(runPath(params.runName), "analysis.md"));
9+
return { analysisMarkdown: content };
10+
} catch {
11+
return error(404, "No analysis found for this run");
12+
}
13+
};
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<script lang="ts">
2+
import { renderMarkdown } from "$lib/utils/markdown";
3+
4+
let { data } = $props();
5+
let html = $derived(renderMarkdown(data.analysisMarkdown));
6+
</script>
7+
8+
<style>
9+
.analysis :global(h1) {
10+
font-size: 1.125rem;
11+
font-weight: 600;
12+
border-bottom: 1px solid var(--border);
13+
padding-bottom: 0.5rem;
14+
margin-bottom: 1rem;
15+
margin-top: 0;
16+
color: var(--foreground);
17+
}
18+
.analysis :global(h2) {
19+
font-size: 1rem;
20+
font-weight: 600;
21+
margin-top: 2rem;
22+
margin-bottom: 0.75rem;
23+
color: var(--foreground);
24+
}
25+
.analysis :global(h3) {
26+
font-size: 0.875rem;
27+
font-weight: 600;
28+
margin-top: 1.5rem;
29+
margin-bottom: 0.5rem;
30+
color: var(--foreground);
31+
}
32+
.analysis :global(p) {
33+
font-size: 0.875rem;
34+
line-height: 1.625;
35+
margin: 0.5rem 0;
36+
color: var(--foreground);
37+
}
38+
.analysis :global(ul),
39+
.analysis :global(ol) {
40+
font-size: 0.875rem;
41+
line-height: 1.625;
42+
margin: 0.5rem 0;
43+
padding-left: 1.5rem;
44+
color: var(--foreground);
45+
}
46+
.analysis :global(li) {
47+
margin: 0.25rem 0;
48+
}
49+
.analysis :global(pre) {
50+
font-size: 0.75rem;
51+
background: var(--muted);
52+
border-radius: 0.5rem;
53+
padding: 0.75rem 1rem;
54+
margin: 0.75rem 0;
55+
overflow-x: auto;
56+
}
57+
.analysis :global(code) {
58+
font-size: 0.75rem;
59+
}
60+
.analysis :global(code):not(:global(pre) :global(code)) {
61+
background: var(--muted);
62+
padding: 0.125rem 0.375rem;
63+
border-radius: 0.25rem;
64+
}
65+
.analysis :global(blockquote) {
66+
border-left: 3px solid var(--border);
67+
padding-left: 1rem;
68+
margin: 0.75rem 0;
69+
color: var(--muted-foreground);
70+
font-size: 0.875rem;
71+
}
72+
.analysis :global(hr) {
73+
border: none;
74+
border-top: 1px solid var(--border);
75+
margin: 1.5rem 0;
76+
}
77+
.analysis :global(strong) {
78+
font-weight: 600;
79+
color: var(--foreground);
80+
}
81+
.analysis :global(em) {
82+
color: var(--muted-foreground);
83+
}
84+
.analysis :global(a) {
85+
color: var(--foreground);
86+
text-decoration: underline;
87+
text-underline-offset: 2px;
88+
}
89+
.analysis :global(a):hover {
90+
opacity: 0.7;
91+
}
92+
.analysis :global(table) {
93+
width: 100%;
94+
font-size: 0.875rem;
95+
border-collapse: collapse;
96+
margin: 0.75rem 0;
97+
}
98+
.analysis :global(th),
99+
.analysis :global(td) {
100+
border: 1px solid var(--border);
101+
padding: 0.375rem 0.75rem;
102+
text-align: left;
103+
}
104+
.analysis :global(th) {
105+
background: var(--muted);
106+
font-weight: 600;
107+
}
108+
</style>
109+
110+
<div class="analysis" style="max-width: none; color: var(--foreground);">
111+
{@html html}
112+
</div>

0 commit comments

Comments
 (0)