642e917f8f318d3eeab008f059ef93c876e697b6 diff --git a/content/blog/agentic-coding-corpus-three-patterns.md b/content/blog/agentic-coding-corpus-three-patterns.md index b7a0159bf8f33e3a224217100a9abab730e9c0c3..47e9c339a0ab79311c4bfee2a1a1b56694cc809b 100644 --- a/content/blog/agentic-coding-corpus-three-patterns.md +++ b/content/blog/agentic-coding-corpus-three-patterns.md @@ -106,7 +106,7 @@ The prescribed solution: > *"Lock them in a box (can ONLY modify explicitly assigned files), Make them work in phases (CONTRACT → STUB → TEST → IMPLEMENT) with validation gates they cannot skip."* -Read that solution against [SAMA's atomic property](/sama/atomic) (one responsibility per module, ~700-line split) and the [iron law](/sama/skill) (failing test first, verify before next phase). DUMBAI's CONTRACT → STUB → TEST → IMPLEMENT is the iron-law cycle expressed in agent-task vocabulary; "lock them to assigned files" is *Atomic* + *Sorted* enforced as a sandbox boundary. +Read that solution against [SAMA's atomic property](/sama/discipline/atomic) (one responsibility per module, ~700-line split) and the [iron law](/sama/skill) (failing test first, verify before next phase). DUMBAI's CONTRACT → STUB → TEST → IMPLEMENT is the iron-law cycle expressed in agent-task vocabulary; "lock them to assigned files" is *Atomic* + *Sorted* enforced as a sandbox boundary. **The top comment on `1rug14a`** lands at the same answer from a different angle: diff --git a/content/blog/sama-empirical-modeled-green.md b/content/blog/sama-empirical-modeled-green.md index c37d8ef5dabcf83f3770e5c45db1271fd06826bb..0a57984a144d22693319ff9904b076ffb03d05d9 100644 --- a/content/blog/sama-empirical-modeled-green.md +++ b/content/blog/sama-empirical-modeled-green.md @@ -137,7 +137,7 @@ The blog post is the receipt; the URL is the proof. - Live dogfood: - The four checks documented: - [Sorted](/sama/sorted) · [Architecture](/sama/architecture) · - [Modeled](/sama/modeled) · [Atomic](/sama/atomic) + [Sorted](/sama/discipline/sorted) · [Architecture](/sama/discipline/architecture) · + [Modeled](/sama/discipline/modeled) · [Atomic](/sama/discipline/atomic) - Previous post in this series: [When the verifier said "split this"](/blog/sama-empirical-c21-split) diff --git a/content/blog/sama-v2-on-ramp-gap.md b/content/blog/sama-v2-on-ramp-gap.md index f3576ae3413d3ee42a11c6e77a659f4184714f34..73a59efe52e4d7acdd3dad630bcdca1fd7aa39e1 100644 --- a/content/blog/sama-v2-on-ramp-gap.md +++ b/content/blog/sama-v2-on-ramp-gap.md @@ -25,7 +25,7 @@ Specifically: - **The `/goal` workflow rule** lives at `/var/home/scri/.claude/projects/-var-home-scri-Documents-tdd-md/memory/feedback_goal_authoring_workflow.md` — an agent-private memory file. A human contributor literally cannot read it. The rule that determines how every PR on this site is authored is preserved for the agent and invisible to everyone else. -- **The layer-prefix convention** (`a31_*` = Layer 0, `b32_*` = Layer 1, `c14_*` = Layer 2, `d21_*` = Layer 3) is partially documented in [/sama/v2 §1.1](/sama/v2#11-layers), partially in the `/sama/sorted` discipline page, and partially encoded in the source code itself. A new contributor has to triangulate. +- **The layer-prefix convention** (`a31_*` = Layer 0, `b32_*` = Layer 1, `c14_*` = Layer 2, `d21_*` = Layer 3) is partially documented in [/sama/v2 §1.1](/sama/v2#11-layers), partially in the `/sama/discipline/sorted` discipline page, and partially encoded in the source code itself. A new contributor has to triangulate. - **The branch-PR-deploy flow** (branch → `gh pr create` → merge → push p620 → `flatpak-spawn --host scripts/p620/deploy-tdd-md.sh` → live-verify) is in *zero* blog posts as a standalone procedure. It's mentioned as a constraint inside individual `/goal` bodies, embedded in PR commit messages, scattered across the recent /goals archive. Never collected. diff --git a/content/home.md b/content/home.md index 6a7cfcccab056d1a0477349fac7ef78af50e6149..a9c3e1f3833afe86034e71b67ad767746c648093 100644 --- a/content/home.md +++ b/content/home.md @@ -20,10 +20,10 @@ The five §5 core metrics — **graphDepth · fanByLayer · boundaryRatio · wor ## The four pillars -- **[S — Sorted.](/sama/sorted)** Lexicographic file order equals import direction. The dependency graph is the file tree. -- **[A — Architecture.](/sama/architecture)** Every file's prefix maps to one layer with explicit allowed/forbidden contents. No rogue files. -- **[M — Modeled.](/sama/modeled)** Every behavior file has a sibling test. Every external input is parsed at the boundary, never cast. -- **[A — Atomic.](/sama/atomic)** Files cap at ~700 lines. Split per domain, never via barrel re-exports. +- **[S — Sorted.](/sama/discipline/sorted)** Lexicographic file order equals import direction. The dependency graph is the file tree. +- **[A — Architecture.](/sama/discipline/architecture)** Every file's prefix maps to one layer with explicit allowed/forbidden contents. No rogue files. +- **[M — Modeled.](/sama/discipline/modeled)** Every behavior file has a sibling test. Every external input is parsed at the boundary, never cast. +- **[A — Atomic.](/sama/discipline/atomic)** Files cap at ~700 lines. Split per domain, never via barrel re-exports. ## SAMA in your agent-coding stack diff --git a/content/sama/architecture.md b/content/sama/architecture.md index 15297d24c93ab1f845931811c5e7e3bec1f7292f..133819d301a6dfe7ff27eb75bbf498c567e329fd 100644 --- a/content/sama/architecture.md +++ b/content/sama/architecture.md @@ -72,4 +72,4 @@ When you spot a violation, the fix is always the same shape: split the file alon --- -[← S — Sorted](/sama/sorted) · [/sama](/sama) · [next: M — Modeled →](/sama/modeled) +[← S — Sorted](/sama/discipline/sorted) · [/sama](/sama) · [next: M — Modeled →](/sama/discipline/modeled) diff --git a/content/sama/atomic.md b/content/sama/atomic.md index fa9cb7537ab818aef821036deb4228895fa8d819..db36cb950408862a45df5e4e8716093d6e76d807 100644 --- a/content/sama/atomic.md +++ b/content/sama/atomic.md @@ -97,4 +97,4 @@ It also keeps **token cost** bounded. An agent given "edit `c51_render_reports.t --- -[← M — Modeled](/sama/modeled) · [/sama](/sama) · [back to the four properties](/sama) +[← M — Modeled](/sama/discipline/modeled) · [/sama](/sama) · [back to the four properties](/sama) diff --git a/content/sama/modeled.md b/content/sama/modeled.md index cba167dc2cd9f6cabd0694e7e90b90a57776b1e8..dba87de9eabc3cd526fa6ebb48b70c7490be1380 100644 --- a/content/sama/modeled.md +++ b/content/sama/modeled.md @@ -102,4 +102,4 @@ The fix: move the type to `c31_project_config.ts`, write a `parseProjectConfig` --- -[← A — Architecture](/sama/architecture) · [/sama](/sama) · [next: A — Atomic →](/sama/atomic) +[← A — Architecture](/sama/discipline/architecture) · [/sama](/sama) · [next: A — Atomic →](/sama/discipline/atomic) diff --git a/content/sama/sorted.md b/content/sama/sorted.md index f6bdc362842c148f4e451b0e89149ab6340332b2..79441418fcee87a038292c94310b399cb6135329 100644 --- a/content/sama/sorted.md +++ b/content/sama/sorted.md @@ -73,4 +73,4 @@ A file tree where: --- -[← /sama](/sama) · [next: A — Architecture →](/sama/architecture) +[← /sama](/sama) · [next: A — Architecture →](/sama/discipline/architecture) diff --git a/src/a31_blog.ts b/src/a31_blog.ts index 4266e6b62c73c7883003ff961ee77e57ae244d0c..502736f598287d9e9cbfe99d8d1d417208f89433 100644 --- a/src/a31_blog.ts +++ b/src/a31_blog.ts @@ -15,7 +15,7 @@ export const ALL_POSTS: BlogEntry[] = [ { slug: "sama-v2-on-ramp-gap", title: "Every artifact has a URL. The on-ramp doesn't.", - description: "Open tdd.md in a fresh browser. Count the entry points — /sama/v2 (spec), /sama/v2/verify (live), /blog (narrative), /goals (contracts), /GIT/tdd.md (source), /sitemap.xml (every URL). Six artifacts, each auditable, each its own blog post subject. Now ask the question the site exists to answer for a reader: 'how do I contribute?' There is no answer. No CONTRIBUTING.md, no /contributing URL, no canonical on-ramp anywhere. Forty PRs of preaching auditability — and the most basic on-ramp test failing the entire time. This post is the drama. Walks the artifact-vs-path table: every OUTPUT lives in a canonical place (✓), every authoring rule lives somewhere a contributor wouldn't think to look (the /goal workflow rule is in an agent-private memory file that humans literally cannot read; the layer convention is in /sama/v2 §1.1 + /sama/sorted + the source tree; the branch-PR-deploy flow is scattered across PR commit messages and individual /goal bodies; the image convention lives in memory only; the Containerfile gotcha lives in one PR's commit message). Concrete 10-step thought experiment of what a new contributor adding their first blog post would actually have to do — ends in 'give up, open an issue saying how do I contribute'. Frames this as a SAMA v2 self-violation parallel to /blog/sama-v2-goal-chain-gap one level up: that post fixed the /goal in the chain; this post argues the workflow itself is the next missing artifact, same structural failure applied to procedure instead of contract. Sketches the fix: one CONTRIBUTING.md, two surfaces (./CONTRIBUTING.md at repo root + /contributing on the site, same markdown source), with three load-bearing properties — single source of truth (links don't restate), dogfooded (its own creation is /goal-driven at /goals/contributing-md), drift-detectable (a test that grep-checks CONTRIBUTING.md contains only references not restatements). The /goal that drives the implementation follows this post per the plan-execute-postmortem pattern.", + description: "Open tdd.md in a fresh browser. Count the entry points — /sama/v2 (spec), /sama/v2/verify (live), /blog (narrative), /goals (contracts), /GIT/tdd.md (source), /sitemap.xml (every URL). Six artifacts, each auditable, each its own blog post subject. Now ask the question the site exists to answer for a reader: 'how do I contribute?' There is no answer. No CONTRIBUTING.md, no /contributing URL, no canonical on-ramp anywhere. Forty PRs of preaching auditability — and the most basic on-ramp test failing the entire time. This post is the drama. Walks the artifact-vs-path table: every OUTPUT lives in a canonical place (✓), every authoring rule lives somewhere a contributor wouldn't think to look (the /goal workflow rule is in an agent-private memory file that humans literally cannot read; the layer convention is in /sama/v2 §1.1 + /sama/discipline/sorted + the source tree; the branch-PR-deploy flow is scattered across PR commit messages and individual /goal bodies; the image convention lives in memory only; the Containerfile gotcha lives in one PR's commit message). Concrete 10-step thought experiment of what a new contributor adding their first blog post would actually have to do — ends in 'give up, open an issue saying how do I contribute'. Frames this as a SAMA v2 self-violation parallel to /blog/sama-v2-goal-chain-gap one level up: that post fixed the /goal in the chain; this post argues the workflow itself is the next missing artifact, same structural failure applied to procedure instead of contract. Sketches the fix: one CONTRIBUTING.md, two surfaces (./CONTRIBUTING.md at repo root + /contributing on the site, same markdown source), with three load-bearing properties — single source of truth (links don't restate), dogfooded (its own creation is /goal-driven at /goals/contributing-md), drift-detectable (a test that grep-checks CONTRIBUTING.md contains only references not restatements). The /goal that drives the implementation follows this post per the plan-execute-postmortem pattern.", date: "2026-05-25", }, { diff --git a/src/b32_edit_resolve.test.ts b/src/b32_edit_resolve.test.ts index 159c5ac7c33293d78ecd97c736042598d8a133c0..228e2014693d037d0d9fa0631a8dce387873a66c 100644 --- a/src/b32_edit_resolve.test.ts +++ b/src/b32_edit_resolve.test.ts @@ -4,7 +4,7 @@ import { resolveEdit } from "./b32_edit_resolve.ts"; test("resolves an existing sama page", () => { const r = resolveEdit("sama", "sorted"); expect(r).not.toBeNull(); - expect(r?.pageUrl).toBe("/sama/sorted"); + expect(r?.pageUrl).toBe("/sama/discipline/sorted"); expect(r?.filePath).toBe("content/sama/sorted.md"); expect(r?.title).toMatch(/Sorted/); }); diff --git a/src/b32_edit_resolve.ts b/src/b32_edit_resolve.ts index e980666f2f22735d25161f7dffefaa93c9f2b16a..5c0c7ec8cc85ddebf29fe6ded9037f48a8808e52 100644 --- a/src/b32_edit_resolve.ts +++ b/src/b32_edit_resolve.ts @@ -58,10 +58,16 @@ export const resolveEdit = (section: string, slug: string): ResolvedEdit | null if (!SAFE_SLUG.test(slug)) return null; const title = lookupTitle(section, slug); if (title === null) return null; + // /sama discipline pages live under /sama/discipline/ as of + // PR #53; other sections keep the /
/ shape. + const pageUrl = + section === "sama" && slug !== "skill" && slug !== "v2" + ? `/sama/discipline/${slug}` + : `/${section}/${slug}`; return { section, slug, - pageUrl: `/${section}/${slug}`, + pageUrl, filePath: `content/${section}/${slug}.md`, title, }; diff --git a/src/b32_sama_discipline_url_redirect.test.ts b/src/b32_sama_discipline_url_redirect.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d7e5b82de79d914ac6df658a9176cc5e329829c9 --- /dev/null +++ b/src/b32_sama_discipline_url_redirect.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from "bun:test"; +import { rewriteOldSamaDisciplineUrl } from "./b32_sama_discipline_url_redirect.ts"; + +describe("rewriteOldSamaDisciplineUrl", () => { + test("rewrites /sama/sorted", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/sorted")).toBe( + "/sama/discipline/sorted", + ); + }); + + test("rewrites /sama/architecture", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/architecture")).toBe( + "/sama/discipline/architecture", + ); + }); + + test("rewrites /sama/modeled", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/modeled")).toBe( + "/sama/discipline/modeled", + ); + }); + + test("rewrites /sama/atomic", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/atomic")).toBe( + "/sama/discipline/atomic", + ); + }); + + test("returns null for new-form URLs", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/discipline/sorted")).toBe(null); + }); + + test("returns null for /sama landing", () => { + expect(rewriteOldSamaDisciplineUrl("/sama")).toBe(null); + }); + + test("returns null for /sama/v2 and its subpaths", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/v2")).toBe(null); + expect(rewriteOldSamaDisciplineUrl("/sama/v2/verify")).toBe(null); + }); + + test("returns null for /sama/skill", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/skill")).toBe(null); + }); + + test("returns null for invented discipline slugs", () => { + expect(rewriteOldSamaDisciplineUrl("/sama/syntropic")).toBe(null); + }); + + test("returns null for non-/sama paths", () => { + expect(rewriteOldSamaDisciplineUrl("/blog/foo")).toBe(null); + expect(rewriteOldSamaDisciplineUrl("/")).toBe(null); + expect(rewriteOldSamaDisciplineUrl("")).toBe(null); + }); +}); diff --git a/src/b32_sama_discipline_url_redirect.ts b/src/b32_sama_discipline_url_redirect.ts new file mode 100644 index 0000000000000000000000000000000000000000..42f8a68a820b86579c63bd8efd63de39fd703dc2 --- /dev/null +++ b/src/b32_sama_discipline_url_redirect.ts @@ -0,0 +1,15 @@ +// b32 — Layer 1 pure helper: rewrite the legacy /sama/ +// URL shape to /sama/discipline/. Second instance of the +// b32__url_redirect pattern established by b32_git_url_redirect.ts +// — same shape, same sibling-test structure, same Layer-3 wrapper +// in d21_handlers_fallback. The four discipline slugs are spec-frozen +// (§0 SAMA = Sorted, Architecture, Modeled, Atomic), so the regex +// hard-codes the enumeration rather than accepting any kebab slug. + +const OLD_PATTERN = /^\/sama\/(sorted|architecture|modeled|atomic)$/; + +export const rewriteOldSamaDisciplineUrl = (pathname: string): string | null => { + const m = OLD_PATTERN.exec(pathname); + if (m === null) return null; + return `/sama/discipline/${m[1]}`; +}; diff --git a/src/b51_render_docs_layout.ts b/src/b51_render_docs_layout.ts index d8243d1c065b3d961896509e85f6f9b50b1f98ef..29725932f28a6a4896d29c722fc94cdd348d19fc 100644 --- a/src/b51_render_docs_layout.ts +++ b/src/b51_render_docs_layout.ts @@ -18,7 +18,7 @@ import { } from "./b51_render_layout.ts"; export interface DocsPageOptions extends Omit { - // The route path the user is on, e.g. "/sama/sorted". Used to + // The route path the user is on, e.g. "/sama/discipline/sorted". Used to // compute prev/next. pathForDocs: string; // Optional override of which file the "edit on GitHub" link diff --git a/src/d21_app.ts b/src/d21_app.ts index 85c3defae187af85376bdfac7ff3fe4b3121da3a..1d8012f6765819b8cf78d0ced5025b74959ef82d 100644 --- a/src/d21_app.ts +++ b/src/d21_app.ts @@ -202,7 +202,7 @@ export const createApp = (port: number) => Bun.serve({ lastmod: p.date, })); const samaUrls: SitemapUrl[] = ALL_SAMA.map((d) => ({ - loc: `${SITE_BASE_URL}/sama/${d.slug}`, + loc: `${SITE_BASE_URL}/sama/discipline/${d.slug}`, })); const guideUrls: SitemapUrl[] = ALL_GUIDES.map((g) => ({ loc: `${SITE_BASE_URL}/guides/${g.slug}`, @@ -415,7 +415,7 @@ ${rows} "/sama": samaLandingHandler, - "/sama/:slug": samaSlugHandler, + "/sama/discipline/:slug": samaSlugHandler, "/goals": goalsLandingHandler, diff --git a/src/d21_handlers_fallback.ts b/src/d21_handlers_fallback.ts index 0246303a543f01c58569f47f8399b194652cbfb8..d17959d25d57cf70f4f7ae38e6df51be16cdd401 100644 --- a/src/d21_handlers_fallback.ts +++ b/src/d21_handlers_fallback.ts @@ -23,6 +23,7 @@ import { repoBrowseHandler, } from "./d21_handlers_repo_browse.ts"; import { rewriteOldGitUrl } from "./b32_git_url_redirect.ts"; +import { rewriteOldSamaDisciplineUrl } from "./b32_sama_discipline_url_redirect.ts"; const isGitProtocol = (pathname: string, search: URLSearchParams): boolean => { if (pathname.includes(".git/") || pathname.endsWith(".git")) return true; @@ -138,6 +139,22 @@ export const appFetch = async (req: Request): Promise => { }); } + // Legacy /sama/ URLs permanent-redirect to the new + // /sama/discipline/ namespace. Same pattern-as-redirect shape + // as the /GIT/ block above — pure Layer-1 transform + Layer-3 + // Response wrapper. Hypothesis-test instance of the pattern's + // reusability (see /blog/sama-v2-git-url-refactor-postmortem). + const newSamaPath = rewriteOldSamaDisciplineUrl(url.pathname); + if (newSamaPath !== null) { + return new Response(null, { + status: 301, + headers: { + Location: newSamaPath, + "Cache-Control": "public, max-age=86400", + }, + }); + } + // SAMA-native repo browse at /GIT/:repo/{tree,blob,raw}/:ref/. // The wildcard path needs more flexibility than Bun's :param routes // give us (no slashes), so we match in the fallback fetch instead. diff --git a/src/d21_handlers_sama.ts b/src/d21_handlers_sama.ts index aa2383f9b1746ae781a45d8bf128aa42a77182c3..8aac01efc4f92a766e87f7ebba29dbc99424df65 100644 --- a/src/d21_handlers_sama.ts +++ b/src/d21_handlers_sama.ts @@ -431,10 +431,10 @@ The four discipline pages below are the v1 practitioner-facing version of SAMA. ### reading order If you're new to this: -1. Start with **[Sorted](/sama/sorted)** — it has the verification grep that everything else is built around. -2. Then **[Architecture](/sama/architecture)** — what each layer prefix means. -3. Then **[Modeled](/sama/modeled)** — where types and tests live. -4. Then **[Atomic](/sama/atomic)** — the split rule that keeps the rest honest as the codebase grows. +1. Start with **[Sorted](/sama/discipline/sorted)** — it has the verification grep that everything else is built around. +2. Then **[Architecture](/sama/discipline/architecture)** — what each layer prefix means. +3. Then **[Modeled](/sama/discipline/modeled)** — where types and tests live. +4. Then **[Atomic](/sama/discipline/atomic)** — the split rule that keeps the rest honest as the codebase grows. Each page is short, opinionated, and ends with the common mistakes you'll see if the discipline lapses. @@ -539,7 +539,7 @@ export const samaSlugHandler = async (req: { params: { slug: string } }): Promis } const rawMd = await file.text(); // Stripe-style "older version" banner — prepended to every v1 - // discipline page so a reader landing directly on /sama/sorted etc. + // discipline page so a reader landing directly on /sama/discipline/sorted etc. // sees the v2-spec pointer before the v1 prose. 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`; const md = v1Banner + rawMd;