From eeb5d7cd2291a470ce01adcbef85548a03c71a1c Mon Sep 17 00:00:00 2001 From: SUaDtL Date: Mon, 22 Jun 2026 03:11:09 -0400 Subject: [PATCH 1/3] feat(site): bespoke landing, Feature Forge surfacing, anti-slop copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #94 --farm artifact built a working docs site but it read as a scaffold: stock Starlight chrome, four generic cards, and a landing that printed `/ca:sprint --farm` while explaining neither --farm nor the Feature Forge anywhere on the site. This polish pass makes the site earn a first-time visitor's understanding within one screen: - A bespoke landing with a CSS-only animated gate-catch terminal that shows a gate blocking a banned crypto primitive, then clearing — the actual conversion mechanic, not an assertion. Honors prefers-reduced-motion with a static full transcript; the transcript is real DOM text. - Feature Forge surfaced as the differentiator: a landing showcase, a Concepts section with the two-axis labelling model, and preview badges driven by a site-side generator allowlist (no plugins/ca change). --farm is now linked to its explanation. - Three hand-authored SVG diagrams (lane-flow, two-axis, gate model) and custom callouts across the inner pages. - Copy de-slopped: em-dashes cut from 7/16/10 to 0/0/0 in body prose, plus removal of templated phrasing and LLM-tell vocabulary. - Functional: logo/favicon/social wired, base-safe internal links, Pagefind verified in dist. Reviewed via multi-agent build with adversarial verify (visual design, link/base-path, copy anti-slop); 4 blocking findings caught and fixed. Crypto-compliance gate passed (demo transcript text, no operational crypto). Site suite green: typecheck clean, 104 tests, build 77 pages. CHANGELOG: Public docs site redesigned — bespoke landing with a live gate-catch demo, Feature Forge showcase + preview badges, and a copy anti-slop pass. Ref: .codearbiter/specs/docs-site-polish.md Claude-Session: https://claude.ai/code/session_016NVNTNAPKXzWD8bmmjVHP7 --- site/astro.config.mjs | 23 +- site/public/diagrams/gate-model.svg | 173 +++++++++ site/public/diagrams/lane-flow.svg | 103 +++++ site/public/diagrams/two-axis-model.svg | 183 +++++++++ site/public/favicon.svg | 38 ++ site/scripts/generator/forge-status.ts | 51 +++ site/scripts/generator/generate.ts | 9 + site/scripts/generator/render-command-page.ts | 25 +- site/scripts/generator/types.ts | 3 + site/src/assets/diagrams/gate-model.svg | 173 +++++++++ site/src/assets/diagrams/lane-flow.svg | 103 +++++ site/src/assets/diagrams/two-axis-model.svg | 183 +++++++++ site/src/assets/favicon.svg | 38 ++ site/src/assets/logo.svg | 36 ++ site/src/components/ForgeShowcase.astro | 70 ++++ site/src/components/GateCatchTerminal.astro | 85 +++++ site/src/content/docs/concepts.md | 117 ++++-- site/src/content/docs/index.mdx | 87 ++--- site/src/content/docs/overview.md | 98 ++--- site/src/styles/callouts.css | 38 ++ site/src/styles/landing.css | 107 ++++++ site/src/styles/theme.css | 357 +++++++++++++++++- site/test/generator/forge-status.test.ts | 36 ++ site/test/generator/generate-forge.test.ts | 86 +++++ .../render-command-page-forge.test.ts | 69 ++++ site/test/landing/landing-page.test.ts | 262 +++++++++++++ 26 files changed, 2408 insertions(+), 145 deletions(-) create mode 100644 site/public/diagrams/gate-model.svg create mode 100644 site/public/diagrams/lane-flow.svg create mode 100644 site/public/diagrams/two-axis-model.svg create mode 100644 site/public/favicon.svg create mode 100644 site/scripts/generator/forge-status.ts create mode 100644 site/src/assets/diagrams/gate-model.svg create mode 100644 site/src/assets/diagrams/lane-flow.svg create mode 100644 site/src/assets/diagrams/two-axis-model.svg create mode 100644 site/src/assets/favicon.svg create mode 100644 site/src/assets/logo.svg create mode 100644 site/src/components/ForgeShowcase.astro create mode 100644 site/src/components/GateCatchTerminal.astro create mode 100644 site/src/styles/callouts.css create mode 100644 site/src/styles/landing.css create mode 100644 site/test/generator/forge-status.test.ts create mode 100644 site/test/generator/generate-forge.test.ts create mode 100644 site/test/generator/render-command-page-forge.test.ts create mode 100644 site/test/landing/landing-page.test.ts diff --git a/site/astro.config.mjs b/site/astro.config.mjs index a5214713..9b589b60 100644 --- a/site/astro.config.mjs +++ b/site/astro.config.mjs @@ -28,13 +28,34 @@ try { export default defineConfig({ // GitHub Pages project site: served from https://arbiterforge.github.io/codeArbiter/. // `base` also applies in local dev — the dev server serves at /codeArbiter/. + // + // BASE-PATH-SAFE LINK PATTERN for downstream authors: + // - In Starlight MDX/Markdown, use root-relative slugs (no leading slash): + // [Overview](/overview) → Starlight maps slugs through the base automatically. + // - In Astro component href props, use import.meta.env.BASE_URL: + // href={`${import.meta.env.BASE_URL}overview/`} + // - Never hardcode "/codeArbiter/" in href strings. That value desynchs + // when the Astro base is changed and is invisible to linting. site: "https://arbiterforge.github.io", base: "/codeArbiter", integrations: [ starlight({ title: "codeArbiter", description: "Gated software-engineering workflows for Claude Code.", - customCss: ["./src/styles/theme.css"], + logo: { + src: "./src/assets/logo.svg", + replacesTitle: true, + alt: "codeArbiter", + }, + favicon: "/favicon.svg", + customCss: [ + "./src/styles/theme.css", + "./src/styles/callouts.css", + "./src/styles/landing.css", + ], + social: { + github: "https://github.com/arbiterforge/codeArbiter", + }, sidebar: [ { label: "Start here", diff --git a/site/public/diagrams/gate-model.svg b/site/public/diagrams/gate-model.svg new file mode 100644 index 00000000..43933eb3 --- /dev/null +++ b/site/public/diagrams/gate-model.svg @@ -0,0 +1,173 @@ + + Gate model: soft gate (surfaces and waits) vs hard gate (true stop) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOFT GATE + surfaces · waits + + + + + + + + + + + + SOFT + + + + + + + + + + + + Decision surfaced. + Which design do you want? + waiting for user + + + + user acts + + + + proceeds + + + surfaces a choice; does not block forever + work resumes once the user decides + design · trade-off · merge + + + + + HARD GATE + true stop + + + + + + + + + + + + + + HARD + + + + + + + + + + + Stopped. + Not auto-decided. + User must act to continue. + + + + never auto-decided + + + security · auth/crypto · irreversible ops + unresolved decisions · gate bypass · merge to main + rare by design — frequent trips signal a thin spec + diff --git a/site/public/diagrams/lane-flow.svg b/site/public/diagrams/lane-flow.svg new file mode 100644 index 00000000..87c0ea79 --- /dev/null +++ b/site/public/diagrams/lane-flow.svg @@ -0,0 +1,103 @@ + + Lane flow: command, route, gate, ship + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /ca:fix + COMMAND + + + + + + + orchestrator + ROUTE + + + + + + + + + + + + + GATE + + tests · review · security + + + + + + + + + + + + version control + SHIP + + + + + + PR + + + never a direct write to main + diff --git a/site/public/diagrams/two-axis-model.svg b/site/public/diagrams/two-axis-model.svg new file mode 100644 index 00000000..117daf44 --- /dev/null +++ b/site/public/diagrams/two-axis-model.svg @@ -0,0 +1,183 @@ + + Two-axis labeling model: SemVer (whole payload) vs Feature Forge (per-feature preview) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SEMVER AXIS + + + whole payload + + + + + v2.4.0 + + + + + + + v2.5.0 + + + + + + + + v2.6.0 + + + version bump = every user gets the change + + + + + + GOVERNED BY + semantic versioning contract + MAJOR · MINOR · PATCH + + + + stable + + + all features + + applies to the whole plugin at once + + + + + FEATURE FORGE AXIS + + + per-feature preview + + + + + preview + opt-in · dormant + + + + evidence + + + + stable + on by default + + + + + + + + FORGE + + + a feature can be preview inside a stable release + + + + + + GOVERNED BY + real-world evidence from users who opt in + + + ships dormant · no surprise behavior · your data promotes it + + + + --farm · preview · opt-in today + diff --git a/site/public/favicon.svg b/site/public/favicon.svg new file mode 100644 index 00000000..5e6ab708 --- /dev/null +++ b/site/public/favicon.svg @@ -0,0 +1,38 @@ + + codeArbiter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/scripts/generator/forge-status.ts b/site/scripts/generator/forge-status.ts new file mode 100644 index 00000000..b9b3a14f --- /dev/null +++ b/site/scripts/generator/forge-status.ts @@ -0,0 +1,51 @@ +/** forge-status.ts — codeArbiter's site-side Feature Forge allowlist. + * + * Marks which commands (or flags on commands) carry Feature Forge preview + * status. This is the single source of truth for preview decoration on the + * generated reference pages. It lives here — not in plugin frontmatter — to + * hold the strict site-only boundary (AC-14, docs-site-polish spec). + * + * [NEEDS-TRIAGE] Allowlist drift: this list is hand-maintained. If a feature is + * promoted to stable, this entry must be removed. A future /ca:doctor check or + * CI reconciliation step (noted in the spec's triage section) should automate + * the drift detection. + */ + +/** A whole command is in preview. */ +export interface PreviewCommandStatus { + kind: "preview-command"; +} + +/** One named flag on a command is in preview; the command itself is stable. */ +export interface PreviewFlagStatus { + kind: "preview-flag"; + /** The CLI flag that is preview, e.g. "--farm". */ + flag: string; +} + +export type ForgeStatus = PreviewCommandStatus | PreviewFlagStatus; + +/** + * Map of command base-names (the slug portion, e.g. "prune" not "/ca:prune") + * to their forge status. + * + * - prune: the entire command is a Feature Forge preview. + * - sprint: the --farm flag is a Feature Forge preview; the base command is stable. + */ +export const PREVIEW_COMMANDS: Record = { + prune: { kind: "preview-command" }, + sprint: { kind: "preview-flag", flag: "--farm" }, +}; + +/** + * Look up the forge status for a command by its base name. + * + * @param commandName - The command slug (e.g. "prune", "sprint", "commit"). + * May be passed with or without a "/ca:" prefix; the prefix is stripped. + * @returns The ForgeStatus for that command, or null if the command is stable. + */ +export function getCommandForgeStatus(commandName: string): ForgeStatus | null { + // Strip the "/ca:" prefix if present, then normalise to lower-case. + const base = commandName.replace(/^\/ca:/, "").toLowerCase(); + return PREVIEW_COMMANDS[base] ?? null; +} diff --git a/site/scripts/generator/generate.ts b/site/scripts/generator/generate.ts index ae82606a..6825e380 100644 --- a/site/scripts/generator/generate.ts +++ b/site/scripts/generator/generate.ts @@ -8,6 +8,7 @@ import { renderAgentPage } from "./render-agent-page"; import { renderCommandPage } from "./render-command-page"; import { renderSkillPage } from "./render-skill-page"; import { buildIndex } from "./build-index"; +import { getCommandForgeStatus } from "./forge-status"; import type { GenerateResult, PageInput, RenderedPage, SourceType } from "./types"; /** Output subdirectory for each source type. */ @@ -73,11 +74,19 @@ export function generate( const pages: RenderedPage[] = parsed.map(({ source, doc }, i) => { const name = names[i]; + // Derive forge status for command pages only. The slug at this point is the + // raw file-basename (e.g. "prune", "sprint") — use that as the lookup key + // so the allowlist stays filename-stable regardless of display name. + const forgeStatus = + source.type === "command" + ? getCommandForgeStatus(slugs[i]) + : null; const input: PageInput = { name, description: doc.fields.description ?? "", model: doc.fields.model, tools: doc.fields.tools, + forgeStatus, }; return { type: source.type, diff --git a/site/scripts/generator/render-command-page.ts b/site/scripts/generator/render-command-page.ts index 41c94c08..ca57cbcc 100644 --- a/site/scripts/generator/render-command-page.ts +++ b/site/scripts/generator/render-command-page.ts @@ -1,3 +1,4 @@ +/** render-command-page.ts — codeArbiter's command reference page renderer. */ import type { PageInput } from "./types"; /** @@ -6,16 +7,36 @@ import type { PageInput } from "./types"; * Includes a `title:` frontmatter line, an `# ` heading, and the * description. Commands have no model or tools, so the page renders neither a * `Model tier` nor a `Tools` section. + * + * When `forgeStatus` is provided on the input the renderer decorates the page: + * - `preview-command` — a preview badge (``) + * is injected immediately after the H1 heading. + * - `preview-flag` — a preview callout (`
`) + * naming the preview flag is injected immediately after the H1 heading. + * - null / undefined — no decoration; stable command renders unchanged. */ export function renderCommandPage(input: PageInput): string { - const { name, description } = input; + const { name, description, forgeStatus } = input; const desc = description ?? ""; + let decoration = ""; + if (forgeStatus?.kind === "preview-command") { + decoration = + '\n\npreview'; + } else if (forgeStatus?.kind === "preview-flag") { + const flag = forgeStatus.flag; + decoration = + `\n\n
` + + `

Feature Forge — preview

` + + `

${flag} — preview. This flag is part of the Feature Forge and ships dormant by default. Promotion to stable is driven by real-world evidence.

` + + `
`; + } + return `--- title: ${name} --- -# ${name} +# ${name}${decoration} ${desc} `; diff --git a/site/scripts/generator/types.ts b/site/scripts/generator/types.ts index 5540598f..4b52369e 100644 --- a/site/scripts/generator/types.ts +++ b/site/scripts/generator/types.ts @@ -1,5 +1,6 @@ // Shared type contract for the codeArbiter reference generator. // Authored as the harness API; individual modules implement against it. +import type { ForgeStatus } from "./forge-status"; /** The three kinds of plugin source documents the generator reads. */ export type SourceType = "command" | "skill" | "agent"; @@ -32,6 +33,8 @@ export interface PageInput { description?: string; model?: string; tools?: string; + /** Feature Forge preview status for this page, or null/undefined for stable features. */ + forgeStatus?: ForgeStatus | null; } /** A rendered reference page ready to write to disk. */ diff --git a/site/src/assets/diagrams/gate-model.svg b/site/src/assets/diagrams/gate-model.svg new file mode 100644 index 00000000..43933eb3 --- /dev/null +++ b/site/src/assets/diagrams/gate-model.svg @@ -0,0 +1,173 @@ + + Gate model: soft gate (surfaces and waits) vs hard gate (true stop) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SOFT GATE + surfaces · waits + + + + + + + + + + + + SOFT + + + + + + + + + + + + Decision surfaced. + Which design do you want? + waiting for user + + + + user acts + + + + proceeds + + + surfaces a choice; does not block forever + work resumes once the user decides + design · trade-off · merge + + + + + HARD GATE + true stop + + + + + + + + + + + + + + HARD + + + + + + + + + + + Stopped. + Not auto-decided. + User must act to continue. + + + + never auto-decided + + + security · auth/crypto · irreversible ops + unresolved decisions · gate bypass · merge to main + rare by design — frequent trips signal a thin spec + diff --git a/site/src/assets/diagrams/lane-flow.svg b/site/src/assets/diagrams/lane-flow.svg new file mode 100644 index 00000000..87c0ea79 --- /dev/null +++ b/site/src/assets/diagrams/lane-flow.svg @@ -0,0 +1,103 @@ + + Lane flow: command, route, gate, ship + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /ca:fix + COMMAND + + + + + + + orchestrator + ROUTE + + + + + + + + + + + + + GATE + + tests · review · security + + + + + + + + + + + + version control + SHIP + + + + + + PR + + + never a direct write to main + diff --git a/site/src/assets/diagrams/two-axis-model.svg b/site/src/assets/diagrams/two-axis-model.svg new file mode 100644 index 00000000..117daf44 --- /dev/null +++ b/site/src/assets/diagrams/two-axis-model.svg @@ -0,0 +1,183 @@ + + Two-axis labeling model: SemVer (whole payload) vs Feature Forge (per-feature preview) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SEMVER AXIS + + + whole payload + + + + + v2.4.0 + + + + + + + v2.5.0 + + + + + + + + v2.6.0 + + + version bump = every user gets the change + + + + + + GOVERNED BY + semantic versioning contract + MAJOR · MINOR · PATCH + + + + stable + + + all features + + applies to the whole plugin at once + + + + + FEATURE FORGE AXIS + + + per-feature preview + + + + + preview + opt-in · dormant + + + + evidence + + + + stable + on by default + + + + + + + + FORGE + + + a feature can be preview inside a stable release + + + + + + GOVERNED BY + real-world evidence from users who opt in + + + ships dormant · no surprise behavior · your data promotes it + + + + --farm · preview · opt-in today + diff --git a/site/src/assets/favicon.svg b/site/src/assets/favicon.svg new file mode 100644 index 00000000..5e6ab708 --- /dev/null +++ b/site/src/assets/favicon.svg @@ -0,0 +1,38 @@ + + codeArbiter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/src/assets/logo.svg b/site/src/assets/logo.svg new file mode 100644 index 00000000..be892b6c --- /dev/null +++ b/site/src/assets/logo.svg @@ -0,0 +1,36 @@ + + codeArbiter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + codeArbiter + + diff --git a/site/src/components/ForgeShowcase.astro b/site/src/components/ForgeShowcase.astro new file mode 100644 index 00000000..bf0ae61b --- /dev/null +++ b/site/src/components/ForgeShowcase.astro @@ -0,0 +1,70 @@ +--- +/** + * ForgeShowcase.astro — Feature Forge landing showcase (AC-12, AC-15). + * + * Explains the two-axis model (SemVer = whole payload version, Feature Forge = + * per-feature preview/maturity), the opt-in / dormant / evidence-promoted + * discipline, and grounds --farm in context so it is no longer a free-floating + * term (AC-15). + * + * The two-axis-model.svg is embedded by path per the diagram-assets contract. + * Class names follow the design-system contract (theme.css). + */ + +const baseUrl = import.meta.env.BASE_URL; +--- + +
+

The Feature Forge

+ +

+ codeArbiter uses two independent versioning axes. SemVer answers one question: + did the whole plugin payload change? The Feature Forge answers a different one: + is a specific capability ready for general use? +

+ +
+ Two-axis model: SemVer left column tracks the whole plugin payload; Feature Forge right column tracks per-feature preview maturity with a promotion arc from preview to stable. +
SemVer versions the payload. The Feature Forge versions each feature's maturity.
+
+ +

+ Preview features follow three rules. They are opt-in: you + enable them by flag, never by default. They ship dormant: + off unless you turn them on, so a new plugin version never changes your + workflow without consent. Promotion to stable requires + real-world evidence: field usage, not a calendar date or a + version bump. +

+ +
+

Preview feature

+

+ --farm is a preview flag on /ca:sprint. It routes + work to cheap parallel workers for autonomous, multi-step execution. Because + it is a preview feature, it is opt-in and off by default. +

+

+ Try it: /ca:sprint --farm "build the docs site" +

+

+ Full reference for /ca:sprint +

+
+ +
+

Stable vs preview

+

+ Stable commands (/ca:feature, /ca:fix, + /ca:commit) are on by default and gate-proven. Preview + features carry a preview + badge in the reference and require explicit opt-in. +

+
+
diff --git a/site/src/components/GateCatchTerminal.astro b/site/src/components/GateCatchTerminal.astro new file mode 100644 index 00000000..c77ecd4a --- /dev/null +++ b/site/src/components/GateCatchTerminal.astro @@ -0,0 +1,85 @@ +--- +/** + * GateCatchTerminal.astro — codeArbiter's conversion centerpiece. + * + * Renders an animated gate-catch demo: a commit blocked by a CRITICAL finding, + * then fixed, then green. Animation is CSS-only (no JS player). The transcript + * is real DOM text for screen readers. Under prefers-reduced-motion the + * animation collapses to static and the full transcript is immediately visible. + * + * Design-system contract (from theme.css): + * .ca-terminal, .ca-terminal__titlebar, .ca-terminal__dot, .ca-terminal__title, + * .ca-terminal__body, .ca-terminal__line, .ca-terminal__line--error, + * .ca-terminal__line--pass, .ca-terminal__line--dim, .ca-terminal__prompt + */ +--- + +
+ + +
    + +
  1. + + git commit -m "add payment hash" +
  2. +
  3. + codeArbiter pre-commit · scanning diff... +
  4. +
  5. + H-09 BLOCKED: banned crypto — createHash("md5") on line 42. +
  6. +
  7. + CRITICAL: MD5 is forbidden. Replace with SHA-256 (see security-controls.md). +
  8. +
  9. + Commit rejected. Fix the finding, then commit again. +
  10. + + +
  11. +   +
  12. + + +
  13. + + sed -i 's/createHash("md5")/createHash("sha256")/g' payment.ts +
  14. +
  15. + + git add payment.ts +
  16. +
  17. + + git commit -m "add payment hash (sha-256)" +
  18. +
  19. + codeArbiter pre-commit · scanning diff... +
  20. + + +
  21. + H-09 passed: SHA-256 confirmed. No banned patterns. +
  22. +
  23. + All gates green. Commit accepted. +
  24. +
  25. + [main 3fb950c] add payment hash (sha-256) +
  26. +
+
diff --git a/site/src/content/docs/concepts.md b/site/src/content/docs/concepts.md index ef6dd0cd..91919571 100644 --- a/site/src/content/docs/concepts.md +++ b/site/src/content/docs/concepts.md @@ -1,68 +1,103 @@ --- title: Concepts -description: The core ideas behind codeArbiter — gated lanes, SMARTS, ADRs, checkpoints, and the persona-register split. +description: The core ideas behind codeArbiter — gated lanes, the Feature Forge, SMARTS, ADRs, and checkpoints. --- These are the ideas the lanes are built on. ## The gated-lane model -Every kind of work has a **lane**: a sanctioned path with gates scaled to its -risk. A lane is not a suggestion — it is the only way that kind of change ships. -`/ca:fix` and `/ca:feature` run test-first through the `tdd` gate; `/ca:refactor` -must prove behavioral parity through *unmodified* pre-existing tests; `/ca:chore` -carries lighter gates because prose and dependency bumps don't demand TDD. +Every kind of work has a **lane**: a sanctioned path with gates scaled to its risk. A lane +is not a suggestion. It is the only way that kind of change ships. `/ca:fix` and +`/ca:feature` run test-first through the `tdd` gate. `/ca:refactor` must prove behavioral +parity through *unmodified* pre-existing tests. `/ca:chore` carries lighter gates, because +prose edits and dependency bumps don't demand TDD. -Gates come in two strengths. **Soft gates** surface a decision and wait for the -user. **Hard gates** are true stops that are never auto-decided, even under -autonomous execution: anything in the security controls, auth/crypto/secret -changes, irreversible operations, a gate bypass, an unresolved decision, or a -merge to the default branch. Hard gates are rare by design — when they trip -often, that's a signal the spec was too thin, not an obstacle to grind past. +Gates come in two strengths. **Soft gates** surface a decision and wait for the user. +**Hard gates** are true stops, never auto-decided, even under autonomous execution: +anything in the security controls, auth/crypto/secret changes, irreversible operations, a +gate bypass, an unresolved decision, or a merge to the default branch. Hard gates are rare +by design. When they trip often, that's a signal the spec was too thin, not an obstacle to +grind past. + +
+ Gate model. A soft gate surfaces a decision bubble and waits for the user. A hard gate is a closed cross-bar that is never auto-decided. +
Soft gates surface and wait. Hard gates stop, and only the user can clear them.
+
+ +## The Feature Forge + +SemVer answers one question: did the plugin payload change? A version bump means every user +gets the new payload. It says nothing about whether a given feature inside that payload is +ready to trust by default. + +The **Feature Forge** answers the second question, per feature. A feature can ship in +**preview**: opt-in, dormant, off by default. It rides along in a release without changing +anyone's behavior until they turn it on. Once real-world evidence shows it holds up, it gets +promoted to **stable** and becomes on by default. The version says the whole payload moved; +the forge says which individual features have earned trust. + +That is the two-axis model. Read them together and a release is legible: SemVer governs the +whole payload, the Feature Forge governs each feature's maturity. + +
+ Two-axis labeling model. SemVer governs the whole plugin payload; the Feature Forge governs each feature as preview (opt-in, dormant) or stable (on by default, evidence-promoted). +
Two axes: SemVer for the whole payload, the Feature Forge for per-feature maturity.
+
+ +
+

Preview

+ /ca:sprint --farm is a preview feature. It ships dormant, off until you opt + in, and stays preview until evidence promotes it. The promotion bar is a deliberate owner + decision tracked in the project's decision log, not a switch the plugin flips on its own. +
+ +Promotion is driven by evidence, not by a calendar. A preview feature graduates when its +real-world use shows it is safe to default on, and that judgment is recorded as a tracked +decision rather than assumed. The forge keeps the cost of trying something new low and the +cost of trusting it honest. ## SMARTS -When codeArbiter must weigh options — and especially when `/ca:sprint` decides -"as the user" during an autonomous run — it doesn't pick on vibes. It runs a -structured, multi-lens scoring rubric called **SMARTS** and records the verdict: -the options weighed, the lens scores, the chosen option, and a confidence flag. -Low-confidence calls are exactly what the user reviews afterward. Nothing is -hidden behind autonomy — every auto-decision lands in an append-only sprint log. +When codeArbiter must weigh options, and especially when `/ca:sprint` decides "as the user" +during an autonomous run, it doesn't pick on vibes. It runs a structured, multi-lens scoring +rubric called **SMARTS** and records the verdict: the options weighed, the lens scores, the +chosen option, and a confidence flag. Low-confidence calls are exactly what the user reviews +afterward. Nothing hides behind autonomy. Every auto-decision lands in an append-only sprint +log. ## ADRs and the decision log -Architecturally significant choices are written down as **Architecture Decision -Records** — numbered, dated, and attributed to the user who made them, stored -under the project's decision log. codeArbiter never authors an ADR as its own -judgment; every record carries explicit user attribution. The decision-lifecycle -skill maintains supersede chains (a newer ADR can replace an older one) and can -report decision health — which records are aging, unchallenged, or in conflict. +Architecturally significant choices are written down as **Architecture Decision Records**: +numbered, dated, and attributed to the user who made them, stored under the project's +decision log. codeArbiter never authors an ADR as its own judgment; every record carries +explicit user attribution. The decision-lifecycle skill maintains supersede chains, so a +newer ADR can replace an older one, and it can report decision health: which records are +aging, unchallenged, or in conflict. ## Checkpoints -A **checkpoint** is a periodic, read-only sweep of the whole codebase by the -reviewer fleet. The findings are consolidated, classified by severity, and -triaged into a single dated report — the ones that block the current change are -called out, the rest recorded. Checkpoints are how drift and latent issues get -caught between feature work, without blocking any single change. +A **checkpoint** is a periodic, read-only sweep of the whole codebase by the reviewer fleet. +The findings are consolidated, classified by severity, and triaged into a single dated +report. The ones that block the current change are called out, the rest recorded. +Checkpoints are how drift and latent issues get caught between feature work, without +blocking any single change. ## The persona-register split -codeArbiter speaks with more than one voice, deliberately. The **orchestrator** -persona is terse and decisive — it routes, gates, and states. The **author** and -**reviewer** agents each carry their own focused register and tools, scoped to -their job. Splitting the persona register keeps each role sharp: the orchestrator -isn't trying to also be a backend engineer, and a security reviewer isn't trying -to also be a copywriter. Each agent loads only the context its role needs, which -is also what keeps the standing footprint small. +codeArbiter speaks with more than one voice, deliberately. The **orchestrator** persona is +terse and decisive; it routes, gates, and states. The **author** and **reviewer** agents +each carry their own focused register and tools, scoped to their job. Splitting the persona +register keeps each role sharp. The orchestrator isn't trying to also be a backend engineer, +and a security reviewer isn't trying to also be a copywriter. Each agent loads only the +context its role needs, which is also what keeps the standing footprint small. ## Auditability -Taken together, these concepts make a codeArbiter repository auditable after the -fact: ADRs say *why*, the sprint log says *what was auto-decided and how -confidently*, checkpoints say *what the sweeps found*, and an append-only override -trail records every sanctioned bypass. The governance record can be assembled for -any range of work on demand. +Taken together, these concepts make a codeArbiter repository auditable after the fact. ADRs +say *why*. The sprint log says *what was auto-decided and how confidently*. Checkpoints say +*what the sweeps found*. An append-only override trail records every sanctioned bypass. The +governance record can be assembled for any range of work on demand. For the concrete catalog of what runs in each lane, see the auto-generated [Reference](../reference/). diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index 8cec8a2b..9b4e3e12 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -4,75 +4,62 @@ description: Gated software-engineering workflows for Claude Code. template: splash hero: tagline: | - Turn ad-hoc AI coding into an orchestrated, gated workflow. Every intent - routes to the skill or agent that owns it and clears its gates before it - ships. You hold the gates; the user holds the decisions. + Orchestrate every Claude Code request through a gated lane. + Tests, reviews, and security checks are real stops: not suggestions, + not optional, not bypassed under time pressure. actions: - # Base path is baked in here because hero actions are static frontmatter - # (no access to import.meta.env.BASE_URL). Keep in sync with `base` in astro.config.mjs. - text: Get started - link: /codeArbiter/overview/ + link: ./overview/ icon: right-arrow variant: primary - - text: Concepts - link: /codeArbiter/concepts/ + - text: How it works + link: ./concepts/ icon: open-book - variant: secondary - - text: GitHub - link: https://github.com/arbiterForge/codeArbiter - icon: external variant: minimal --- -import { Card, CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; +import GateCatchTerminal from '../../components/GateCatchTerminal.astro'; +import ForgeShowcase from '../../components/ForgeShowcase.astro'; - - - One persona routes every request to the skill or agent that owns it — it orchestrates, it does not freelance. - - - Tests, reviews, secrets, and crypto are real stops. "It looks good" is not authorization. - - - ADRs, a decision log, dated checkpoints, and an append-only override trail record every call. - - - The command, skill & agent reference is generated from the plugin's own frontmatter — it can't drift. - - +
+

+ A Claude Code plugin. One orchestrator. Gated lanes for every kind of work. +

-## How a request flows +
- - -1. **Command** — you invoke a namespaced slash command: `/ca:feature`, `/ca:fix`, `/ca:commit`. +```bash +/ca:feature "add rate limiting" # brainstorm → plan → TDD → review → PR +/ca:fix "login 500 on empty body" # test-first bug fix, gate before commit +/ca:commit # the only sanctioned path to a commit +``` -2. **Route** — the orchestrator hands it to the one skill or agent that owns that lane. +
+
-3. **Gate** — nothing advances until its gates are green: a failing test, a CRITICAL finding, an unresolved decision are real stops. +
-4. **Ship** — code reaches version control only through the commit gate, and `main` only through a pull request. +## A gate catching a real mistake - +The conversion mechanic is not a description of gates. It is a gate doing its job. -## Start with a lane + -Each kind of work has a sanctioned lane, with gates scaled to its risk. +
-
+
-```bash -/ca:feature "add rate limiting" # brainstorm → plan → TDD → review → PR -/ca:fix "login 500 on empty body" # test-first bug fix -/ca:commit # the only sanctioned path to a commit -/ca:sprint --farm "build the docs site" # autonomous, cheap-worker execution -``` +
+ Lane flow: a command routes to the owning skill, clears its gate pillars, then ships to version control via a pull request. +
Every request flows: command → route → gate → ship. Nothing skips the gate.
+
- - - - - - + diff --git a/site/src/content/docs/overview.md b/site/src/content/docs/overview.md index 746d0f72..6e6a3959 100644 --- a/site/src/content/docs/overview.md +++ b/site/src/content/docs/overview.md @@ -3,64 +3,74 @@ title: Overview description: How codeArbiter orchestrates gated software-engineering workflows in Claude Code. --- -codeArbiter is a Claude Code plugin. When enabled in a repository, it installs a -single orchestrator persona that mediates every request: it **orchestrates; it -does not freelance**. Every user intent flows through a slash command, routes to -the one skill or agent that owns it, and clears its gates before it ships. +codeArbiter is a Claude Code plugin. When you enable it in a repository, it installs a +single orchestrator persona that mediates every request. It orchestrates. It does not +freelance. Every user intent flows through a slash command, routes to the one skill or +agent that owns it, and clears its gates before it ships. ## You hold the gates; the user holds the decisions -This is the organizing principle. codeArbiter is decisive about *process* and -deferential about *choices*. It holds the gates — the test-first rule, the review -chain, the secret and crypto checks, the commit and merge boundaries — and will -not wave work through them. But the decisions those gates surface (which design, -which trade-off, whether to merge) belong to the user. The plugin states; it does -not hedge. It stops; it does not silently reconcile. +This is the organizing principle. codeArbiter is decisive about process and deferential +about choices. It holds the gates: the test-first rule, the review chain, the secret and +crypto checks, the commit and merge boundaries. It will not wave work through them. But +the decisions those gates surface belong to the user. Which design? Which trade-off? +Whether to merge? Those are yours. The plugin states; it does not hedge. It stops; it does +not silently reconcile. ## How a request flows -1. **Command.** The user invokes a namespaced slash command — `/ca:feature`, - `/ca:fix`, `/ca:commit`, and so on. (The plugin is named `ca`; Claude Code - namespaces every command behind it.) -2. **Route.** The orchestrator routes the command to the single skill or agent - that owns that lane — `/ca:fix` to the test-first gate, `/ca:adr` to the - decision-lifecycle skill, and so on. -3. **Dispatch.** The owning skill dispatches the agents the change actually - demands — an author to write code test-first, a reviewer fleet matched to the - diff (security, auth/crypto, dependencies, migrations, coverage). -4. **Gate.** Nothing advances until its gates are green. A failing test, a - CRITICAL security finding, an unresolved decision — these are real stops. -5. **Ship.** Code reaches version control only through the commit gate, and the - default branch only through a pull request — never a direct write. +1. **Command.** You invoke a namespaced slash command: `/ca:feature`, `/ca:fix`, + `/ca:commit`, and so on. (The plugin is named `ca`, and Claude Code namespaces every + command behind it.) +2. **Route.** The orchestrator hands the command to the single skill or agent that owns + that lane. `/ca:fix` goes to the test-first gate, `/ca:adr` to the decision-lifecycle + skill. +3. **Dispatch.** The owning skill calls the agents the change actually demands: an author + to write code test-first, plus a reviewer fleet matched to the diff (security, + auth/crypto, dependencies, migrations, coverage). +4. **Gate.** Nothing advances until its gates are green. A failing test, a CRITICAL + security finding, an unresolved decision: each is a real stop. +5. **Ship.** Code reaches version control only through the commit gate, and the default + branch only through a pull request. Never a direct write. + +
+ Lane flow: a command invocation routes to the owning skill, clears its gate, then ships to version control. +
One lane, five steps: command, route, gate, ship to a PR.
+
+ +
+

Gate

+ A gate is not advice you can wave off. It is the only path that kind of change takes to + ship. When a gate trips, codeArbiter surfaces the decision and waits; it does not invent + an answer and push past. +
## Context minimization -codeArbiter is built so that almost nothing loads until it is needed. The only -always-loaded context is a single file — the orchestrator persona — injected by a -`SessionStart` hook, and only in repositories whose project state opts in. A repo -that hasn't opted in loads nothing at all. +codeArbiter is built so almost nothing loads until it is needed. The only always-loaded +context is one file, the orchestrator persona, injected by a `SessionStart` hook, and only +in repositories whose project state opts in. A repo that hasn't opted in loads nothing at +all. -Everything else — the routing table, the reference map, all of the skill bodies, -all of the agent bodies, the design-quality reference bundle — is paid for on -demand, only when an entry point is invoked, and only for the nodes that entry -point actually reaches. A typical `/ca:fix` touches the persona, the test-first -skill, one author, and maybe one reviewer — not the whole payload. Read-only meta -commands like `/ca:status`, `/ca:btw`, and `/ca:commands` route to no skill at all. +Everything else is paid for on demand: the routing table, the reference map, every skill +body, every agent body, the design-quality reference bundle. A node loads only when an +entry point reaches it. A typical `/ca:fix` touches the persona, the test-first skill, one +author, and maybe one reviewer. It never pulls the whole payload. Read-only meta commands +like `/ca:status`, `/ca:btw`, and `/ca:commands` route to no skill at all. ## The lanes -Work is organized into lanes, each a sanctioned path with gates scaled to its -risk: +Work is organized into lanes. Each is a sanctioned path with gates scaled to its risk. -- **Implementation** — `/ca:feature` (checkpointed), `/ca:sprint` (autonomous), - `/ca:fix`, `/ca:refactor`, `/ca:debug`, `/ca:chore`, `/ca:spike`. -- **Commit & ship** — `/ca:commit`, `/ca:pr`, `/ca:watch`, `/ca:checkpoint`, - `/ca:release`, `/ca:add-dep`. -- **Decisions** — `/ca:adr`, `/ca:adr-status`, `/ca:reconcile`, `/ca:conflict`, +- **Implementation:** `/ca:feature` (checkpointed), `/ca:sprint` (autonomous), `/ca:fix`, + `/ca:refactor`, `/ca:debug`, `/ca:chore`, `/ca:spike`. +- **Commit & ship:** `/ca:commit`, `/ca:pr`, `/ca:watch`, `/ca:checkpoint`, `/ca:release`, + `/ca:add-dep`. +- **Decisions:** `/ca:adr`, `/ca:adr-status`, `/ca:reconcile`, `/ca:conflict`, `/ca:threat-model`. -- **Project & meta** — `/ca:init`, `/ca:status`, `/ca:audit`, `/ca:doctor`, - `/ca:btw`, and more. +- **Project & meta:** `/ca:init`, `/ca:status`, `/ca:audit`, `/ca:doctor`, `/ca:btw`, and + more. See [Concepts](../concepts/) for the ideas behind the lanes, and the -[Reference](../reference/) for the full, auto-generated catalog of commands, -skills, and agents. +[Reference](../reference/) for the full, auto-generated catalog of commands, skills, and +agents. diff --git a/site/src/styles/callouts.css b/site/src/styles/callouts.css new file mode 100644 index 00000000..fde7b63e --- /dev/null +++ b/site/src/styles/callouts.css @@ -0,0 +1,38 @@ +/* codeArbiter docs — callouts.css + Page-scoped refinements for callouts and inline diagrams on the Overview and + Concepts prose pages. The base .ca-callout / .ca-diagram systems and all tokens + live in theme.css; this file only tunes how they sit inside the Starlight prose + column. Do NOT redefine the tokens established in theme.css here. +*/ + +/* Inline code inside a callout should read as code without fighting the tinted + callout background. Lean on the callout's own border token for the chip edge. */ +.ca-callout code { + background: color-mix(in srgb, var(--ca-callout-note-border) 12%, transparent); + border-radius: 4px; + padding: 0.1em 0.35em; + font-size: 0.92em; +} +.ca-callout--gate code { + background: color-mix(in srgb, var(--ca-callout-gate-border) 14%, transparent); +} +.ca-callout--preview code { + background: color-mix(in srgb, var(--ca-callout-preview-border) 14%, transparent); +} + +/* Diagrams embedded mid-prose get room to breathe and never overflow the column. */ +.sl-markdown-content .ca-diagram { + margin-block: 1.75rem; +} +.ca-diagram img, +.ca-diagram svg { + display: block; + width: 100%; + height: auto; +} +.ca-diagram figcaption { + margin-top: 0.6rem; + text-align: center; + font-size: 0.85rem; + color: var(--ca-text-muted, #8b95a2); +} diff --git a/site/src/styles/landing.css b/site/src/styles/landing.css new file mode 100644 index 00000000..0b67f622 --- /dev/null +++ b/site/src/styles/landing.css @@ -0,0 +1,107 @@ +/* codeArbiter docs — landing.css + Bespoke landing page layout: hero, gate-catch terminal section, Feature Forge + showcase. Design tokens and shared component classes live in theme.css. + Build on those; do not redefine tokens here. +*/ + +/* ========================================================= + BESPOKE HERO + Replaces the stock CardGrid. Placed inside the Starlight + splash template body, constrained to a readable measure. + ========================================================= */ + +.ca-hero { + max-width: 56rem; + margin-inline: auto; + padding-block: 1.5rem 0; +} + +.ca-hero__what { + font-size: clamp(1.05rem, 2vw, 1.2rem); + color: var(--ca-text-muted); + font-family: ui-monospace, "Cascadia Code", Consolas, monospace; + letter-spacing: 0.04em; + margin-block-end: 0.5rem; +} + +.ca-hero__first-command { + margin-block: 1.5rem; +} + +.ca-hero__first-command pre, +.ca-hero__first-command .expressive-code { + border: 1px solid color-mix(in srgb, var(--ca-gold) 28%, transparent); + border-radius: 0.5rem; + background: var(--ca-slate-mid); +} + +/* ========================================================= + TERMINAL SECTION + The gate-catch demo sits in a capped-width reading column. + ========================================================= */ + +.ca-terminal-section { + max-width: 56rem; + margin-inline: auto; + margin-block: 2.5rem; +} + +.ca-terminal-section > h2 { + font-size: 1.1rem; + font-weight: 600; + letter-spacing: 0.01em; + color: var(--ca-text-muted); + margin-block-end: 0.75rem; +} + +/* ========================================================= + FORGE SHOWCASE + Separated from the terminal by whitespace; narrower column + to read like editorial prose, not a feature grid. + ========================================================= */ + +.ca-forge { + max-width: 56rem; + margin-inline: auto; + margin-block: 3rem 2rem; + padding-block-start: 2rem; + border-block-start: 1px solid var(--ca-slate-border); +} + +.ca-forge h2 { + font-size: clamp(1.2rem, 2.5vw, 1.6rem); + font-weight: 700; + margin-block-end: 1rem; + color: var(--ca-gold); +} + +.ca-forge p { + max-width: 60ch; +} + +/* ========================================================= + LANE FLOW DIAGRAM + The lane-flow SVG is wide (900px viewBox); let it fill the + prose column and clip gracefully on narrow viewports. + ========================================================= */ + +.ca-laneflow { + max-width: 56rem; + margin-inline: auto; + margin-block: 2rem; +} + +/* ========================================================= + REDUCED-MOTION OVERRIDE (landing-specific) + theme.css handles the ca-terminal__line rule globally; + this block ensures any additional landing-specific motion + (e.g., hover lifts on the hero) collapses cleanly. + ========================================================= */ + +@media (prefers-reduced-motion: reduce) { + .ca-hero, + .ca-terminal-section, + .ca-forge { + transition: none; + } +} diff --git a/site/src/styles/theme.css b/site/src/styles/theme.css index 60c6fced..1a3573e0 100644 --- a/site/src/styles/theme.css +++ b/site/src/styles/theme.css @@ -1,21 +1,118 @@ /* codeArbiter docs — brand theme. Palette lifted from docs/banner.svg: "gate gold" (#e3b341 / #d29922) on a deep slate (#0b0f14). The accent variables retint Starlight site-wide; the - rest is scoped to the splash landing page. */ + rest is scoped to the splash landing page. + + DESIGN SYSTEM CONTRACT — downstream authors build on these tokens and + classes; never edit this file for per-page content. + + Callout classes (use in MDX/Astro via
): + .ca-callout--note informational, slate-blue left border + .ca-callout--warn caution, amber left border + .ca-callout--gate gate-catch highlight, gold left border + tinted bg + .ca-callout--preview Feature Forge preview, ember-orange left border + + Badge class (inline span): + .ca-badge small pill label (use data-kind="preview" for orange) + + Diagram container: + .ca-diagram centers + constrains an SVG figure with accessible border + + Gate-catch terminal: + .ca-terminal animated terminal shell for the gate-catch demo + .ca-terminal__body scrollable output area + .ca-terminal__line one output line (role="listitem") + .ca-terminal__prompt shell prompt prefix + .ca-terminal__error red-tinted blocked-commit line + .ca-terminal__pass green-tinted gate-pass line +*/ :root { - --sl-color-accent-low: #3b2e0a; - --sl-color-accent: #c0871d; + /* ---- Brand palette ---- */ + --ca-gold: #e3b341; + --ca-gold-dim: #d29922; + --ca-gold-low: #3b2e0a; + --ca-slate: #0b0f14; + --ca-slate-mid: #131a23; + --ca-slate-border: #22303f; + --ca-text-muted: #8b95a2; + --ca-ember: #d97757; /* Feature Forge / preview channel */ + --ca-ember-dim: #c0643e; + + /* ---- Callout palette ---- */ + --ca-callout-note-border: #5b8dd9; + --ca-callout-note-bg: rgba(91, 141, 217, 0.07); + --ca-callout-warn-border: #d29922; + --ca-callout-warn-bg: rgba(210, 153, 34, 0.08); + --ca-callout-gate-border: #e3b341; + --ca-callout-gate-bg: rgba(227, 179, 65, 0.09); + --ca-callout-preview-border:#d97757; + --ca-callout-preview-bg: rgba(217, 119, 87, 0.09); + + /* ---- Badge palette ---- */ + --ca-badge-preview-bg: rgba(217, 119, 87, 0.18); + --ca-badge-preview-text: #f0b35a; + --ca-badge-stable-bg: rgba(91, 141, 217, 0.14); + --ca-badge-stable-text: #88b4ea; + + /* ---- Terminal palette ---- */ + --ca-term-bg: #0d1117; + --ca-term-border: color-mix(in srgb, var(--ca-gold) 18%, transparent); + --ca-term-header: #161c24; + --ca-term-text: #c9d1d9; + --ca-term-prompt: var(--ca-gold-dim); + --ca-term-error: #ff7b72; + --ca-term-pass: #3fb950; + --ca-term-dim: #6e7b8b; + + /* ---- Starlight accent overrides ---- */ + --sl-color-accent-low: var(--ca-gold-low); + --sl-color-accent: #c0871d; --sl-color-accent-high: #ecd083; - --ca-gold: #e3b341; - --ca-gold-dim: #d29922; } + :root[data-theme="dark"] { - --sl-color-accent-low: #3b2e0a; - --sl-color-accent: #e3b341; + --sl-color-accent-low: var(--ca-gold-low); + --sl-color-accent: var(--ca-gold); --sl-color-accent-high: #f3e1a6; } +/* ---- Light-mode token overrides ---- + medium-web §7.D — design light and dark together. The :root block above is a + dark palette; every custom-styled component (callouts, badges, the diagram + container, muted prose text) needs a light-base counterpart so Starlight's + light theme does not render dark blobs on a white shell. + + The gate-catch terminal is deliberately NOT relit: a terminal emulator reads + as dark chrome by convention, and its body is internally AA (light text on + #0d1117). It is a self-contained dark surface in both modes, not an + un-adapted leak. */ +:root[data-theme="light"] { + /* Muted prose text must clear WCAG AA (4.5:1) on the light shell. + #586072 ≈ 6.3:1 on white — used by the hero lead and terminal-section + heading (landing.css) and diagram captions. */ + --ca-text-muted: #586072; + + /* Slate surfaces flip to light neutrals so the diagram container and hero + command block read as light cards, not dark cutouts. */ + --ca-slate: #eef1f5; + --ca-slate-mid: #f4f6f9; + --ca-slate-border: #d2d9e2; + + /* Callout tints: same hues, slightly stronger alpha to register on white. + Borders stay saturated; they already pass on a light base. */ + --ca-callout-note-bg: rgba(45, 99, 173, 0.08); + --ca-callout-warn-bg: rgba(166, 116, 16, 0.10); + --ca-callout-gate-bg: rgba(176, 134, 25, 0.11); + --ca-callout-preview-bg: rgba(192, 100, 62, 0.10); + + /* Badge pills: light tint backgrounds with darkened text that clears AA. */ + --ca-badge-preview-bg: rgba(217, 119, 87, 0.14); + --ca-badge-preview-text: #9a4b27; + --ca-badge-stable-bg: rgba(91, 141, 217, 0.13); + --ca-badge-stable-text: #2f5aa0; +} + /* ---- Splash hero: brand glow + tighter vertical rhythm ---- */ .hero { position: relative; @@ -73,3 +170,249 @@ border-block-end: 1px solid var(--sl-color-gray-5); padding-block-end: 0.25rem; } + +/* ========================================================= + CALLOUTS + Usage:
+

Note

+

Body text here.

+
+ ========================================================= */ + +.ca-callout { + border-inline-start: 3px solid var(--ca-callout-note-border); + background: var(--ca-callout-note-bg); + border-radius: 0 0.4rem 0.4rem 0; + padding: 0.75rem 1rem; + margin-block: 1.25rem; +} + +.ca-callout--note { + border-color: var(--ca-callout-note-border); + background: var(--ca-callout-note-bg); +} + +.ca-callout--warn { + border-color: var(--ca-callout-warn-border); + background: var(--ca-callout-warn-bg); +} + +.ca-callout--gate { + border-color: var(--ca-callout-gate-border); + background: var(--ca-callout-gate-bg); +} + +.ca-callout--preview { + border-color: var(--ca-callout-preview-border); + background: var(--ca-callout-preview-bg); +} + +/* Optional label line inside the callout */ +.ca-callout__label { + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + margin-block-end: 0.35rem; + opacity: 0.75; +} + +.ca-callout--note .ca-callout__label { color: var(--ca-callout-note-border); } +.ca-callout--warn .ca-callout__label { color: var(--ca-callout-warn-border); } +.ca-callout--gate .ca-callout__label { color: var(--ca-callout-gate-border); } +.ca-callout--preview .ca-callout__label { color: var(--ca-callout-preview-border); } + +/* Remove margin from last child inside callout */ +.ca-callout > *:last-child { + margin-block-end: 0; +} + +/* ========================================================= + BADGE + Usage: preview + stable + ========================================================= */ + +.ca-badge { + display: inline-flex; + align-items: center; + font-family: ui-monospace, "Cascadia Code", Consolas, "Courier New", monospace; + font-size: 0.65rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + padding: 0.15em 0.5em; + border-radius: 0.25rem; + vertical-align: middle; + line-height: 1.4; + /* default: preview style */ + background: var(--ca-badge-preview-bg); + color: var(--ca-badge-preview-text); + border: 1px solid color-mix(in srgb, var(--ca-ember) 30%, transparent); +} + +.ca-badge[data-kind="preview"] { + background: var(--ca-badge-preview-bg); + color: var(--ca-badge-preview-text); + border-color: color-mix(in srgb, var(--ca-ember) 30%, transparent); +} + +.ca-badge[data-kind="stable"] { + background: var(--ca-badge-stable-bg); + color: var(--ca-badge-stable-text); + border-color: color-mix(in srgb, var(--ca-callout-note-border) 30%, transparent); +} + +/* ========================================================= + DIAGRAM CONTAINER + Usage:
+ ... + +
+ ========================================================= */ + +.ca-diagram { + display: flex; + flex-direction: column; + align-items: center; + margin-block: 2rem; + padding: 1.25rem; + border: 1px solid color-mix(in srgb, var(--ca-gold) 16%, transparent); + border-radius: 0.5rem; + background: color-mix(in srgb, var(--ca-slate) 60%, transparent); + overflow-x: auto; +} + +.ca-diagram > svg, +.ca-diagram > img { + max-width: 100%; + height: auto; + display: block; +} + +.ca-diagram figcaption { + margin-block-start: 0.75rem; + font-size: 0.8rem; + color: var(--ca-text-muted); + text-align: center; + font-family: ui-monospace, "Cascadia Code", Consolas, monospace; + letter-spacing: 0.04em; +} + +/* ========================================================= + GATE-CATCH TERMINAL + Usage:
+ +
    +
  1. + $ + git commit -m "add feature" +
  2. +
  3. + H-09 BLOCKED: banned crypto pattern detected. +
  4. +
  5. + Gate passed after fix. +
  6. +
+
+ + Animation: lines fade in sequentially via --ca-term-delay CSS custom property. + prefers-reduced-motion: collapse animation, render transcript statically. + ========================================================= */ + +.ca-terminal { + background: var(--ca-term-bg); + border: 1px solid var(--ca-term-border); + border-radius: 0.6rem; + overflow: hidden; + font-family: ui-monospace, "Cascadia Code", Consolas, "Courier New", monospace; + font-size: 0.825rem; + line-height: 1.6; + margin-block: 1.5rem; + /* Prevent CLS when animation runs */ + contain: layout; +} + +.ca-terminal__titlebar { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.5rem 0.875rem; + background: var(--ca-term-header); + border-block-end: 1px solid color-mix(in srgb, var(--ca-gold) 10%, transparent); +} + +.ca-terminal__dot { + width: 0.625rem; + height: 0.625rem; + border-radius: 50%; + background: var(--ca-slate-border); + display: inline-block; + flex-shrink: 0; +} + +.ca-terminal__title { + margin-inline-start: auto; + font-size: 0.7rem; + letter-spacing: 0.08em; + color: var(--ca-term-dim); + text-transform: lowercase; +} + +.ca-terminal__body { + padding: 0.875rem 1rem; + margin: 0; + list-style: none; + overflow-x: auto; + scrollbar-width: thin; + scrollbar-color: var(--ca-slate-border) transparent; +} + +.ca-terminal__line { + display: flex; + gap: 0.6rem; + color: var(--ca-term-text); + white-space: pre; + /* Animation: each line fades in after its delay */ + opacity: 0; + animation: ca-term-fadein 0.25s ease forwards; + animation-delay: var(--ca-term-delay, 0s); +} + +.ca-terminal__prompt { + color: var(--ca-term-prompt); + user-select: none; + flex-shrink: 0; +} + +.ca-terminal__line--error { + color: var(--ca-term-error); +} + +.ca-terminal__line--pass { + color: var(--ca-term-pass); +} + +.ca-terminal__line--dim { + color: var(--ca-term-dim); +} + +@keyframes ca-term-fadein { + from { opacity: 0; transform: translateY(3px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Reduced-motion: skip animation, render all lines immediately and statically */ +@media (prefers-reduced-motion: reduce) { + .ca-terminal__line { + opacity: 1; + animation: none; + transform: none; + } +} diff --git a/site/test/generator/forge-status.test.ts b/site/test/generator/forge-status.test.ts new file mode 100644 index 00000000..d45a2f26 --- /dev/null +++ b/site/test/generator/forge-status.test.ts @@ -0,0 +1,36 @@ +/** forge-status.test.ts — codeArbiter's site-side Feature Forge allowlist tests. */ +import { describe, it, expect } from "vitest"; +import { + getCommandForgeStatus, + PREVIEW_COMMANDS, +} from "../../scripts/generator/forge-status"; + +describe("getCommandForgeStatus", () => { + it("returns 'preview-command' for the prune command (whole command is preview)", () => { + expect(getCommandForgeStatus("prune")).toEqual({ kind: "preview-command" }); + }); + + it("returns 'preview-flag' with flag name for the sprint command (--farm flag is preview)", () => { + expect(getCommandForgeStatus("sprint")).toEqual({ + kind: "preview-flag", + flag: "--farm", + }); + }); + + it("returns null for a stable command (commit)", () => { + expect(getCommandForgeStatus("commit")).toBeNull(); + }); + + it("returns null for an unknown command", () => { + expect(getCommandForgeStatus("nonexistent-command")).toBeNull(); + }); + + it("PREVIEW_COMMANDS includes prune and sprint", () => { + expect(Object.keys(PREVIEW_COMMANDS)).toContain("prune"); + expect(Object.keys(PREVIEW_COMMANDS)).toContain("sprint"); + }); + + it("PREVIEW_COMMANDS does not include commit", () => { + expect(Object.keys(PREVIEW_COMMANDS)).not.toContain("commit"); + }); +}); diff --git a/site/test/generator/generate-forge.test.ts b/site/test/generator/generate-forge.test.ts new file mode 100644 index 00000000..2c93198c --- /dev/null +++ b/site/test/generator/generate-forge.test.ts @@ -0,0 +1,86 @@ +/** generate-forge.test.ts — codeArbiter's end-to-end forge badge injection tests. */ +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; +import { tmpdir } from "node:os"; +import { + rmSync, + readFileSync, + mkdirSync, + writeFileSync, + existsSync, +} from "node:fs"; +import { generate } from "../../scripts/generator/generate"; + +const here = dirname(fileURLToPath(import.meta.url)); +const outDir = join(tmpdir(), "ca-gen-forge-test-out"); + +/** Build a minimal in-memory plugin tree under a temp srcDir. */ +function makeSrcDir(base: string): string { + const commandsDir = join(base, "commands"); + mkdirSync(commandsDir, { recursive: true }); + + // prune — whole command is preview + writeFileSync( + join(commandsDir, "prune.md"), + `---\ndescription: Trim transcript clutter.\nargument-hint: status | dry\n---\n\n# /ca:prune\n\nBody.\n`, + ); + + // sprint — --farm flag is preview + writeFileSync( + join(commandsDir, "sprint.md"), + `---\ndescription: Autonomous sprint.\nargument-hint: "[goal] [--farm]"\n---\n\n# /ca:sprint\n\nBody.\n`, + ); + + // commit — stable; no badge expected + writeFileSync( + join(commandsDir, "commit.md"), + `---\ndescription: Run the full commit gate.\n---\n\n# /ca:commit\n\nBody.\n`, + ); + + return base; +} + +describe("generate — forge badge injection", () => { + let srcDir: string; + + beforeEach(() => { + rmSync(outDir, { recursive: true, force: true }); + mkdirSync(outDir, { recursive: true }); + srcDir = join(tmpdir(), "ca-gen-forge-src-" + Date.now()); + mkdirSync(srcDir, { recursive: true }); + makeSrcDir(srcDir); + }); + + afterEach(() => { + rmSync(outDir, { recursive: true, force: true }); + rmSync(srcDir, { recursive: true, force: true }); + }); + + it("stamps a preview badge onto the prune page", () => { + generate(srcDir, outDir); + const prunePage = join(outDir, "commands", "prune.md"); + expect(existsSync(prunePage)).toBe(true); + const content = readFileSync(prunePage, "utf8"); + expect(content).toContain('data-kind="preview"'); + expect(content).toContain("ca-badge"); + }); + + it("stamps a --farm preview callout onto the sprint page", () => { + generate(srcDir, outDir); + const sprintPage = join(outDir, "commands", "sprint.md"); + expect(existsSync(sprintPage)).toBe(true); + const content = readFileSync(sprintPage, "utf8"); + expect(content).toContain("ca-callout--preview"); + expect(content).toContain("--farm"); + }); + + it("does NOT add any badge or preview callout to the stable commit page", () => { + generate(srcDir, outDir); + const commitPage = join(outDir, "commands", "commit.md"); + expect(existsSync(commitPage)).toBe(true); + const content = readFileSync(commitPage, "utf8"); + expect(content).not.toContain("ca-badge"); + expect(content).not.toContain("ca-callout--preview"); + }); +}); diff --git a/site/test/generator/render-command-page-forge.test.ts b/site/test/generator/render-command-page-forge.test.ts new file mode 100644 index 00000000..d49873b2 --- /dev/null +++ b/site/test/generator/render-command-page-forge.test.ts @@ -0,0 +1,69 @@ +/** render-command-page-forge.test.ts — codeArbiter's preview badge/callout rendering tests. */ +import { describe, it, expect } from "vitest"; +import { renderCommandPage } from "../../scripts/generator/render-command-page"; + +describe("renderCommandPage — forge status badges and callouts", () => { + it("renders a preview badge for a preview-command (prune)", () => { + const md = renderCommandPage({ + name: "/ca:prune", + description: "Trim transcript clutter.", + forgeStatus: { kind: "preview-command" }, + }); + // Must contain the badge with data-kind="preview" + expect(md).toContain('class="ca-badge"'); + expect(md).toContain('data-kind="preview"'); + expect(md).toContain("preview"); + }); + + it("renders a --farm preview callout for a preview-flag (sprint)", () => { + const md = renderCommandPage({ + name: "/ca:sprint", + description: "Autonomous sprint.", + forgeStatus: { kind: "preview-flag", flag: "--farm" }, + }); + // Must contain the preview callout with the flag name + expect(md).toContain("ca-callout--preview"); + expect(md).toContain("--farm"); + expect(md).toContain("preview"); + }); + + it("does NOT render a badge or callout for a stable command (commit)", () => { + const md = renderCommandPage({ + name: "/ca:commit", + description: "Run the full commit gate.", + forgeStatus: null, + }); + expect(md).not.toContain("ca-badge"); + expect(md).not.toContain("ca-callout--preview"); + }); + + it("does NOT render a badge or callout when forgeStatus is omitted", () => { + const md = renderCommandPage({ + name: "/ca:commit", + description: "Run the full commit gate.", + }); + expect(md).not.toContain("ca-badge"); + expect(md).not.toContain("ca-callout--preview"); + }); + + it("preview-flag callout uses the contract callout classes", () => { + const md = renderCommandPage({ + name: "/ca:sprint", + description: "Autonomous sprint.", + forgeStatus: { kind: "preview-flag", flag: "--farm" }, + }); + // Must carry both base class and variant modifier + expect(md).toContain("ca-callout"); + expect(md).toContain("ca-callout--preview"); + }); + + it("preview-command badge uses the contract badge class and data-kind attribute", () => { + const md = renderCommandPage({ + name: "/ca:prune", + description: "Trim transcript clutter.", + forgeStatus: { kind: "preview-command" }, + }); + expect(md).toContain('class="ca-badge"'); + expect(md).toContain('data-kind="preview"'); + }); +}); diff --git a/site/test/landing/landing-page.test.ts b/site/test/landing/landing-page.test.ts new file mode 100644 index 00000000..b477b4af --- /dev/null +++ b/site/test/landing/landing-page.test.ts @@ -0,0 +1,262 @@ +/** + * TDD Phase 1 obligations — bespoke landing page (AC 6, 7, 8, 12, 15, 16, 19). + * + * These tests read the landing source files because the full Astro build is not + * run inside vitest. The "landing source" is the union of: + * - src/content/docs/index.mdx (frontmatter + MDX body) + * - src/components/GateCatchTerminal.astro + * - src/components/ForgeShowcase.astro + * - src/styles/landing.css + * + * Source-level assertions are reliable here because: + * - AC 6/7/8/12/15/16: structural/markup obligations are authored in the above + * files and render directly to the built HTML without transformation. + * - AC 19: em-dash cap is a source-level prose check by spec definition. + * + * The design-quality-reviewer (AC-11) is dispatched separately as a visual gate. + */ + +import { describe, it, expect } from "vitest"; +import { readFileSync, existsSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import * as path from "node:path"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const siteRoot = path.resolve(__dirname, "../../"); + +function readSrc(rel: string): string { + return readFileSync(path.join(siteRoot, rel), "utf8"); +} + +// --------------------------------------------------------------------------- +// Fixtures +// --------------------------------------------------------------------------- + +const indexMdx = readSrc("src/content/docs/index.mdx"); +const terminalCmp = readSrc("src/components/GateCatchTerminal.astro"); +const forgeCmp = readSrc("src/components/ForgeShowcase.astro"); +const landingCss = readSrc("src/styles/landing.css"); + +/** Combined source of all landing page artifacts */ +const landingSrc = indexMdx + "\n" + terminalCmp + "\n" + forgeCmp; + +// --------------------------------------------------------------------------- +// AC-6: Bespoke landing — stock CardGrid replaced +// --------------------------------------------------------------------------- + +describe("AC-6: bespoke landing replaces stock CardGrid", () => { + it("index.mdx does not contain the stock component from Starlight", () => { + expect(indexMdx).not.toMatch(//); + }); + + it("landing source contains the gate-catch terminal (ca-terminal)", () => { + expect(landingSrc).toContain("ca-terminal"); + }); + + it("landing source contains the Feature Forge showcase section (ca-forge)", () => { + expect(landingSrc).toContain("ca-forge"); + }); + + it("index.mdx contains the bespoke hero section (ca-hero)", () => { + expect(indexMdx).toContain("ca-hero"); + }); +}); + +// --------------------------------------------------------------------------- +// AC-7: prefers-reduced-motion honored for terminal animation +// --------------------------------------------------------------------------- + +describe("AC-7: terminal honors prefers-reduced-motion", () => { + it("theme.css contains a prefers-reduced-motion media query that collapses animation", () => { + const themeCss = readSrc("src/styles/theme.css"); + expect(themeCss).toContain("prefers-reduced-motion"); + expect(themeCss).toContain("animation: none"); + }); + + it("terminal lines carry the ca-terminal__line class (so the reduced-motion rule applies)", () => { + expect(terminalCmp).toContain("ca-terminal__line"); + }); + + it("theme.css reduced-motion block sets opacity: 1 so all lines are visible statically", () => { + const themeCss = readSrc("src/styles/theme.css"); + expect(themeCss).toMatch(/prefers-reduced-motion[\s\S]*?opacity:\s*1/); + }); +}); + +// --------------------------------------------------------------------------- +// AC-8: Terminal transcript is real DOM text +// --------------------------------------------------------------------------- + +describe("AC-8: terminal transcript is real DOM text, not canvas/image", () => { + it("terminal component does not use elements", () => { + expect(terminalCmp).not.toContain(" elements for the transcript", () => { + // The terminal transcript itself must not be an image + // (img is allowed in diagrams but not inside the terminal body) + const bodySection = terminalCmp.match(/ca-terminal__body[\s\S]*/)?.[0] ?? ""; + expect(bodySection).not.toContain(" { + expect(terminalCmp).toMatch(/git commit|ca:commit/i); + }); + + it("terminal body has role=list for screen-reader grouping", () => { + expect(terminalCmp).toContain('role="list"'); + }); + + it("terminal lines have role=listitem", () => { + expect(terminalCmp).toContain('role="listitem"'); + }); + + it("terminal region has an aria-label", () => { + expect(terminalCmp).toMatch(/aria-label="[^"]+"/); + }); +}); + +// --------------------------------------------------------------------------- +// AC-12: Feature Forge showcase +// --------------------------------------------------------------------------- + +describe("AC-12: Feature Forge showcase with key claims", () => { + it("showcase states features are opt-in", () => { + expect(forgeCmp).toMatch(/opt.in/i); + }); + + it("showcase states features ship dormant or off by default", () => { + expect(forgeCmp).toMatch(/dormant|off by default/i); + }); + + it("showcase states promotion is by real-world evidence", () => { + expect(forgeCmp).toMatch(/evidence/i); + }); + + it("showcase mentions SemVer for whole payload versioning", () => { + expect(forgeCmp).toMatch(/SemVer|semver/i); + }); + + it("showcase mentions Feature Forge per-feature preview model", () => { + expect(forgeCmp).toMatch(/Feature Forge|per.feature/i); + }); + + it("showcase references the two-axis-model diagram", () => { + expect(forgeCmp).toContain("two-axis-model"); + }); +}); + +// --------------------------------------------------------------------------- +// AC-15: --farm is adjacent to / links the Feature Forge showcase +// --------------------------------------------------------------------------- + +describe("AC-15: --farm is no longer context-free", () => { + it("--farm appears in the landing source", () => { + expect(landingSrc).toContain("--farm"); + }); + + it("--farm appears inside the forge showcase component", () => { + expect(forgeCmp).toContain("--farm"); + }); + + it("forge showcase component contains a preview callout for --farm", () => { + // The --farm must be in a visible explanatory block, not bare + expect(forgeCmp).toMatch(/ca-callout--preview[\s\S]*?--farm|--farm[\s\S]*?ca-callout/); + }); +}); + +// --------------------------------------------------------------------------- +// AC-16: Above-the-fold: what / why / first command + single primary CTA +// --------------------------------------------------------------------------- + +describe("AC-16: above-fold hero answers what/why/first command + one primary CTA", () => { + it("hero tagline is present in frontmatter", () => { + expect(indexMdx).toContain("tagline:"); + }); + + it("exactly one primary CTA variant exists in the hero frontmatter actions", () => { + const primaryMatches = indexMdx.match(/variant: primary/g); + expect(primaryMatches).not.toBeNull(); + expect(primaryMatches!.length).toBe(1); + }); + + it("primary CTA links to the overview page with a base-safe relative link", () => { + // Hero `actions` links are emitted verbatim by Starlight's Hero/LinkButton + // (no base-prefixing, unlike slug-based nav). A leading-slash link like + // `/overview/` would render non-base-prefixed and 404 under the /codeArbiter + // base on GH Pages. A page-relative `./overview/` resolves against the page + // URL (served at /codeArbiter/) → /codeArbiter/overview/, and auto-corrects + // if the base changes. No hardcoded base. + expect(indexMdx).toMatch(/link: \.\/overview\//); + }); + + it("first command is shown in the hero section", () => { + // The hero body must show a copy-able first command + expect(indexMdx).toMatch(/\/ca:feature|\/ca:commit|\/ca:fix/); + }); + + it("hero body answers 'what' (the orchestrator description)", () => { + expect(indexMdx).toMatch(/orchestrat|gated|lane/i); + }); +}); + +// --------------------------------------------------------------------------- +// AC-19: Em-dash cap (≤3 per page, ≤1 per paragraph) on prose +// --------------------------------------------------------------------------- + +describe("AC-19: em-dash cap on landing prose", () => { + /** + * Strips frontmatter, code blocks, HTML/JSX tags, and import statements + * so the cap applies only to hand-written prose, per spec. + */ + function extractProse(src: string): string { + let text = src.replace(/^---[\s\S]*?---\n/, ""); + text = text.replace(/```[\s\S]*?```/g, ""); + text = text.replace(/`[^`\n]*`/g, ""); + text = text.replace(/<[^>]+>/g, " "); + text = text.replace(/^import\s+.*$/gm, ""); + return text; + } + + it("index.mdx contains ≤3 em-dashes in hand-written prose", () => { + const prose = extractProse(indexMdx); + const emDashes = (prose.match(/—/g) ?? []).length; + expect(emDashes).toBeLessThanOrEqual(3); + }); + + it("no paragraph in index.mdx prose has more than one em-dash", () => { + const prose = extractProse(indexMdx); + const paragraphs = prose.split(/\n{2,}/); + for (const para of paragraphs) { + const count = (para.match(/—/g) ?? []).length; + expect(count).toBeLessThanOrEqual(1); + } + }); + + it("GateCatchTerminal.astro prose contains ≤3 em-dashes", () => { + const prose = extractProse(terminalCmp); + const emDashes = (prose.match(/—/g) ?? []).length; + expect(emDashes).toBeLessThanOrEqual(3); + }); + + it("ForgeShowcase.astro prose contains ≤3 em-dashes", () => { + const prose = extractProse(forgeCmp); + const emDashes = (prose.match(/—/g) ?? []).length; + expect(emDashes).toBeLessThanOrEqual(3); + }); +}); + +// --------------------------------------------------------------------------- +// Lane-flow diagram embedded in landing +// --------------------------------------------------------------------------- + +describe("lane-flow diagram embedded in landing", () => { + it("lane-flow.svg asset exists", () => { + const svgPath = path.join(siteRoot, "src/assets/diagrams/lane-flow.svg"); + expect(existsSync(svgPath)).toBe(true); + }); + + it("index.mdx references the lane-flow diagram", () => { + expect(indexMdx).toContain("lane-flow"); + }); +}); From d19a80afb6cdc73a50ef99de6ca7ce451b5dbd3e Mon Sep 17 00:00:00 2001 From: SUaDtL Date: Mon, 22 Jun 2026 03:12:04 -0400 Subject: [PATCH 2/3] docs(governance): record docs-site-polish spec + harvest follow-ups Persist the approved spec that drove the feat(site) docs overhaul and record the two follow-ups its build surfaced, so the out-of-scope work isn't lost: - Allowlist-drift reconciler: a CI/doctor check that the site-side Feature Forge preview allowlist matches real preview status. - Base-path img-href convention: unify the relative vs BASE_URL forms used for diagram/image sources across the site. Ref: .codearbiter/specs/docs-site-polish.md Claude-Session: https://claude.ai/code/session_016NVNTNAPKXzWD8bmmjVHP7 --- .codearbiter/open-tasks.md | 2 + .codearbiter/specs/docs-site-polish.md | 154 +++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 .codearbiter/specs/docs-site-polish.md diff --git a/.codearbiter/open-tasks.md b/.codearbiter/open-tasks.md index 67e6cea1..cb87f625 100644 --- a/.codearbiter/open-tasks.md +++ b/.codearbiter/open-tasks.md @@ -5,6 +5,8 @@ per task. Schema and the count rule: see `plugins/ca/hooks/init-codearbiter.py` (`OPEN_TASKS`) or `.codearbiter/specs/task-board-lifecycle.md`. ## In-flight +- [ ] Centralize the base-path pattern for diagram/image hrefs on the docs site — index.mdx uses a relative path while ForgeShowcase.astro uses import.meta.env.BASE_URL + /diagrams/. Both resolve today, but if the Astro base changes the BASE_URL form needs a manual update. Pick one base-safe img-href convention and apply it site-wide. (from specs/docs-site-polish.md) +- [ ] Reconcile the site Feature-Forge preview allowlist (site/scripts/generator/forge-status.ts) against real preview status — a CI or /ca:doctor check that fails when a feature promoted to stable still carries a preview badge (or vice-versa). Follow-up from specs/docs-site-polish.md (AC-14 allowlist can drift). Optional stretch: revisit the rejected forge: source-frontmatter approach to make preview status single-source/can't-drift if a plugins/ca change is otherwise warranted. (from specs/docs-site-polish.md) - [ ] v2.test.0002 - Backfill direct unit tests in test_hooklib.py for the ~18 pre-existing CRYPTO_RE branches (3des, RSA, sha1, createCipher, crypto.pbkdf2/scrypt/sign, etc.) currently exercised only indirectly (coverage-auditor NEEDS-TRIAGE, checkpoint 2026-06-22). (from checkpoint-2026-06-22) - [ ] v2.chore.0001 - Move sharp from dependencies to optionalDependencies in site/package.json (checkpoint 2026-06-22 MEDIUM) to cut install-time native-binary surface; requires regenerating site/package-lock.json. (from checkpoint-2026-06-22) - [ ] v2.test.0001 - _sloplib prose-separator detector (find_prose_separator_dashes) has no direct unit tests (checkpoint 2026-06-22 MEDIUM). Add test_sloplib.py covering fence/inline-code/URL/numeric-range exemptions and in_antislop_doc_scope. (from checkpoint-2026-06-22) diff --git a/.codearbiter/specs/docs-site-polish.md b/.codearbiter/specs/docs-site-polish.md new file mode 100644 index 00000000..34792277 --- /dev/null +++ b/.codearbiter/specs/docs-site-polish.md @@ -0,0 +1,154 @@ +# Spec — docs-site-polish: make the public docs site beautiful, functional, forge-forward + +status: APPROVED — 2026-06-22, by repo owner (Phase 4 STOP gate cleared); build path: ultracode workflow +slug: docs-site-polish +created: 2026-06-22 +lane: full → build path: **ultracode workflow** (multi-agent), owner-selected +scope-guard: **site-only** — no `plugins/ca/**` change (badge data lives in the site generator) +relates-to: [CONFIRM-05] (Feature Forge promotion bar for `--farm`) — the preview badges this spec adds +*surface* the preview status that CONFIRM-05 governs; promotion stays a separate owner decision and this +spec does not resolve it. Follows `specs/docs-site-mvp.md` (the #94 `--farm` Step-6 artifact this polishes). + +## Problem + +The docs site under `site/` was produced by the `/sprint --farm` Step-6 test (#94) — a functional MVP, +but it reads like a scaffold. It is now public-facing and does not earn a first-time visitor's +understanding: + +- The landing page is stock Starlight chrome (a four-card grid) — it asserts "Gated" but never *shows* + a gate doing its job, which is codeArbiter's actual conversion mechanic (adoption-strategy: *show a + gate catch a real mistake*). +- The **Feature Forge** — the per-feature preview/maturity model that is a core differentiator — is + mentioned **nowhere** on the site, even though the landing page literally prints `/ca:sprint --farm` + with no explanation of what `--farm` or "preview" means. +- Stock chrome with no logo, favicon, or social link reads as unfinished; base-path-fragile links + (hero actions hardcode `/codeArbiter/`) are a known GH-Pages breakage class (#109 "unbreak docs deploy"). + +## Caller + +A first-time visitor — a Claude Code user evaluating adoption. "Done" = they land, *get it within one +screen* (what it is, why it matters, their first command), see the differentiator (gated lanes + the +transparent, data-promoted Feature Forge), and watch a gate catch a real mistake. + +## Scope + +**In scope** (the `site/` tree only): + +- A **bespoke landing page** replacing the stock card grid: custom hero, an animated gate-catch terminal + (the conversion centerpiece), and a Feature Forge showcase. +- An **inner-page visual pass** on Overview and Concepts: custom callouts (note / warn / gate / preview), + a coherent **three-diagram set** (lane-flow, two-axis labeling model, gate model), and per-reference + **preview badges**. +- **Feature Forge surfacing**: landing showcase + a Concepts section + automated preview badges driven by + a **site-side generator allowlist**. +- **Functional integrity**: working logo/favicon/social config, base-path-safe internal links, working + Pagefind search, and a green `site/` build + test + typecheck. + +**Out of scope (the boundary that keeps this honest):** + +- **No `plugins/ca/**` change.** Preview status is sourced from an allowlist inside `site/scripts/generator/`, + *not* from new plugin frontmatter. (Owner-selected; the can't-drift frontmatter alternative was rejected + to hold the site-only boundary — see Open questions / triage.) +- **No ejecting from Starlight** or replacing the theme engine. Customize *within* the framework. +- **No rewrite of the auto-generated reference bodies.** Their content is generator output; only the badge + decoration and the generator allowlist are added. +- **No new framework/plugin behavior.** This is documentation presentation only. + +## Acceptance criteria + +Each is verifiable by a single test (automated unit/build check, or the named design reviewer where the +criterion is visual). Each becomes one `tdd` Phase-1 obligation. + +**Functional integrity** + +1. `npm run build` in `site/` exits 0 with `gen` having regenerated the reference pages (assert exit 0 + + reference pages present in `dist`). +2. Every internal link in the built `site/dist` HTML resolves under `base: /codeArbiter` — a link-audit + asserts each internal href is base-prefixed (or correctly relative) and its target file exists; zero + dangling internal links. +3. Hero/primary action links derive from a single base-path source (or a test asserts each rendered hero + action href maps to an existing `dist` page) — the hardcoded-base desync class is closed. +4. `astro.config` sets `favicon`, a `logo`, and a GitHub `social` link; the favicon/logo asset exists in + `dist` (assert config fields present + asset emitted). +5. Pagefind search is present and indexed in `dist`, and a query for a known term (e.g. `gate`) is + resolvable against the built index (assert index emitted + search UI present). + +**Beautiful (within Starlight)** + +6. The landing page is bespoke: the stock `CardGrid` is replaced by a custom hero + gate-catch terminal + + forge showcase (assert the named landing components/markup exist in built `index.html`, stock card grid + absent). +7. The gate-catch terminal honors `prefers-reduced-motion: reduce`: under that query the animation is + disabled and the **full transcript renders statically** (assert the media query + static transcript + present). +8. The gate-catch terminal transcript is real DOM text (not an image/canvas) and screen-reader readable; + animation is presentational only (assert transcript text nodes present in `index.html`). +9. Three new diagrams exist as SVG assets — **lane-flow** (command→route→gate→ship), **two-axis labeling + model** (SemVer vs Feature Forge), **gate model** — each referenced from its relevant page and each + carrying an accessible ``/alt (assert 3 SVGs exist, referenced, titled). +10. Overview and Concepts carry the custom callout treatment (≥1 `note`/`gate`/`preview` callout rendered; + assert callout markup present). +11. **Visual design gate:** the `design-quality-reviewer` clears the landing + inner pages against the + `anti-slop-design` reference with **no CRITICAL or HIGH** *visual* design-slop finding. (Reviewer is the + verifier for this visual criterion. Prose slop is a separate axis — see AC-19/20; the visual reviewer + does not gate copy voice.) + +**Feature Forge (highlight the forge)** + +12. The landing has a Feature Forge showcase stating preview features are **opt-in, ship dormant/off by + default, and are promoted by real-world evidence**, plus the two-axis model (SemVer = whole payload; + Feature Forge = per-feature preview) — assert showcase markup + those key claims present in `index.html`. +13. Concepts gains a "Feature Forge" section carrying the two-axis model and the preview→stable + (evidence-promoted) idea (assert section heading + content present). +14. A site-side allowlist (`site/scripts/generator/` — no `plugins/ca` change) marks preview features; the + generated `/ca:prune` reference page renders a **preview badge**, and the `/ca:sprint` page renders a + "`--farm` — preview" callout. A generator vitest asserts the badge/callout appears for allowlisted + features and **not** for stable ones. +15. The landing `--farm` command line is no longer context-free: it links to / sits adjacent to the Feature + Forge showcase so a visitor can find what `--farm`/"preview" means (assert the link/adjacency in markup). + +**UX (how adoption feels)** + +16. Above the fold, the landing answers *what / why / first command* within one screen: hero tagline, the + gate-catch demo, and a copy-able primary first command with a single primary CTA (Get started → + overview) (assert the three elements + exactly one primary action variant in the hero markup). + +**Copy anti-slop (prose voice — the separate axis from visual design)** + +The current copy reads as AI-generated, the loudest tell being em-dash overuse (`index.mdx` 7, `overview.md` +16, `concepts.md` 10). Em-dashes are not banned — the goal is *sparing* use so commas, colons, periods, and +parentheses carry the load they should. + +19. **Hard, automated em-dash cap.** Every user-facing site prose page (`index.mdx`, `overview.md`, + `concepts.md`, and any new prose page) contains **≤ 3 em-dashes**, and no single paragraph uses more than + one. A repeatable counting check (committed under `site/`) asserts the per-page ceiling and exits non-zero + on a violation — this is the test for the criterion. (Code blocks, frontmatter, and the auto-generated + reference bodies are exempt; the cap is on hand-written prose.) +20. **Copy-slop checklist pass.** The landing + inner copy clears a written copy anti-slop checklist: + no "not just X, but Y" / "it's not A — it's B" template, no padded tricolons, no hedging filler + ("perhaps", "arguably"), no LLM-tell vocabulary ("delve", "leverage", "seamless", "robust", "tapestry"), + and varied sentence length (not a uniform medium-rhythm wall). The authoring agent self-applies the + checklist and a reviewer confirms; the checklist ships in the workflow's verify stage as a named gate. + +**Build/CI integrity (boundary + green)** + +21. `site/` vitest (`npm test`) and `npm run typecheck` both exit 0, including the new badge/allowlist tests + and the em-dash check (AC-19). +22. `git diff --name-only` for the change touches only `site/**` (and this spec/plan under `.codearbiter/**`) + — **no `plugins/ca/**` file modified** (boundary held). + +## Open questions + +- **[CONFIRM-05]** (cross-ref, non-blocking here) — the preview badges (AC-14) *display* the preview status + that CONFIRM-05's promotion bar governs. When `--farm` is promoted, the site allowlist must drop its + preview entry. This spec surfaces the status; it does not set or resolve the promotion bar. + +No new blocking `[CONFIRM-NN]` is raised by this spec. + +## Deferred / triage (non-blocking) + +- **[NEEDS-TRIAGE] Allowlist drift.** The generator allowlist (AC-14) is hand-maintained and can drift if a + forge feature is promoted to stable and nobody updates it. Two follow-ups worth promoting: (a) a check + (CI or `/ca:doctor`) that reconciles the site allowlist against actual preview status; (b) revisit the + rejected `forge:` source-frontmatter approach if/when a `plugins/ca` change is otherwise warranted, to + make preview status single-source / can't-drift. From 0f66ef9e375417739bcfc1a6fb759d01b7d94b46 Mon Sep 17 00:00:00 2001 From: SUaDtL <SUaDtL@users.noreply.github.com> Date: Mon, 22 Jun 2026 03:19:24 -0400 Subject: [PATCH 3/3] docs(governance): harvest coverage-auditor follow-ups for docs site The PR coverage audit confirmed the build green but flagged two spec acceptance criteria the build delivered as content without the automated assertion the spec promised (AC-9 diagram titles, AC-13 Concepts forge section), plus an untested defensive path in forge-status. Record them so the spec's testability promise is honored rather than quietly dropped. Ref: .codearbiter/specs/docs-site-polish.md Claude-Session: https://claude.ai/code/session_016NVNTNAPKXzWD8bmmjVHP7 --- .codearbiter/open-tasks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.codearbiter/open-tasks.md b/.codearbiter/open-tasks.md index cb87f625..13134f7d 100644 --- a/.codearbiter/open-tasks.md +++ b/.codearbiter/open-tasks.md @@ -5,6 +5,8 @@ per task. Schema and the count rule: see `plugins/ca/hooks/init-codearbiter.py` (`OPEN_TASKS`) or `.codearbiter/specs/task-board-lifecycle.md`. ## In-flight +- [ ] Add a CI link-audit gate for the docs site: assert every internal dist link resolves under the /codeArbiter base path and the favicon/logo assets emit to dist (spec AC-2/AC-4, currently build-time/manual only). (from coverage-auditor:docs-site-polish) +- [ ] Add the automated assertions the docs-site-polish spec promised but the build shipped without: AC-9 (3 diagram SVGs exist + accessible <title>/alt, referenced from their pages), AC-13 (Concepts 'Feature Forge' section present with evidence-promotion language), and forge-status edge-case tests (/ca: prefix strip + case normalization, the documented-but-untested defensive path). (from coverage-auditor:docs-site-polish) - [ ] Centralize the base-path pattern for diagram/image hrefs on the docs site — index.mdx uses a relative path while ForgeShowcase.astro uses import.meta.env.BASE_URL + /diagrams/. Both resolve today, but if the Astro base changes the BASE_URL form needs a manual update. Pick one base-safe img-href convention and apply it site-wide. (from specs/docs-site-polish.md) - [ ] Reconcile the site Feature-Forge preview allowlist (site/scripts/generator/forge-status.ts) against real preview status — a CI or /ca:doctor check that fails when a feature promoted to stable still carries a preview badge (or vice-versa). Follow-up from specs/docs-site-polish.md (AC-14 allowlist can drift). Optional stretch: revisit the rejected forge: source-frontmatter approach to make preview status single-source/can't-drift if a plugins/ca change is otherwise warranted. (from specs/docs-site-polish.md) - [ ] v2.test.0002 - Backfill direct unit tests in test_hooklib.py for the ~18 pre-existing CRYPTO_RE branches (3des, RSA, sha1, createCipher, crypto.pbkdf2/scrypt/sign, etc.) currently exercised only indirectly (coverage-auditor NEEDS-TRIAGE, checkpoint 2026-06-22). (from checkpoint-2026-06-22)