Rewrite /sama: v2-first framing + three images + v1 content preserved below hr
Applies the SV "latest is default, legacy is preserved" pattern (Stripe,
Vue, React): the canonical /sama URL stays unchanged, but the first
paint is now v2-framing (SAMA wordmark + "Architecture as code, for
code AI writes" tagline + hero image + prominent v2 spec + live verifier
links). The v1 practitioner content (four-disciplines table, reading
order, sama CLI, blog references) is preserved below a horizontal rule
under the heading "v1 practitioner pages (preserved)", with the v1
sama CLI section honest about being the v1 four-discipline checker
(the v2 verifier with the seven §4 checks runs on the web).
Three new images so the page isn't text-only:
- public/sama-hero.{svg,png} (1200x630) — variant of og.png with the
new tagline + a one-line live-state strip: "7/7 ✓ live · §5 metrics
measured · n=8 cross-repo datapoints". Dark theme matching og.png.
- public/sama-layers.{svg,png} (1200x600) — visual of the four
canonical layers (Pure 0 / Core 1 / Adapter 2 / Entry 3) stacked
with green downward import-direction arrows and the §1.2 Law as
caption. Hard to grok the architecture-as-code concept in prose;
trivial visually.
- public/sama-metrics.{svg,png} (1200x600) — horizontal bar chart of
measured workingSetFit for n=8 datapoints (tdd.md 80.00% highlighted
green as SAMA-disciplined; cli/cli, fd, lazygit, eza, ripgrep, dive,
bat as non-SAMA baseline colored by language). Mean 60.68%, range
27pp, stddev 10.13pp legend.
Each discipline page (/sama/sorted, /sama/architecture, /sama/modeled,
/sama/atomic) gains a Stripe-style older-version banner prepended to
its body via samaSlugHandler — a blockquote callout pointing readers
to /sama/v2 as the formal/normative version.
Six new static routes added to d21_app.ts (3 SVG + 3 PNG) mirroring
the existing /og.{svg,png} pattern.
Anti-fudge: URLs unchanged; v1 content preserved verbatim (just
relocated under an hr); og.png NOT touched (just shipped at v=3,
social caches still warming); no §4 verifier logic changed.
367/367 tests pass. /sama/v2/verify continues to report 7/7 ✓.
Co-Authored-By: Claude Opus 4.7 <[email protected]>
8 files changed · +242 −39
public/sama-hero.png
+0
−0
public/sama-hero.svg
+24
−0
| @@ -0,0 +1,24 @@ | ||
| 1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630"> | |
| 2 | + <rect width="1200" height="630" fill="#0a0a0a"/> | |
| 3 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 4 | + <text x="80" y="240" font-size="180" font-weight="600" fill="#e8e8e8" letter-spacing="-2">SAMA</text> | |
| 5 | + <text x="80" y="320" font-size="36" fill="#8a8a8a">Architecture as code, for code AI writes.</text> | |
| 6 | + </g> | |
| 7 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace" font-size="40" font-weight="600"> | |
| 8 | + <text x="80" y="460" fill="#e8e8e8">Sorted</text> | |
| 9 | + <text x="240" y="460" fill="#5a5a5a">·</text> | |
| 10 | + <text x="275" y="460" fill="#e8e8e8">Architecture</text> | |
| 11 | + <text x="565" y="460" fill="#5a5a5a">·</text> | |
| 12 | + <text x="600" y="460" fill="#e8e8e8">Modeled</text> | |
| 13 | + <text x="800" y="460" fill="#5a5a5a">·</text> | |
| 14 | + <text x="835" y="460" fill="#e8e8e8">Atomic</text> | |
| 15 | + </g> | |
| 16 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace" font-size="22"> | |
| 17 | + <text x="80" y="540" fill="#7ec77e">7/7 ✓ live</text> | |
| 18 | + <text x="220" y="540" fill="#5a5a5a">·</text> | |
| 19 | + <text x="245" y="540" fill="#a0a0a0">§5 metrics measured</text> | |
| 20 | + <text x="510" y="540" fill="#5a5a5a">·</text> | |
| 21 | + <text x="535" y="540" fill="#a0a0a0">n=8 cross-repo datapoints</text> | |
| 22 | + </g> | |
| 23 | + <text x="80" y="590" font-family="ui-sans-serif, system-ui, sans-serif" font-size="22" fill="#5a5a5a">https://tdd.md/sama</text> | |
| 24 | +</svg> | |
public/sama-layers.png
+0
−0
public/sama-layers.svg
+58
−0
| @@ -0,0 +1,58 @@ | ||
| 1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600" width="1200" height="600"> | |
| 2 | + <rect width="1200" height="600" fill="#0a0a0a"/> | |
| 3 | + | |
| 4 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 5 | + <text x="80" y="55" font-size="28" font-weight="600" fill="#e8e8e8">The four canonical layers</text> | |
| 6 | + <text x="80" y="85" font-size="18" fill="#8a8a8a">SAMA v2 §1.1 — frozen; no profile may add, remove, renumber, or rename them.</text> | |
| 7 | + </g> | |
| 8 | + | |
| 9 | + <!-- Layer 3: Entry (top) --> | |
| 10 | + <rect x="200" y="115" width="800" height="78" fill="#1a1a1a" stroke="#3a3a3a" stroke-width="1.5" rx="6"/> | |
| 11 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 12 | + <text x="225" y="148" font-size="26" font-weight="600" fill="#e8e8e8">Layer 3 · Entry</text> | |
| 13 | + <text x="225" y="178" font-size="17" fill="#8a8a8a">main · CLI handler · HTTP route · UI mount · job entry</text> | |
| 14 | + <text x="975" y="158" font-size="14" fill="#5a5a5a" text-anchor="end">may import 0, 1, 2</text> | |
| 15 | + </g> | |
| 16 | + | |
| 17 | + <!-- Arrow 3→2 --> | |
| 18 | + <line x1="600" y1="200" x2="600" y2="220" stroke="#4a8a4a" stroke-width="2"/> | |
| 19 | + <polygon points="595,217 605,217 600,228" fill="#4a8a4a"/> | |
| 20 | + | |
| 21 | + <!-- Layer 2: Adapter --> | |
| 22 | + <rect x="200" y="232" width="800" height="78" fill="#1a1a1a" stroke="#3a3a3a" stroke-width="1.5" rx="6"/> | |
| 23 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 24 | + <text x="225" y="265" font-size="26" font-weight="600" fill="#e8e8e8">Layer 2 · Adapter</text> | |
| 25 | + <text x="225" y="295" font-size="17" fill="#8a8a8a">the boundary — DB · network · filesystem · framework bindings · external input parsed here</text> | |
| 26 | + <text x="975" y="275" font-size="14" fill="#5a5a5a" text-anchor="end">may import 0, 1</text> | |
| 27 | + </g> | |
| 28 | + | |
| 29 | + <!-- Arrow 2→1 --> | |
| 30 | + <line x1="600" y1="317" x2="600" y2="337" stroke="#4a8a4a" stroke-width="2"/> | |
| 31 | + <polygon points="595,334 605,334 600,345" fill="#4a8a4a"/> | |
| 32 | + | |
| 33 | + <!-- Layer 1: Core --> | |
| 34 | + <rect x="200" y="349" width="800" height="78" fill="#1a1a1a" stroke="#3a3a3a" stroke-width="1.5" rx="6"/> | |
| 35 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 36 | + <text x="225" y="382" font-size="26" font-weight="600" fill="#e8e8e8">Layer 1 · Core</text> | |
| 37 | + <text x="225" y="412" font-size="17" fill="#8a8a8a">domain logic · decisions · pure render — no network, disk, clock, framework</text> | |
| 38 | + <text x="975" y="392" font-size="14" fill="#5a5a5a" text-anchor="end">may import 0</text> | |
| 39 | + </g> | |
| 40 | + | |
| 41 | + <!-- Arrow 1→0 --> | |
| 42 | + <line x1="600" y1="434" x2="600" y2="454" stroke="#4a8a4a" stroke-width="2"/> | |
| 43 | + <polygon points="595,451 605,451 600,462" fill="#4a8a4a"/> | |
| 44 | + | |
| 45 | + <!-- Layer 0: Pure (bottom) --> | |
| 46 | + <rect x="200" y="466" width="800" height="78" fill="#1a1a1a" stroke="#3a3a3a" stroke-width="1.5" rx="6"/> | |
| 47 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 48 | + <text x="225" y="499" font-size="26" font-weight="600" fill="#e8e8e8">Layer 0 · Pure</text> | |
| 49 | + <text x="225" y="529" font-size="17" fill="#8a8a8a">types · constants · pure functions · domain models — no I/O, no side effects</text> | |
| 50 | + <text x="975" y="509" font-size="14" fill="#5a5a5a" text-anchor="end">imports nothing above</text> | |
| 51 | + </g> | |
| 52 | + | |
| 53 | + <!-- Caption: the Law --> | |
| 54 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 55 | + <text x="80" y="578" font-size="15" fill="#7ec77e">§1.2 the Law:</text> | |
| 56 | + <text x="220" y="578" font-size="15" fill="#a0a0a0">imports always point to a strictly lower layer number — never upward, never sideways, never cyclic.</text> | |
| 57 | + </g> | |
| 58 | +</svg> | |
public/sama-metrics.png
+0
−0
public/sama-metrics.svg
+86
−0
| @@ -0,0 +1,86 @@ | ||
| 1 | +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 600" width="1200" height="600"> | |
| 2 | + <rect width="1200" height="600" fill="#0a0a0a"/> | |
| 3 | + | |
| 4 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace"> | |
| 5 | + <text x="80" y="50" font-size="26" font-weight="600" fill="#e8e8e8">§5 workingSetFit · measured · n=8 cross-repo datapoints</text> | |
| 6 | + <text x="80" y="78" font-size="16" fill="#8a8a8a">share of source files in the [50, 500] LOC working-set band · same bounds, same emitter, same source-of-truth.</text> | |
| 7 | + </g> | |
| 8 | + | |
| 9 | + <!-- Chart geometry: | |
| 10 | + label column x: 80..280 (200 wide, right-aligned text at x=275) | |
| 11 | + bar area x: 290..1090 (800 wide = 100%) | |
| 12 | + value column x: 1100..1180 (right-aligned text at x=1175) | |
| 13 | + rows: 8 rows, y=120..540 (52px per row) --> | |
| 14 | + | |
| 15 | + <!-- Background grid lines at 25%, 50%, 75% --> | |
| 16 | + <line x1="490" y1="100" x2="490" y2="555" stroke="#1f1f1f" stroke-width="1"/> | |
| 17 | + <line x1="690" y1="100" x2="690" y2="555" stroke="#1f1f1f" stroke-width="1"/> | |
| 18 | + <line x1="890" y1="100" x2="890" y2="555" stroke="#1f1f1f" stroke-width="1"/> | |
| 19 | + <line x1="1090" y1="100" x2="1090" y2="555" stroke="#1f1f1f" stroke-width="1"/> | |
| 20 | + <!-- Baseline at 0% --> | |
| 21 | + <line x1="290" y1="100" x2="290" y2="555" stroke="#3a3a3a" stroke-width="1.5"/> | |
| 22 | + | |
| 23 | + <!-- x-axis labels --> | |
| 24 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace" font-size="13" fill="#6a6a6a" text-anchor="middle"> | |
| 25 | + <text x="290" y="572">0%</text> | |
| 26 | + <text x="490" y="572">25%</text> | |
| 27 | + <text x="690" y="572">50%</text> | |
| 28 | + <text x="890" y="572">75%</text> | |
| 29 | + <text x="1090" y="572">100%</text> | |
| 30 | + </g> | |
| 31 | + | |
| 32 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace" font-size="18"> | |
| 33 | + | |
| 34 | + <!-- Row 1: tdd.md 80.00% (SAMA-disciplined - highlighted) --> | |
| 35 | + <text x="275" y="139" text-anchor="end" fill="#7ec77e" font-weight="600">tdd.md</text> | |
| 36 | + <rect x="290" y="121" width="640" height="26" fill="#7ec77e"/> | |
| 37 | + <text x="1175" y="139" text-anchor="end" fill="#7ec77e" font-weight="600">80.00%</text> | |
| 38 | + <text x="1175" y="155" text-anchor="end" font-size="11" fill="#7ec77e">SAMA-disciplined</text> | |
| 39 | + | |
| 40 | + <!-- Row 2: cli/cli (gh) Go 73.59% --> | |
| 41 | + <text x="275" y="190" text-anchor="end" fill="#c8c8c8">cli/cli (gh)</text> | |
| 42 | + <rect x="290" y="174" width="589" height="22" fill="#6a8db5"/> | |
| 43 | + <text x="1175" y="190" text-anchor="end" fill="#c8c8c8">73.59%</text> | |
| 44 | + | |
| 45 | + <!-- Row 3: sharkdp/fd Rust 69.57% --> | |
| 46 | + <text x="275" y="238" text-anchor="end" fill="#c8c8c8">sharkdp/fd</text> | |
| 47 | + <rect x="290" y="222" width="557" height="22" fill="#a07a5a"/> | |
| 48 | + <text x="1175" y="238" text-anchor="end" fill="#c8c8c8">69.57%</text> | |
| 49 | + | |
| 50 | + <!-- Row 4: lazygit Go 67.38% --> | |
| 51 | + <text x="275" y="286" text-anchor="end" fill="#c8c8c8">lazygit</text> | |
| 52 | + <rect x="290" y="270" width="539" height="22" fill="#6a8db5"/> | |
| 53 | + <text x="1175" y="286" text-anchor="end" fill="#c8c8c8">67.38%</text> | |
| 54 | + | |
| 55 | + <!-- Row 5: eza Rust 61.76% --> | |
| 56 | + <text x="275" y="334" text-anchor="end" fill="#c8c8c8">eza</text> | |
| 57 | + <rect x="290" y="318" width="494" height="22" fill="#a07a5a"/> | |
| 58 | + <text x="1175" y="334" text-anchor="end" fill="#c8c8c8">61.76%</text> | |
| 59 | + | |
| 60 | + <!-- Row 6: ripgrep Rust 54.00% --> | |
| 61 | + <text x="275" y="382" text-anchor="end" fill="#c8c8c8">ripgrep</text> | |
| 62 | + <rect x="290" y="366" width="432" height="22" fill="#a07a5a"/> | |
| 63 | + <text x="1175" y="382" text-anchor="end" fill="#c8c8c8">54.00%</text> | |
| 64 | + | |
| 65 | + <!-- Row 7: dive Go 52.17% --> | |
| 66 | + <text x="275" y="430" text-anchor="end" fill="#c8c8c8">dive</text> | |
| 67 | + <rect x="290" y="414" width="417" height="22" fill="#6a8db5"/> | |
| 68 | + <text x="1175" y="430" text-anchor="end" fill="#c8c8c8">52.17%</text> | |
| 69 | + | |
| 70 | + <!-- Row 8: bat Rust 46.27% --> | |
| 71 | + <text x="275" y="478" text-anchor="end" fill="#c8c8c8">sharkdp/bat</text> | |
| 72 | + <rect x="290" y="462" width="370" height="22" fill="#a07a5a"/> | |
| 73 | + <text x="1175" y="478" text-anchor="end" fill="#c8c8c8">46.27%</text> | |
| 74 | + </g> | |
| 75 | + | |
| 76 | + <!-- Legend strip --> | |
| 77 | + <g font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, monospace" font-size="13" fill="#8a8a8a"> | |
| 78 | + <rect x="80" y="520" width="14" height="14" fill="#7ec77e"/> | |
| 79 | + <text x="100" y="532">SAMA-disciplined (n=1)</text> | |
| 80 | + <rect x="290" y="520" width="14" height="14" fill="#6a8db5"/> | |
| 81 | + <text x="310" y="532">Go (n=3)</text> | |
| 82 | + <rect x="420" y="520" width="14" height="14" fill="#a07a5a"/> | |
| 83 | + <text x="440" y="532">Rust (n=4)</text> | |
| 84 | + <text x="560" y="532" fill="#6a6a6a">· non-SAMA baseline: mean 60.68%, stddev 10.13pp, range 27.32pp</text> | |
| 85 | + </g> | |
| 86 | +</svg> | |
src/d21_app.ts
+19
−0
| @@ -231,6 +231,25 @@ ${url("https://tdd.md/leaderboard", "0.7")} | ||
| 231 | 231 | }, |
| 232 | 232 | }), |
| 233 | 233 | |
| 234 | + "/sama-hero.svg": new Response(Bun.file("./public/sama-hero.svg"), { | |
| 235 | + headers: { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" }, | |
| 236 | + }), | |
| 237 | + "/sama-hero.png": new Response(Bun.file("./public/sama-hero.png"), { | |
| 238 | + headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=3600" }, | |
| 239 | + }), | |
| 240 | + "/sama-layers.svg": new Response(Bun.file("./public/sama-layers.svg"), { | |
| 241 | + headers: { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" }, | |
| 242 | + }), | |
| 243 | + "/sama-layers.png": new Response(Bun.file("./public/sama-layers.png"), { | |
| 244 | + headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=3600" }, | |
| 245 | + }), | |
| 246 | + "/sama-metrics.svg": new Response(Bun.file("./public/sama-metrics.svg"), { | |
| 247 | + headers: { "Content-Type": "image/svg+xml", "Cache-Control": "public, max-age=3600" }, | |
| 248 | + }), | |
| 249 | + "/sama-metrics.png": new Response(Bun.file("./public/sama-metrics.png"), { | |
| 250 | + headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=3600" }, | |
| 251 | + }), | |
| 252 | + | |
| 234 | 253 | "/games": htmlResponse(GAMES_INDEX_HTML), |
| 235 | 254 | |
| 236 | 255 | "/blog": async () => { |
src/d21_handlers_sama.ts
+55
−39
| @@ -380,48 +380,67 @@ export const samaVerifyHandler = async (req: { url: string }): Promise<Response> | ||
| 380 | 380 | |
| 381 | 381 | const SAMA_LANDING_MD = `# SAMA |
| 382 | 382 | |
| 383 | -> **Sorted, Architecture, Modeled, Atomic.** Four properties of a codebase that an AI agent can navigate, change, and verify without drift. The acronym is the rule set; each letter has a one-paragraph definition and a verification you can run. | |
| 383 | +> **Architecture as code, for code AI writes.** A formal v2 specification, a deterministic verifier that runs against this very repo on every deploy, and an n=8 cross-repo measurement baseline. The v1 practitioner pages are preserved below. | |
| 384 | 384 | |
| 385 | -This is the file-naming and module-organisation convention this site is built on, shared across two other projects in my workspace. It exists to give an AI agent **one obvious place** for every change — and one mechanical check for every layer rule. | |
| 385 | + | |
| 386 | 386 | |
| 387 | -## the four disciplines | |
| 387 | +## the v2 specification (current) | |
| 388 | 388 | |
| 389 | -| letter | discipline | one-line rule | | |
| 390 | -|---|---|---| | |
| 391 | -%ROWS% | |
| 389 | +The formal, normative spec — frozen core + profile mechanism, written so a deterministic verifier in any language can ingest it — lives at **[/sama/v2](/sama/v2)** (v2.0 draft). Four canonical layers, one import law, a binary §4 conformance gate, and §5 core metrics for cross-repo empirical measurement. The TypeScript verifier that implements §4 is itself checked by the verifier; the website is the spec is the verifier is the test suite. | |
| 392 | 390 | |
| 393 | -## reading order | |
| 391 | +The verifier at **[/sama/v2/verify](/sama/v2/verify)** runs the seven §4 conformance checks against this very repository's source on every deploy. Right now it reports **✓ conforms · 7/7 checks pass**. | |
| 394 | 392 | |
| 395 | -If you're new to this: | |
| 396 | -1. Start with **[Sorted](/sama/sorted)** — it has the verification grep that everything else is built around. | |
| 397 | -2. Then **[Architecture](/sama/architecture)** — what each layer prefix means. | |
| 398 | -3. Then **[Modeled](/sama/modeled)** — where types and tests live. | |
| 399 | -4. Then **[Atomic](/sama/atomic)** — the split rule that keeps the rest honest as the codebase grows. | |
| 393 | + | |
| 400 | 394 | |
| 401 | -Each page is short, opinionated, and ends with the common mistakes you'll see if the discipline lapses. | |
| 395 | +## the empirical chain | |
| 396 | + | |
| 397 | +Compliance is binary. The interesting question is the *delta* — what changes when the rules are followed, measured across multiple repos. §5 of the spec defines five language-neutral core metrics so the delta can be measured rather than asserted. The polyglot \`workingSetFit\` emitter has now been run against eight projects at pinned commit SHAs: | |
| 402 | 398 | |
| 403 | -## the v2 specification (draft) | |
| 399 | + | |
| 404 | 400 | |
| 405 | -The four discipline pages above are the practitioner-facing version. The formal, normative version — frozen core + profile mechanism, written so a deterministic verifier in any language can ingest it — lives at **[/sama/v2](/sama/v2)** (draft for v2.0). That doc defines the four canonical layers (Pure / Core / Adapter / Entry), the single import law, the binary conformance gate, and the SAMA-independent core metrics for cross-repo empirical measurement. | |
| 401 | +- **[Seven measured workingSetFit datapoints — what the baseline distribution actually looks like](/blog/sama-v2-workingset-cross-repo-baseline)** — n=2 → n=7 across mature compiled-language CLI tools. Range 27pp, mean 60.68%, sample stddev 10.13pp. | |
| 402 | +- **[Pointing SAMA v2 at \`dive\`](/blog/sama-v2-go-project-dive)** · **[Pointing SAMA v2 at \`ripgrep\`](/blog/sama-v2-rust-project-ripgrep)** — two non-SAMA mature CLI codebases audited and scored on two §5 axes (workingSetFit + graphDepth) at pinned SHAs. | |
| 403 | +- **[The §5 metrics emitter post](/blog/sama-v2-metrics-emitter)** — why measurement matters more than compliance, with the original metric build-out. | |
| 404 | +- **[The three v2.1 dialects at /sama/v2 §6.A](/sama/v2#6a-v21-dialects-provisional)** — provisional extensions (directory-layout, inline-tests, declarative-exemption) that the Rust + Go audits surfaced. | |
| 405 | + | |
| 406 | +**tdd.md** (the only SAMA-disciplined repo measured to date) lands at **80.00%** — 6.4pp above the top of the non-SAMA baseline. Suggestive, but n=1 vs n=7 is far from a SAMA-worth-following claim. §6 of the spec is explicit that promotion requires cross-repo deltas across multiple SAMA-disciplined repos. | |
| 406 | 407 | |
| 407 | 408 | ## drop into your agent |
| 408 | 409 | |
| 409 | -For agents that load skills from \`~/.claude/skills/\` (Claude Code, obra/superpowers, etc.), grab the SKILL.md version: | |
| 410 | +For agents that load skills from \`~/.claude/skills/\` (Claude Code, obra/superpowers, etc.): | |
| 410 | 411 | |
| 411 | 412 | \`\`\`bash |
| 412 | 413 | mkdir -p ~/.claude/skills |
| 413 | 414 | curl -fsSL https://tdd.md/skills/sama.md -o ~/.claude/skills/sama.md |
| 414 | 415 | \`\`\` |
| 415 | 416 | |
| 416 | -The skill is the same content as the four pages here, written in obra/superpowers SKILL.md format with frontmatter, an iron-rule statement, and a verification checklist your agent can run before merging. **[Read it formatted →](/sama/skill)** · **[Raw markdown →](/skills/sama.md)** | |
| 417 | +The skill captures the load-bearing parts of the v2 spec in obra/superpowers SKILL.md format with frontmatter, an iron-rule statement, and a verification checklist your agent runs before merging. **[Read it formatted →](/sama/skill)** · **[Raw markdown →](/skills/sama.md)** | |
| 418 | + | |
| 419 | +--- | |
| 417 | 420 | |
| 418 | -## verify any public repo | |
| 421 | +## v1 practitioner pages (preserved) | |
| 419 | 422 | |
| 420 | -Want to know whether a repo follows SAMA without reading its source? Paste the \`owner/name\` and tdd.md will run all four checks against the default branch — *Sorted* (the import-direction grep), *Architecture* (known layer prefixes), *Modeled* (sibling tests), *Atomic* (700-line + placeholder-test detection). Pass/fail per discipline, with violation lists. **[verify a repo on the web →](/sama/verify)** · or try it on this site: [\`syntaxai/tdd.md\`](/sama/verify?repo=syntaxai/tdd.md). | |
| 423 | +The four discipline pages below are the v1 practitioner-facing version of SAMA. They predate the v2 specification and remain accessible as background reading; the formal, normative version of every rule lives in [/sama/v2](/sama/v2). The v1 pages frame the same four properties in less spec-flavoured prose, with one-page-per-discipline depth and a working CI grep at the bottom of *Sorted*. | |
| 421 | 424 | |
| 422 | -## the \`sama\` CLI | |
| 425 | +### the four disciplines | |
| 423 | 426 | |
| 424 | -The web verifier is good for ad-hoc checks. For CI and pre-commit, install the standalone CLI — same checks, no network needed for local repos: | |
| 427 | +| letter | discipline | one-line rule | | |
| 428 | +|---|---|---| | |
| 429 | +%ROWS% | |
| 430 | + | |
| 431 | +### reading order | |
| 432 | + | |
| 433 | +If you're new to this: | |
| 434 | +1. Start with **[Sorted](/sama/sorted)** — it has the verification grep that everything else is built around. | |
| 435 | +2. Then **[Architecture](/sama/architecture)** — what each layer prefix means. | |
| 436 | +3. Then **[Modeled](/sama/modeled)** — where types and tests live. | |
| 437 | +4. Then **[Atomic](/sama/atomic)** — the split rule that keeps the rest honest as the codebase grows. | |
| 438 | + | |
| 439 | +Each page is short, opinionated, and ends with the common mistakes you'll see if the discipline lapses. | |
| 440 | + | |
| 441 | +### the v1 \`sama\` CLI | |
| 442 | + | |
| 443 | +For local CI and pre-commit hooks. The v1 CLI verifies the original four disciplines; the v2 verifier with the seven §4 conformance checks runs on the web at [/sama/v2/verify](/sama/v2/verify). | |
| 425 | 444 | |
| 426 | 445 | \`\`\`bash |
| 427 | 446 | mkdir -p ~/.local/bin |
| @@ -440,17 +459,15 @@ sama verify-repo owner/name # verify a public GitHub repo (no token) | ||
| 440 | 459 | |
| 441 | 460 | Exit codes: \`0\` on pass, \`1\` if any check fails, \`2\` on error. The CLI is a single Bun bundle (~14 KB). [Bun](https://bun.sh) needs to be on \`PATH\`. |
| 442 | 461 | |
| 443 | -### pre-commit hook | |
| 444 | - | |
| 445 | -Add to \`.git/hooks/pre-commit\` (or via \`husky\`, \`pre-commit\`, \`lefthook\`): | |
| 462 | +#### pre-commit hook | |
| 446 | 463 | |
| 447 | 464 | \`\`\`bash |
| 448 | 465 | #!/usr/bin/env bash |
| 449 | -# Block commits that violate SAMA layer/atomic/modeled rules. | |
| 466 | +# Block commits that violate the v1 SAMA disciplines. | |
| 450 | 467 | exec sama check |
| 451 | 468 | \`\`\` |
| 452 | 469 | |
| 453 | -### GitHub Action | |
| 470 | +#### GitHub Action | |
| 454 | 471 | |
| 455 | 472 | \`\`\`yaml |
| 456 | 473 | # .github/workflows/sama.yml |
| @@ -468,18 +485,14 @@ jobs: | ||
| 468 | 485 | ./sama check |
| 469 | 486 | \`\`\` |
| 470 | 487 | |
| 471 | -If the rule lives in a hook or an action that fails the build, the harness can't talk the agent out of it. That is the whole point of the [corpus post](/blog/agentic-coding-corpus-three-patterns) and the next step from the [from-rules-to-checks](/blog/from-rules-to-checks) wrap-up. | |
| 488 | +### the case behind it (v1 essays) | |
| 472 | 489 | |
| 473 | -## the case behind it | |
| 490 | +Two long-form pieces that argued *why* SAMA was shaped this way, written before the v2 spec drafted: | |
| 474 | 491 | |
| 475 | -Two long-form pieces that argue *why* SAMA is shaped this way: | |
| 492 | +- [**The Claude Code harness postmortem read through TDD + SAMA**](/blog/claude-code-harness-postmortem) — ThePaSch's r/ClaudeAI audit read against the iron law and the verification grep. | |
| 493 | +- [**Three patterns ten threads converge on**](/blog/agentic-coding-corpus-three-patterns) — a six-month corpus of r/ClaudeAI, r/ClaudeCode, r/AgentsOfAI failure-mode threads with per-pattern mitigation tables. | |
| 476 | 494 | |
| 477 | -- [**The Claude Code harness postmortem read through TDD + SAMA**](/blog/claude-code-harness-postmortem) — ThePaSch's r/ClaudeAI audit (40+ hidden reminders, 5 gag-order sites, 158 prompt versions in 11 days) read against the iron law and the verification grep. *The harness is loud; the diff doesn't have to be.* | |
| 478 | -- [**Three patterns ten threads converge on**](/blog/agentic-coding-corpus-three-patterns) — a six-month corpus of r/ClaudeAI, r/ClaudeCode, r/AgentsOfAI failure-mode threads. Per-pattern mitigation tables map each thread to the SAMA / iron-law rule that catches or prevents it. | |
| 479 | - | |
| 480 | -If you're reading these for the first time, the order to take them is harness postmortem → corpus → back here. | |
| 481 | - | |
| 482 | -## why these four together | |
| 495 | +### why these four together | |
| 483 | 496 | |
| 484 | 497 | Each property fixes a different failure mode: |
| 485 | 498 | |
| @@ -488,9 +501,7 @@ Each property fixes a different failure mode: | ||
| 488 | 501 | - *Modeled* fails when types and tests scatter → siblings are mandatory. |
| 489 | 502 | - *Atomic* fails when files swell → the ~700-line split keeps atoms small. |
| 490 | 503 | |
| 491 | -Pick one and you'll claw back some clarity. Pick all four and the codebase becomes the kind an agent can be left alone with — there is exactly one right place for any change, and a one-line shell command that proves the layer rule. | |
| 492 | - | |
| 493 | -The blog post [*Red, tokens, atoms*](/blog/three-constraints-agentic-coding) argues SAMA also compounds with TDD and Claude Code's token-saving discipline; the four properties on this page are the *Atomic* / *Modeled* / *Architecture* / *Sorted* halves of that story. | |
| 504 | +The blog post [*Red, tokens, atoms*](/blog/three-constraints-agentic-coding) argues SAMA also compounds with TDD and Claude Code's token-saving discipline. | |
| 494 | 505 | |
| 495 | 506 | [← back to tdd.md](/) · [the blog](/blog) · [the guides](/guides) |
| 496 | 507 | `; |
| @@ -526,7 +537,12 @@ export const samaSlugHandler = async (req: { params: { slug: string } }): Promis | ||
| 526 | 537 | const html = await renderNotFound(`/sama/${slug}`); |
| 527 | 538 | return htmlResponse(html, 404); |
| 528 | 539 | } |
| 529 | - const md = await file.text(); | |
| 540 | + const rawMd = await file.text(); | |
| 541 | + // Stripe-style "older version" banner — prepended to every v1 | |
| 542 | + // discipline page so a reader landing directly on /sama/sorted etc. | |
| 543 | + // sees the v2-spec pointer before the v1 prose. | |
| 544 | + const v1Banner = `> **You are reading the v1 practitioner version of this property.** The formal, normative v2 specification — frozen core, profile mechanism, deterministic verifier, and §5 cross-repo measurement chain — lives at **[/sama/v2](/sama/v2)**. This page is preserved as background reading.\n\n`; | |
| 545 | + const md = v1Banner + rawMd; | |
| 530 | 546 | const html = await renderDocsPage({ |
| 531 | 547 | title: `SAMA · ${entry.letter} — ${entry.title} — tdd.md`, |
| 532 | 548 | description: entry.description, |