Skip to content

Commit 95a9790

Browse files
DylanMerigaudclaude
andcommitted
Trim README, add Prettier, reframe the deterministic section
README: cut ~35% — drop the Mastra-tutorial framing, the .branch() code dump, the ASCII architecture tree, "verified live", and the "no ESLint/Prettier" defense. Reframe the deterministic-decisions section as "LLMs for language, code for the numbers" (confident, not apologetic) and add an honest prod-vs-demo note. Tooling: add Prettier (format + format:check scripts, .prettierignore, wired into CI). Reformatted the code (markdown left hand-formatted for the Mermaid/tables). All gates green: format · typecheck · knip · 82 tests · build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1b39981 commit 95a9790

39 files changed

Lines changed: 1051 additions & 574 deletions

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
check:
11-
name: typecheck · knip · test · build
11+
name: format · typecheck · knip · test · build
1212
runs-on: ubuntu-latest
1313
steps:
1414
- uses: actions/checkout@v4
@@ -23,6 +23,9 @@ jobs:
2323
- name: Install dependencies
2424
run: pnpm install --frozen-lockfile
2525

26+
- name: Format check (Prettier)
27+
run: pnpm format:check
28+
2629
- name: Typecheck
2730
run: pnpm typecheck
2831

.prettierignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Generated / vendored
2+
node_modules
3+
.next
4+
drizzle
5+
pnpm-lock.yaml
6+
7+
# Markdown is hand-formatted (tables, the Mermaid diagram) — leave it alone.
8+
*.md

README.md

Lines changed: 56 additions & 147 deletions
Large diffs are not rendered by default.

app/api/run/route.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { loadRunBundle } from "@/db/client";
33
import { toTraceEvent, pipelineErrorEvent, type TraceEvent } from "@/lib/trace";
44
import { ndjsonLine } from "@/lib/ndjson";
55
import { checkRateLimit, clientIpFrom } from "@/lib/ratelimit";
6-
import { RunRequest, STREAM_CONTENT_TYPE, type StreamDone } from "@/lib/api-types";
6+
import {
7+
RunRequest,
8+
STREAM_CONTENT_TYPE,
9+
type StreamDone,
10+
} from "@/lib/api-types";
711

812
/**
913
* POST /api/run — execute the procure-to-pay pipeline for one seeded invoice and
@@ -50,7 +54,10 @@ export async function POST(request: Request): Promise<Response> {
5054
Math.ceil(verdict.retryAfterSeconds / 60),
5155
)} minute(s).`,
5256
},
53-
{ status: 429, headers: { "retry-after": String(verdict.retryAfterSeconds) } },
57+
{
58+
status: 429,
59+
headers: { "retry-after": String(verdict.retryAfterSeconds) },
60+
},
5461
);
5562
}
5663

@@ -64,7 +71,9 @@ export async function POST(request: Request): Promise<Response> {
6471
decision = parsed.decision;
6572
} catch {
6673
return Response.json(
67-
{ error: "Body must be { id: string, decision?: \"approve\" | \"reject\" }." },
74+
{
75+
error: 'Body must be { id: string, decision?: "approve" | "reject" }.',
76+
},
6877
{ status: 400 },
6978
);
7079
}
@@ -88,7 +97,10 @@ export async function POST(request: Request): Promise<Response> {
8897
return Response.json({ error: message }, { status: 500 });
8998
}
9099
if (!bundle) {
91-
return Response.json({ error: `No seeded invoice with id "${id}".` }, { status: 404 });
100+
return Response.json(
101+
{ error: `No seeded invoice with id "${id}".` },
102+
{ status: 404 },
103+
);
92104
}
93105

94106
// 3. Stream the workflow run.
@@ -134,7 +146,10 @@ export async function POST(request: Request): Promise<Response> {
134146
err instanceof Error ? err.message : "Unexpected pipeline error.";
135147
emit(pipelineErrorEvent(message));
136148
} finally {
137-
const done: StreamDone = { done: true, durationMs: Date.now() - startedAt };
149+
const done: StreamDone = {
150+
done: true,
151+
durationMs: Date.now() - startedAt,
152+
};
138153
controller.enqueue(line(done));
139154
controller.close();
140155
}

app/opengraph-image.tsx

Lines changed: 103 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -22,114 +22,124 @@ export default function OpengraphImage() {
2222
const stages = ["Intake", "Matching", "Approval", "Reconciliation"];
2323

2424
return new ImageResponse(
25-
(
26-
<div
27-
style={{
28-
width: "100%",
29-
height: "100%",
30-
display: "flex",
31-
flexDirection: "column",
32-
justifyContent: "space-between",
33-
background: "#FAFAFA",
34-
padding: "72px 80px",
35-
fontFamily: "sans-serif",
36-
}}
37-
>
38-
{/* Top: wordmark + a small accent tag */}
39-
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
40-
<div
41-
style={{
42-
width: 18,
43-
height: 18,
44-
borderRadius: 5,
45-
background: ACCENT,
46-
}}
47-
/>
48-
<div style={{ fontSize: 30, fontWeight: 700, color: INK, letterSpacing: -0.5 }}>
49-
ledgerloop
50-
</div>
25+
<div
26+
style={{
27+
width: "100%",
28+
height: "100%",
29+
display: "flex",
30+
flexDirection: "column",
31+
justifyContent: "space-between",
32+
background: "#FAFAFA",
33+
padding: "72px 80px",
34+
fontFamily: "sans-serif",
35+
}}
36+
>
37+
{/* Top: wordmark + a small accent tag */}
38+
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
39+
<div
40+
style={{
41+
width: 18,
42+
height: 18,
43+
borderRadius: 5,
44+
background: ACCENT,
45+
}}
46+
/>
47+
<div
48+
style={{
49+
fontSize: 30,
50+
fontWeight: 700,
51+
color: INK,
52+
letterSpacing: -0.5,
53+
}}
54+
>
55+
ledgerloop
5156
</div>
57+
</div>
5258

53-
{/* Middle: headline + subline */}
54-
<div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
55-
<div
56-
style={{
57-
fontSize: 60,
58-
fontWeight: 700,
59-
color: INK,
60-
lineHeight: 1.08,
61-
letterSpacing: -1.5,
62-
maxWidth: 980,
63-
}}
64-
>
65-
A multi-agent procure-to-pay pipeline
66-
</div>
67-
<div style={{ fontSize: 27, color: MUTED, lineHeight: 1.4, maxWidth: 940 }}>
68-
Four cooperating AI agents match, route, and reconcile each invoice —
69-
with the live execution trace streamed and a real human-in-the-loop on
70-
caught mismatches.
71-
</div>
59+
{/* Middle: headline + subline */}
60+
<div style={{ display: "flex", flexDirection: "column", gap: 22 }}>
61+
<div
62+
style={{
63+
fontSize: 60,
64+
fontWeight: 700,
65+
color: INK,
66+
lineHeight: 1.08,
67+
letterSpacing: -1.5,
68+
maxWidth: 980,
69+
}}
70+
>
71+
A multi-agent procure-to-pay pipeline
72+
</div>
73+
<div
74+
style={{ fontSize: 27, color: MUTED, lineHeight: 1.4, maxWidth: 940 }}
75+
>
76+
Four cooperating AI agents match, route, and reconcile each invoice —
77+
with the live execution trace streamed and a real human-in-the-loop on
78+
caught mismatches.
7279
</div>
80+
</div>
7381

74-
{/* Stage flow */}
75-
<div style={{ display: "flex", alignItems: "center", gap: 14 }}>
76-
{stages.map((label, i) => (
77-
<div key={label} style={{ display: "flex", alignItems: "center", gap: 14 }}>
82+
{/* Stage flow */}
83+
<div style={{ display: "flex", alignItems: "center", gap: 14 }}>
84+
{stages.map((label, i) => (
85+
<div
86+
key={label}
87+
style={{ display: "flex", alignItems: "center", gap: 14 }}
88+
>
89+
<div
90+
style={{
91+
display: "flex",
92+
alignItems: "center",
93+
gap: 12,
94+
background: "#FFFFFF",
95+
border: `1px solid ${LINE}`,
96+
borderRadius: 999,
97+
padding: "12px 22px",
98+
fontSize: 24,
99+
color: INK,
100+
}}
101+
>
78102
<div
79103
style={{
80104
display: "flex",
81105
alignItems: "center",
82-
gap: 12,
83-
background: "#FFFFFF",
84-
border: `1px solid ${LINE}`,
106+
justifyContent: "center",
107+
width: 26,
108+
height: 26,
85109
borderRadius: 999,
86-
padding: "12px 22px",
87-
fontSize: 24,
88-
color: INK,
110+
background: "#EEF2FF",
111+
color: ACCENT,
112+
fontSize: 15,
113+
fontWeight: 700,
89114
}}
90115
>
91-
<div
92-
style={{
93-
display: "flex",
94-
alignItems: "center",
95-
justifyContent: "center",
96-
width: 26,
97-
height: 26,
98-
borderRadius: 999,
99-
background: "#EEF2FF",
100-
color: ACCENT,
101-
fontSize: 15,
102-
fontWeight: 700,
103-
}}
104-
>
105-
{i + 1}
106-
</div>
107-
{label}
116+
{i + 1}
108117
</div>
109-
{i < stages.length - 1 && (
110-
<div style={{ fontSize: 26, color: LINE }}></div>
111-
)}
118+
{label}
112119
</div>
113-
))}
114-
</div>
120+
{i < stages.length - 1 && (
121+
<div style={{ fontSize: 26, color: LINE }}></div>
122+
)}
123+
</div>
124+
))}
125+
</div>
115126

116-
{/* Footer: stack + author */}
117-
<div
118-
style={{
119-
display: "flex",
120-
justifyContent: "space-between",
121-
alignItems: "center",
122-
fontSize: 22,
123-
color: MUTED,
124-
borderTop: `1px solid ${LINE}`,
125-
paddingTop: 28,
126-
}}
127-
>
128-
<div>Mastra · Next.js · Claude Haiku · Supabase</div>
129-
<div style={{ color: INK }}>Dylan Mérigaud</div>
130-
</div>
127+
{/* Footer: stack + author */}
128+
<div
129+
style={{
130+
display: "flex",
131+
justifyContent: "space-between",
132+
alignItems: "center",
133+
fontSize: 22,
134+
color: MUTED,
135+
borderTop: `1px solid ${LINE}`,
136+
paddingTop: 28,
137+
}}
138+
>
139+
<div>Mastra · Next.js · Claude Haiku · Supabase</div>
140+
<div style={{ color: INK }}>Dylan Mérigaud</div>
131141
</div>
132-
),
142+
</div>,
133143
{ ...size },
134144
);
135145
}

app/page.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ function Header() {
5858
</h1>
5959
<p className="mt-1 max-w-2xl text-[13px] leading-relaxed text-muted">
6060
A chain of AI agents runs each invoice through{" "}
61-
<span className="text-ink">procure-to-pay</span> — with the live execution
62-
trace and a real human approval gate on caught mismatches.
61+
<span className="text-ink">procure-to-pay</span> — with the live
62+
execution trace and a real human approval gate on caught mismatches.
6363
</p>
6464
</div>
6565
<div className="flex items-center gap-1.5 text-[11px] text-muted">
@@ -88,7 +88,11 @@ function FlowChip({ n, label }: { n: number; label: string }) {
8888
}
8989

9090
function Arrow() {
91-
return <span aria-hidden className="text-line"></span>;
91+
return (
92+
<span aria-hidden className="text-line">
93+
94+
</span>
95+
);
9296
}
9397

9498
function SetupNotice({ detail }: { detail: string }) {
@@ -97,10 +101,19 @@ function SetupNotice({ detail }: { detail: string }) {
97101
<p className="font-medium">Almost there — the demo needs its database.</p>
98102
<p className="mt-1 text-ink/80">{detail}</p>
99103
<p className="mt-2 text-ink/70">
100-
Set <code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">DATABASE_URL</code>{" "}
101-
(and <code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">ANTHROPIC_API_KEY</code>) in your
102-
environment, then run <code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">pnpm db:push && pnpm db:seed</code>. See
103-
the README for the full setup.
104+
Set{" "}
105+
<code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">
106+
DATABASE_URL
107+
</code>{" "}
108+
(and{" "}
109+
<code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">
110+
ANTHROPIC_API_KEY
111+
</code>
112+
) in your environment, then run{" "}
113+
<code className="rounded bg-surface px-1 py-0.5 font-mono text-[12px]">
114+
pnpm db:push && pnpm db:seed
115+
</code>
116+
. See the README for the full setup.
104117
</p>
105118
</div>
106119
);
@@ -111,8 +124,8 @@ function Footer() {
111124
<footer className="mt-8 flex flex-wrap items-center justify-between gap-3 border-t border-line pt-4 text-[12px] text-muted">
112125
<p>
113126
Built with <span className="text-ink">Mastra</span> · agents on{" "}
114-
<span className="font-mono text-ink">{PIPELINE_MODEL}</span> · Next.js · Supabase ·
115-
Drizzle. Runs are stateless — nothing is written back.
127+
<span className="font-mono text-ink">{PIPELINE_MODEL}</span> · Next.js ·
128+
Supabase · Drizzle. Runs are stateless — nothing is written back.
116129
</p>
117130
<div className="flex items-center gap-2">
118131
<span className="text-ink">Dylan Mérigaud</span>

0 commit comments

Comments
 (0)