diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml index 494be04..033ff44 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/static.yml @@ -40,6 +40,9 @@ jobs: mkdir -p _site/status cp landing/index.html _site/index.html cp public/index.html _site/status/index.html + # Brand assets referenced by absolute URL from both pages (favicon, + # apple-touch-icon, 1200x630 og card). Served from the Pages root. + cp docs/assets/og.png docs/assets/favicon.svg docs/assets/apple-touch-icon.png _site/ - name: Setup Pages uses: actions/configure-pages@v5 - name: Upload artifact diff --git a/.gitignore b/.gitignore index 6101656..118cb3a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ npm-debug.log* # Local backups the installer creates .backup-*/ + +# Generated status page — built fresh at deploy (scripts/build-pages.mjs) +public/ diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index ce55df0..58f20ff 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -240,9 +240,14 @@ every intra-repo Markdown anchor (`#x` and `path.md#x`) against the target's rea (GitHub-exact slugs — em-dashes yield `--`, never collapsed), killing the dead-anchor class; and `checkRoadmap` fails when the ROADMAP's "Now" marker trails the shipped `package.json` version. The two public pages -(`landing/index.html` + the `build-pages.mjs` status page) share one set of design tokens, -enforced for parity (plus non-empty changes list, no phantom webfont) by -`test/pages.test.js` — so neither the docs' numbers nor the site's look can silently drift. +(`landing/index.html` + the `build-pages.mjs` status page) derive from ONE color source — +`brand.json.colors` (full dark + light palettes), emitted as CSS by `src/brand.js` +(`rootTokensCss()`). `test/pages.test.js` enforces full-palette parity: every hex in +`brand.json` must appear on both surfaces, so the palette can't fork into "two palettes +claiming to be one" again (plus non-empty changes list, no phantom webfont, present +social/favicon metadata). `checkDiagrams` extends the same single-source rule to Mermaid — +every `%%{init` theme must carry the brand's ember + warm-black hexes — so neither the docs' +numbers nor the site's look can silently drift. **Auto-release (`.github/workflows/bump.yml` + `scripts/bump.mjs`).** A push to `master` runs `bump.mjs auto`: it releases only when a `feat`/`fix`/`perf`/breaking commit landed diff --git a/CHANGELOG.md b/CHANGELOG.md index f513236..87f1e10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,65 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- **Single design-token source.** `brand.json` gained a `colors` block (full dark + + light palettes) as the one source of the visual palette, plus `fonts` and `site`. + `src/brand.js` exposes it via `cssVars(scheme)` + `rootTokensCss()` pure helpers; + the generated status page (`scripts/build-pages.mjs`) now injects tokens from that + one source (and gains light mode), and `test/pages.test.js` enforces full-palette + parity — every dark and light hex in `brand.json` must appear on both public pages, + so the landing page and status page can no longer fork into two palettes claiming + to be one. +- **Landing page redesign.** Rebuilt `landing/index.html` on a Stat-Led structure: + the real shipped hero diagram is now embedded (inlined so it resolves in local + preview and at the deployed site root), the fake terminal chrome, placeholder + digit/glyph icons, and faux-live pulse dot are gone, capabilities use real inline + SVG icons in a hairline-divided layout instead of a uniform card grid, and the + sticky-nav blur is compositor-light. Light-mode accents are darkened to meet AA + contrast on the light paper. +- **Accessibility + design-system hygiene.** All accent/supplementary-text pairs on + both themes are now verified ≥4.5:1 (bumped `--faint` in dark + light, and light + `--brand`/`--ok`, at the single brand.json source). Border-radii are collapsed onto + a deliberate 3-level scale (`--radius-sm` / `--radius` / pill), so `forge uicheck +design` passes spacing-scale, radius-levels, and shadow-levels with a healthy + slop-distance. Focus rings appear instantly (no animated outline), motion is + transform/opacity-only under `prefers-reduced-motion`. + +### Added + +- **Social + icon metadata on both public pages.** The landing and generated status + pages now ship `og:image` / `twitter:image` (a 1200×630 brand card, + `docs/assets/og.png`, rasterized once via Chromium — an author artifact, not a + runtime dep), a favicon + apple-touch-icon (`docs/assets/favicon.svg` / + `apple-touch-icon.png`), and consistent `canonical` == `og:url`. The status page + gained a full Open Graph / Twitter / `SoftwareApplication` JSON-LD head; the deploy + workflow copies the brand assets to the Pages root so the absolute URLs resolve. +- **Brand-aligned status line.** `global/statusline.sh` now renders the exact brand + tokens (ember `#f26430`, warm-taupe greys) in 24-bit truecolor from a named palette + block, with a 256-color fallback when the terminal can't do truecolor. New + `test/statusline.test.js` smoke-tests the segments, the exact truecolor hexes, the + fallback, and graceful degradation on minimal input. +- **Mermaid theme-value guard.** `forge docs check`'s `checkDiagrams` now verifies each + `%%{init` block carries the brand's actual color values (ember + warm-black from + `brand.json`), not just that a theme directive is present — a diagram can no longer + declare a theme and still render off-brand. README leads with a `Start in 60 seconds` + block, and `ARCHITECTURE.md` documents `brand.json` as the single color source. + +### Fixed + +- **Status-page metrics were silently stale.** The `impact` and `saved` regexes in + `scripts/build-pages.mjs` no longer matched the current README, so those + "repo-sourced" numbers were really hardcoded fallbacks. Regexes fixed to parse the + README, and a non-match is now a hard build error (`mustMatch`) instead of a silent + fallback; the dead `claim` field is removed. +- **Generated status page no longer ships stale/leaky.** `public/index.html` is a + build artifact (regenerated at deploy), so it is now gitignored and dropped from + the npm `files` list — a stale committed copy (old version + a dev-branch name + leaked into a visible chip) can no longer be published in the tarball. +- **Landing → status link** now points at the absolute Pages URL, so it resolves in + local file preview instead of 404-ing on `./status/`. + ## [0.17.0] - 2026-07-15 ### Added @@ -25,7 +84,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - **Playwright interaction loop** — `forge uicheck interact ` drives the - page headless under `prefers-reduced-motion` and checks what it *does* + page headless under `prefers-reduced-motion` and checks what it _does_ (console-clean, keyboard-reachable, focus-visible, reduced-motion), where `uicheck visual` only fingerprints what it paints. The verdict is recorded through the ledger's cross-family-gated `behavioral` oracle (advisory by default; @@ -55,7 +114,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). MAE. Advisory by default — routing keeps the rubric until a promotion is adopted (`calibratedComplexity` mirrors `predictor.riskFor`). Zero deps, fully unit-tested. - **Legacy-store retirement (`FORGE_LEDGER_ONLY`)** — the PCM ledger can now be the - *only* store. Since P1 it has been the convergent write store (dual-write) with a + _only_ store. Since P1 it has been the convergent write store (dual-write) with a merged read (`ledger_read`); with `FORGE_LEDGER_ONLY=1` the legacy files (`.forge/lessons/*.md`, recall/brain fact files) stop being written and every read materializes from the ledger — cortex confirm/create/distill dedup against diff --git a/README.md b/README.md index 33a69c7..67dd1aa 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,21 @@ delivers them into every tool you use. > **Status: beta.** The core (`init`, `sync`, `substrate`, `impact`, `ledger`, guards) is > tested and in daily use; some flags may change before `1.0`. +## Start in 60 seconds + +```bash +npm install -g @codewithjuber/forgekit # or: npm install -g github:CodeWithJuber/forgekit +forge init # emit every AI tool's native config from one source +forge doctor # verify providers, hooks, and MCP wiring +``` + +That's it — your agents now share one source of truth. The +[full quickstart](#60-second-quickstart) walks through the loop; [Commands](#commands) +lists everything `forge` can do. + ## Contents +- [Start in 60 seconds](#start-in-60-seconds) - [The problem](#the-problem) - [How it works — the loop](#how-it-works--the-loop) - [What you get](#what-you-get) diff --git a/brand.json b/brand.json index 79275bb..88bd568 100644 --- a/brand.json +++ b/brand.json @@ -11,5 +11,45 @@ "atlas": "code-graph (symbol/call index)", "lean": "minimalism discipline", "recall": "cross-session memory" + }, + "site": { + "url": "https://codewithjuber.github.io/forgekit/", + "repo": "https://github.com/CodeWithJuber/forgekit" + }, + "fonts": { + "sans": "system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif", + "mono": "ui-monospace, \"SF Mono\", SFMono-Regular, Menlo, Consolas, monospace" + }, + "colors": { + "dark": { + "bg": "#171310", + "bg-2": "#201a15", + "panel": "#201a15", + "panel-2": "#272019", + "text": "#f2ede7", + "muted": "#a99e90", + "faint": "#8a7d6d", + "line": "#372c22", + "line-strong": "#4a3b2e", + "brand": "#f26430", + "brand-2": "#f7a072", + "ok": "#67e8a5", + "shadow": "0 24px 64px rgba(23, 19, 16, 0.56)" + }, + "light": { + "bg": "#f7f3ed", + "bg-2": "#fffdf9", + "panel": "#fffdf9", + "panel-2": "#efe7db", + "text": "#201a15", + "muted": "#6b5f50", + "faint": "#7e7161", + "line": "#e2d6c4", + "line-strong": "#cbbba3", + "brand": "#ba4b1e", + "brand-2": "#b8481c", + "ok": "#1b7e48", + "shadow": "0 24px 64px rgba(32, 26, 21, 0.12)" + } } } diff --git a/docs/assets/apple-touch-icon.png b/docs/assets/apple-touch-icon.png new file mode 100644 index 0000000..b6f5ec9 Binary files /dev/null and b/docs/assets/apple-touch-icon.png differ diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg new file mode 100644 index 0000000..12bbe12 --- /dev/null +++ b/docs/assets/favicon.svg @@ -0,0 +1,10 @@ + + + + + + + + + F + diff --git a/docs/assets/og.png b/docs/assets/og.png new file mode 100644 index 0000000..bd21401 Binary files /dev/null and b/docs/assets/og.png differ diff --git a/docs/assets/og.svg b/docs/assets/og.svg new file mode 100644 index 0000000..93761ae --- /dev/null +++ b/docs/assets/og.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + F + forgekit + + + ONE BRAIN FOR EVERY AI CODING AGENT + + + Give every coding agent + the same working memory. + + + Proof-carrying memory · impact foresight · reuse · enforced guardrails — + compiled to native config for the agents your team already runs. + + + + MIT + · + zero runtime dependencies + · + Claude Code, Codex, Cursor, Gemini, Aider & more + + diff --git a/global/statusline.sh b/global/statusline.sh index e66b084..af5ca03 100644 --- a/global/statusline.sh +++ b/global/statusline.sh @@ -31,15 +31,27 @@ fi [ -n "${dir:-}" ] || dir="$PWD" short="${dir/#$HOME/\~}" -# Palette (256-color): muted structure, one ember accent, green/red reserved for the diff. +# Palette — the exact forgekit brand tokens (brand.json.colors.dark) rendered in +# 24-bit truecolor so the ember and warm-taupe greys are on-brand, not the nearest +# off-brand xterm-256 index. Named block; one ember accent, green/red only for the +# diff. Falls back to the closest 256-color indices when the terminal can't do +# truecolor (COLORTERM unset / not 24-bit), so it degrades instead of mis-rendering. e=$(printf '\033') R="${e}[0m" BOLD="${e}[1m" -DIM="${e}[38;5;245m" -MUTED="${e}[38;5;247m" -EMBER="${e}[38;5;209m" -GREEN="${e}[38;5;114m" -RED="${e}[38;5;174m" +if [[ "${COLORTERM:-}" == *truecolor* || "${COLORTERM:-}" == *24bit* ]]; then + DIM="${e}[38;2;125;114;99m" # faint #7d7263 warm taupe + MUTED="${e}[38;2;169;158;144m" # muted #a99e90 + EMBER="${e}[38;2;242;100;48m" # brand #f26430 ember + GREEN="${e}[38;2;103;232;165m" # ok #67e8a5 + RED="${e}[38;2;224;96;90m" # diff-removed (warm red, distinct from ember) +else + DIM="${e}[38;5;245m" + MUTED="${e}[38;5;247m" + EMBER="${e}[38;5;209m" + GREEN="${e}[38;5;114m" + RED="${e}[38;5;174m" +fi SEP=" ${DIM}·${R} " # dir diff --git a/landing/index.html b/landing/index.html index d0f01a0..b539ee2 100644 --- a/landing/index.html +++ b/landing/index.html @@ -10,7 +10,18 @@ /> + + + + + + + + + + + + @@ -600,7 +737,7 @@ Platform Workflow Evidence - Status + Status GitHub ↗ @@ -610,164 +747,806 @@
-
-
-

- forgekit v0.17.0 · - MIT · zero runtime dependencies -

-

- Give every coding agent the same working memory. -

-

- Forgekit turns one source of truth into native configuration for - AI coding tools, then adds proof-carrying lessons, blast-radius - prediction, reuse search, and pre-action guardrails so agents act - from shared evidence instead of fresh guesses. -

- -
- - npm install -g @codewithjuber/forgekit && forge init - -
+
+

+ forgekit v0.17.0 · MIT + · zero runtime dependencies +

+

+ Give every coding agent the same working memory. +

+

+ forgekit turns one source of truth into native configuration for AI + coding tools, then adds proof-carrying lessons, blast-radius + prediction, reuse search, and pre-action guardrails — so agents act + from shared evidence instead of fresh guesses. +

+ -
+ +
+
+ + + + + + + + + + + + + + + + + forge + kit + + + - -
  • - Impact atlas predicts likely files, tests, and blast - radius. + + + + stateless model + + + one context window + + + wiped every call + + + + + + + no memory + + + no foresight + + + + + + the cognitive substrate + + + + + + memory + + + proof-carrying, team-shared + + + + + + + foresight + + + predicts an edit's blast radius + + + + + + + guardrails + + + enforced hooks, not prose + + + + + + + + + + + + one config + + + Claude Code + Codex + Cursor + Gemini + Aider + Copilot + Windsurf + Zed + Continue + + MCP + + + + + + + -
  • -
  • - Lessons carry provenance and gain trust only from oracle - outcomes. + -
  • -
  • - Guardrails compile to Claude Code, Codex, Cursor, Gemini, - Aider, and more. + +
  • - - -
    -
    -
    - 118 msfull pre-action gate from repository benchmarks -
    -
    - 0.43 msblast-radius lookup from benchmark reports -
    -
    - 62.1%measured routing-stage cost saved + ONE BRAIN FOR EVERY AI CODING AGENT + + + + + stateless model + + + one context window + + + wiped every call + + + + + + + no memory + + + no foresight + + + + + + the cognitive substrate + + + + + + memory + + + proof-carrying, team-shared + + + + + + + foresight + + + predicts an edit's blast radius + + + + + + + guardrails + + + enforced hooks, not prose + + + + + + + + + + + + one config + + + Claude Code + Codex + Cursor + Gemini + Aider + Copilot + Windsurf + Zed + Continue + + MCP + + + + + + + + every task passes a fast gate · every verified outcome flows + back into the shared memory + + + author once · zero runtime dependencies · plain-git team + memory + + + +
    + One substrate — memory, foresight, guardrails — compiled to native + config for every agent your team already runs. +
    -
    - 0runtime dependencies in package.json + +
    +
    + 118 ms + full pre-action gate + reports/benchmarks.md +
    +
    + 0.43 ms + blast-radius lookup + reports/benchmarks.md +
    +
    + 62.1% + routing cost saved + white-paper prototype +
    +
    + 0 + runtime dependencies + package.json +
    -
    -
    -

    Platform

    -

    - A cognitive substrate, packaged like developer tooling. -

    -
    -

    - The site uses only repository-backed claims: README, CHANGELOG, - package metadata, benchmark reports, and optional GitHub API - counters on the status page. -

    -
    -
    -
    -
    01
    -

    Cross-tool config

    -

    - Author rules once, then compile native config layers for the - agents your team already uses. -

    +

    Platform

    +

    + A cognitive substrate, packaged like developer tooling. +

    +

    + Every claim here is repository-backed: README, CHANGELOG, package + metadata, benchmark reports, and optional GitHub counters on the + status page. No mock marketing data. +

    +
    +
    + +
    +

    Cross-tool config

    +

    + Author rules once, then compile native config layers for the + agents your team already uses. +

    +
    -
    -
    02
    -

    Proof-carrying memory

    -

    - Every durable lesson includes evidence, provenance, confidence, - decay, and merge behavior. -

    +
    + +
    +

    Proof-carrying memory

    +

    + Every durable lesson carries evidence, provenance, confidence, + decay, and merge behavior. +

    +
    -
    -
    03
    -

    Impact foresight

    -

    - Before editing, Forge predicts affected files and tests so - agents can plan smaller, safer changes. -

    +
    + +
    +

    Impact foresight

    +

    + Before editing, Forge predicts affected files and tests so + agents plan smaller, safer changes. +

    +
    -
    -
    04
    -

    Reuse-first retrieval

    -

    - Agents are pushed toward existing code paths and prior fixes - before inventing another solution. -

    +
    + +
    +

    Reuse-first retrieval

    +

    + Agents are pushed toward existing code paths and prior fixes + before inventing another solution. +

    +
    -
    -
    05
    -

    Guardrails that run

    -

    - Scope, assumptions, anchoring, cost, and secret-redaction checks - are enforced by CLI hooks. -

    +
    + +
    +

    Guardrails that run

    +

    + Scope, assumptions, anchoring, cost, and secret-redaction + checks are enforced by CLI hooks. +

    +
    -
    -
    06
    -

    Team memory through git

    -

    - Knowledge moves with the repository, not with a single chat - transcript or vendor workspace. -

    +
    + +
    +

    Team memory through git

    +

    + Knowledge moves with the repository, not with a single chat + transcript or vendor workspace. +

    +
    @@ -778,7 +1557,7 @@

    Team memory through git

    Workflow

    - Install once. Gate every meaningful agent action. + Install once. Gate every meaningful action.

    The CLI stays small and dependency-free, while optional live @@ -787,17 +1566,17 @@

    - 1 +

    Initialize the substrate

    - Run the installer and generate project-local agent + Run the installer to generate project-local agent instructions, hooks, and memory stores.

    - 2 +

    Let agents plan with evidence

    @@ -807,88 +1586,111 @@

    Let agents plan with evidence

    - 3 +

    Promote only verified lessons

    Tests and human corrections update memory; self-graded - claims do not become trusted facts. + claims never become trusted facts.

    -
    -
    - forge substrate -
    -
    $ forge substrate "Change auth validation and update tests"
    - loaded team rules from AGENTS.md
    - found reuse candidates before implementation
    - predicted blast radius and test targets
    -! assumption requires citation before action
    - emits native config for agent tools
    -
    +
    +
    forge substrate — example gate output
    +
    $ forge substrate "Change auth validation and update tests"
    + loaded team rules from AGENTS.md
    + found reuse candidates before implementation
    + predicted blast radius and test targets
    +! assumption requires citation before action
    + emitted native config for agent tools
    +
    -
    -
    -

    Evidence

    -

    - Professional, but honest about limits. -

    -
    -

    - The strongest design choice is restraint: measured numbers are - labeled as measured, targets stay targets, and generated pages - document their sources. -

    -
    +

    Evidence

    +

    Professional, but honest about limits.

    +

    + The strongest design choice is restraint: measured numbers are + labeled as measured, targets stay targets, and generated pages + document their sources. +

    -
    -
    -

    Approximate impact atlas

    -

    - Import-graph predictions are useful for gating and planning, not - presented as perfect dependency analysis. -

    +
    + +
    +

    Approximate impact atlas

    +

    + Import-graph predictions are useful for gating and planning, + not presented as perfect dependency analysis. +

    +
    -
    -
    %
    -

    Cost claims stay scoped

    -

    - The page shows the measured routing-stage savings rather than - blending measured data with aspirational targets. -

    +
    + +
    +

    Cost claims stay scoped

    +

    + The page shows measured routing-stage savings rather than + blending measured data with aspirational targets. +

    +
    -
    -
    -

    Data sources

    -

    No mock marketing data.

    -

    - Every metric and product claim on this site comes from local - repository files or the optional public GitHub repository endpoint - used by the generated status page. -

    -
    -
    -
      -
    • package.json
    • -
    • README.md
    • -
    • CHANGELOG.md
    • -
    • reports/benchmarks.md
    • -
    • https://api.github.com/repos/CodeWithJuber/forgekit
    • -
    -
    +
    +

    Data sources

    +

    No mock marketing data.

    +

    + Every metric and product claim on this site comes from local + repository files or the optional public GitHub repository endpoint + used by the generated status page. +

    +
      +
    • package.json
    • +
    • README.md
    • +
    • CHANGELOG.md
    • +
    • reports/benchmarks.md
    • +
    • https://api.github.com/repos/CodeWithJuber/forgekit
    • +
    @@ -908,7 +1710,7 @@

    No mock marketing data.

    href="https://github.com/CodeWithJuber/forgekit/blob/master/SECURITY.md" >Security - Status + Status diff --git a/package.json b/package.json index 9114b7c..dd7b283 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@codewithjuber/forgekit", "version": "0.17.0", - "description": "One brain for every AI coding agent \u2014 the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more.", + "description": "One brain for every AI coding agent — the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more.", "type": "module", "bin": { "forge": "src/cli.js" @@ -48,7 +48,6 @@ "ONBOARDING.md", "CHANGELOG.md", "LICENSE", - "public", "scripts" ], "scripts": { diff --git a/public/index.html b/public/index.html deleted file mode 100644 index c243747..0000000 --- a/public/index.html +++ /dev/null @@ -1,45 +0,0 @@ -forgekit status — live repository data

    @codewithjuber/forgekit · v0.10.0 · Node >=20

    Live status, straight from the repository.

    One brain for every AI coding agent — the cognitive substrate every frozen model is missing (proof-carrying memory, impact foresight, enforced guardrails), authored once and delivered as native config to Claude Code, Codex, Cursor, Gemini, Aider, and more.

    Install in 60 seconds Read the docs

    MIT license0 runtime dependenciesclaude/forge-work-system-setup-p26ka5 @ 31eb862live GitHub stats disabled
    0.43 ms
    blast-radius lookup

    Measured from this repo's benchmark report, not a marketing placeholder.

    reports/benchmarks.md

    118 ms
    pre-action gate

    Assumptions, routing, reuse, context, impact, scope, and anchoring.

    reports/benchmarks.md

    62.1%
    cost saved

    Documented from the white-paper prototype and exposed by Forge cost reports.

    whitepaper prototype

    Quickstart

    npm install -g @codewithjuber/forgekit -forge init -forge doctor -forge substrate "Change auth validation and update tests"

    Latest repo changes

    • forge stack — dynamic stack detection: reads the repo's dependency manifests (package.json, pyproject.toml, go.mod, Cargo.toml, Gemfile, composer.json, pom.xml/build.gradle, *.csproj) and reports its real languages, frameworks, package managers, and test commands — data-driven (extend by adding a SIGNATURES row), not a hardcoded menu. The detected test commands now drive the substrate's verification checklist instead of assuming npm.
    • Six more atlas languages — Ruby, C#, PHP, Kotlin, Swift, and C/C++ join JS/TS, Python, Go, Rust, and Java (whose method defs are now indexed too). One RULES table; the walk, completion gate, and docs sweep pick each up automatically.
    • forge update — self-update: --check reports whether a newer version is available (commits behind upstream, from a cached hourly fetch), bare applies it (git pull --ff-only for a checkout, or the npm i -g command otherwise). forge doctor surfaces a non-nagging "update available" notice; FORGE_NO_UPDATE_CHECK=1 silences it. Fail-open: offline / non-git / detached-HEAD never error.
    • Self-dogfood — a committed .claude/settings.json wires forgekit's own guards via ${CLAUDE_PROJECT_DIR}, so the repo runs its own completion gate, cortex, and guards during local dev without a marketplace install.

    Benchmark sections indexed: 3 · benchmarks file updated 2026-07-10.

    Data Sources

    No mock data is used. This page is regenerated from repository files during CI (generated 2026-07-11T01:43:16.194Z from 31eb862). Enable BUILD_PAGES_LIVE=1 to refresh public GitHub counters with ETag/Last-Modified caching.

    • package.json
    • README.md
    • CHANGELOG.md
    • reports/benchmarks.md
    • https://api.github.com/repos/CodeWithJuber/forgekit (optional, no auth, only when BUILD_PAGES_LIVE=1)
    WCAG-minded semantic HTML, keyboard focus, responsive 320px–1920px+, and reduced-motion-safe. Same design tokens as the landing page — parity enforced in test/pages.test.js.
    \ No newline at end of file diff --git a/scripts/build-pages.mjs b/scripts/build-pages.mjs index f4a7387..b66d351 100644 --- a/scripts/build-pages.mjs +++ b/scripts/build-pages.mjs @@ -5,6 +5,12 @@ import { execFileSync } from "node:child_process"; import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { BRAND, rootTokensCss } from "../src/brand.js"; + +// The deployed site base (no trailing slash), from the single brand.json source. +// Absolute URLs are required for og:image/canonical and must resolve on the Pages +// project site (/forgekit/...), so both public surfaces derive them from here. +const SITE = (BRAND.site?.url ?? "").replace(/\/+$/, ""); const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const out = join(root, "public", "index.html"); @@ -22,9 +28,18 @@ const readme = read("README.md"); const changelog = read("CHANGELOG.md"); const benchmarks = read("reports/benchmarks.md"); -function lineMatch(text, re, fallback = "") { +// A repo-sourced metric MUST parse from the README — a silent fallback is how the +// site drifted into showing hardcoded numbers the README no longer stated. If the +// phrasing changes, fail the build loudly so the regex (or the README) gets fixed. +function mustMatch(text, re, label) { const m = text.match(re); - return m?.[1]?.trim() ?? fallback; + if (!m?.[1]?.trim()) { + throw new Error( + `build-pages: metric "${label}" no longer matches ${re} in README.md — ` + + `update the regex or the README (no silent fallback).`, + ); + } + return m[1].trim(); } function latestChanges() { // Walk version sections in order and use the first one that actually has bullets — @@ -146,10 +161,9 @@ export async function collect({ live = process.env.BUILD_PAGES_LIVE === "1" } = branch: git(["branch", "--show-current"]), generated: new Date().toISOString(), github, - claim: lineMatch(readme, /\*\*(Author[\s\S]*?)\*\*/m, pkg.description), - speed: lineMatch(readme, /\*\*A full pre-action gate in ([^*]+)\*\*/m, "118 ms"), - impact: lineMatch(readme, /answers in \*\*([^*]+)\*\*/m, "0.43 ms"), - saved: lineMatch(readme, /measured \*\*([^*]+)\*\*/m, "62.1% cost saved"), + speed: mustMatch(readme, /\*\*A full pre-action gate in ([^*]+)\*\*/m, "speed"), + impact: mustMatch(readme, /\*\*Blast radius in ([^*]+)\*\*/m, "impact"), + saved: mustMatch(readme, /\*\*([\d.]+% cost saved)/m, "saved"), benchUpdated: statSync(join(root, "reports/benchmarks.md")).mtime.toISOString().slice(0, 10), latest: latestChanges(), benchMentions: (benchmarks.match(/^## /gm) ?? []).length, @@ -163,45 +177,57 @@ export function render(d) { const live = d.github ? `${esc(d.github.stars)} stars${esc(d.github.forks)} forks${esc(d.github.issues)} open issues` : `live GitHub stats disabled`; - return `forgekit status — live repository data

    ${esc(d.name)} · v${esc(d.version)} · Node ${esc(d.node)}

    Live status, straight from the repository.

    ${esc(d.description)}

    Install in 60 seconds Read the docs

    ${esc(d.license)} license${esc(d.deps)} runtime dependencies${esc(d.branch)} @ ${esc(d.commit)}${live}
    ${esc(d.impact)}
    blast-radius lookup

    Measured from this repo's benchmark report, not a marketing placeholder.

    reports/benchmarks.md

    ${esc(d.speed)}
    pre-action gate

    Assumptions, routing, reuse, context, impact, scope, and anchoring.

    reports/benchmarks.md

    ${esc(d.saved.match(/^[\d.]+\s*%?/)?.[0] ?? d.saved)}
    ${esc(d.saved.replace(/^[\d.]+\s*%?\s*/, "") || "routing signal")}

    Documented from the white-paper prototype and exposed by Forge cost reports.

    whitepaper prototype

    Quickstart

    npm install -g @codewithjuber/forgekit diff --git a/src/brand.js b/src/brand.js index 318942e..a1a738d 100644 --- a/src/brand.js +++ b/src/brand.js @@ -23,3 +23,31 @@ export function fill(template) { .replaceAll("{pkg}", brand.pkg) .replaceAll("{home}", brand.home); } + +// --------------------------------------------------------------------------- +// Design tokens — brand.json.colors is the SINGLE source for the visual palette. +// Both public web surfaces (landing/index.html + the build-pages status page) and +// test/pages.test.js derive from here, so the palette can't fork into "two palettes +// claiming to be one" again. DATA (the hexes) lives in brand.json; the CSS emission +// is a pure formula over that data. +// --------------------------------------------------------------------------- + +/** CSS custom-property declarations for one color scheme ("dark" | "light"), + * e.g. `--bg:#171310;--panel:#201a15;…`. Keys mirror brand.json.colors. */ +export function cssVars(scheme = "dark") { + const palette = brand.colors?.[scheme] ?? brand.colors?.dark ?? {}; + return Object.entries(palette) + .map(([k, v]) => `--${k}:${v}`) + .join(";"); +} + +/** The full `:root` token block (dark) + a `prefers-color-scheme: light` override, + * plus the shared font stacks. Emitted verbatim into the generated status page and + * reconciled against the hand-authored landing page by test/pages.test.js. */ +export function rootTokensCss() { + const fonts = `--sans:${brand.fonts.sans};--mono:${brand.fonts.mono}`; + return ( + `:root{color-scheme:dark;${cssVars("dark")};${fonts}}` + + `@media(prefers-color-scheme:light){:root{color-scheme:light;${cssVars("light")}}}` + ); +} diff --git a/src/docs_check.js b/src/docs_check.js index 4e48a05..85593a8 100644 --- a/src/docs_check.js +++ b/src/docs_check.js @@ -200,6 +200,24 @@ function checkDiagrams(root, issues) { severity: "error", detail: `${rel}: a mermaid diagram has no branded \`%%{init\` theme — it renders in Mermaid's off-brand default`, }); + } else { + // Presence of `%%{init` isn't enough — the theme must carry the actual brand + // VALUES from the single color source (brand.json.colors.dark), or a diagram can + // declare a theme and still render in off-brand colors. Require the load-bearing + // identity hexes: the ember accent and the warm-black canvas. + const dark = BRAND.colors?.dark ?? {}; + for (const [role, hex] of [ + ["ember accent (lineColor)", dark.brand], + ["warm-black canvas (tertiaryColor)", dark.bg], + ]) { + if (hex && !block.toLowerCase().includes(hex.toLowerCase())) { + issues.push({ + check: "diagrams", + severity: "error", + detail: `${rel}: a mermaid \`%%{init\` theme is missing the brand ${role} \`${hex}\` — it declares a theme but not the brand.json colors`, + }); + } + } } if (block.includes("\\n")) { issues.push({ diff --git a/test/docs_check.test.js b/test/docs_check.test.js index 32d0472..d0dd228 100644 --- a/test/docs_check.test.js +++ b/test/docs_check.test.js @@ -116,9 +116,13 @@ test("docsCheck: an undocumented MCP tool is flagged", () => { assert.ok(r.issues.some((i) => i.check === "mcp-tools" && i.detail.includes(TOOLS[0].name))); }); +// A theme that carries the actual brand color VALUES (ember #f26430 + warm-black +// #171310), not just the `%%{init` directive. +const BRAND_INIT = + "%%{init: {'theme':'base','themeVariables':{'primaryColor':'#201a15','lineColor':'#f26430','tertiaryColor':'#171310'}}}%%"; + test("docsCheck: a branded mermaid diagram with
    passes; diagrams is a checked dimension", () => { - const good = - "```mermaid\n%%{init: {'theme':'base'}}%%\nflowchart LR\n A[\"a
    b\"] --> B[\"c\"]\n```\n"; + const good = `\`\`\`mermaid\n${BRAND_INIT}\nflowchart LR\n A["a
    b"] --> B["c"]\n\`\`\`\n`; const r = docsCheck({ root: fixtureRoot((f) => ({ ...f, @@ -129,6 +133,20 @@ test("docsCheck: a branded mermaid diagram with
    passes; diagrams is a chec assert.ok(r.checked.includes("diagrams")); }); +test("docsCheck: a mermaid theme missing the brand color values is flagged", () => { + // Has `%%{init` but not the brand.json hexes — declares a theme, renders off-brand. + const bad = "```mermaid\n%%{init: {'theme':'base'}}%%\nflowchart LR\n A --> B\n```\n"; + const root = fixtureRoot((f) => ({ + ...f, + "ARCHITECTURE.md": `${f["ARCHITECTURE.md"]}\n${bad}`, + })); + const r = docsCheck({ root }); + assert.ok( + r.issues.some((i) => i.check === "diagrams" && /missing the brand/.test(i.detail)), + "a theme without the brand hexes is flagged", + ); +}); + test("docsCheck: an unstyled mermaid diagram (no branded theme) is flagged", () => { const bad = "```mermaid\nflowchart LR\n A --> B\n```\n"; const root = fixtureRoot((f) => ({ diff --git a/test/pages.test.js b/test/pages.test.js index 9d1f963..0b3e303 100644 --- a/test/pages.test.js +++ b/test/pages.test.js @@ -3,6 +3,7 @@ import { readFileSync } from "node:fs"; import { test } from "node:test"; import { fileURLToPath } from "node:url"; import { collect, render } from "../scripts/build-pages.mjs"; +import { BRAND } from "../src/brand.js"; const repo = (rel) => readFileSync(fileURLToPath(new URL(`../${rel}`, import.meta.url)), "utf8"); const landing = repo("landing/index.html"); @@ -22,12 +23,23 @@ test("pages renderer uses repo data and accessible landmarks", async () => { // loaded, an empty changes list, hardcoded metrics). Each assertion below is a defect that // silently returned before nothing checked it. -test("landing + status share the SAME core brand tokens (one palette, not two)", async () => { +test("landing + status derive the SAME palette from brand.json (one source, dark+light)", async () => { const status = render(await collect({ live: false })); - // The warm ember/near-black set both pages must carry verbatim. - for (const hex of ["#171310", "#201a15", "#272019", "#372c22", "#f26430", "#f2ede7"]) { - assert.ok(landing.includes(hex), `landing page is missing shared token ${hex}`); - assert.ok(status.includes(hex), `status page is missing shared token ${hex}`); + // brand.json.colors is the single source of the palette. Every hex it defines — for BOTH + // schemes — must appear verbatim on both public pages. Change a hex there and this fails + // until every surface is updated, which is what makes brand.json the source of truth. + const hexes = (palette) => Object.values(palette).filter((v) => v.startsWith("#")); + for (const [scheme, palette] of Object.entries(BRAND.colors)) { + for (const hex of hexes(palette)) { + assert.ok( + landing.includes(hex), + `landing missing ${scheme} token ${hex} (brand.json.colors.${scheme})`, + ); + assert.ok( + status.includes(hex), + `status missing ${scheme} token ${hex} (brand.json.colors.${scheme})`, + ); + } } }); @@ -67,6 +79,66 @@ test("landing benchmark metrics are numbers reports/benchmarks.md actually measu assert.ok(measured.has(`${n} ms`), `landing claims ${n} ms but no benchmark row measures it`); }); +// Metadata + freshness enforcement — each assertion below is a defect this change +// fixed (blank social cards, missing favicon, canonical/og drift, stale landing +// version, a repaint-heavy nav blur, the generated status page shipping in the +// tarball). They stay fixed because the test fails the moment they regress. + +test("both public pages ship social image + favicon (no blank cards)", async () => { + const status = render(await collect({ live: false })); + for (const [name, html] of [ + ["landing", landing], + ["status", status], + ]) { + assert.match( + html, + /property="og:image"[^>]*content="https:\/\/[^"]+\.png"/, + `${name}: absolute og:image`, + ); + assert.match( + html, + /name="twitter:image"[^>]*content="https:\/\/[^"]+\.png"/, + `${name}: twitter:image`, + ); + assert.match(html, /rel="icon"[^>]*image\/svg/, `${name}: svg favicon`); + assert.match(html, /rel="apple-touch-icon"/, `${name}: apple-touch-icon`); + } +}); + +test("canonical == og:url on both pages", async () => { + const status = render(await collect({ live: false })); + for (const [name, html] of [ + ["landing", landing], + ["status", status], + ]) { + const canon = html.match(/rel="canonical"\s+href="([^"]+)"/)?.[1]; + const ogUrl = html.match(/property="og:url"\s+content="([^"]+)"/)?.[1]; + assert.ok(canon, `${name}: has canonical`); + assert.equal(canon, ogUrl, `${name}: canonical must equal og:url`); + } +}); + +test("landing states the current package version, never a stale one", () => { + const { version } = JSON.parse(repo("package.json")); + const shown = [...landing.matchAll(/forgekit v(\d+\.\d+\.\d+)/g)].map((m) => m[1]); + assert.ok(shown.length > 0, "landing states its version"); + for (const v of shown) + assert.equal(v, version, `landing shows v${v}, package.json is ${version}`); +}); + +test("sticky-nav blur stays compositor-light (<=8px)", () => { + for (const [, px] of landing.matchAll(/backdrop-filter:\s*blur\((\d+)px\)/g)) + assert.ok(Number(px) <= 8, `backdrop blur ${px}px > 8px is repaint-heavy on scroll`); +}); + +test("the generated status page is not shipped in the npm tarball", () => { + const { files } = JSON.parse(repo("package.json")); + assert.ok( + !files.includes("public"), + "public/ is a build artifact (regenerated at deploy), not a shipped file", + ); +}); + test("pages optional integration can validate live GitHub data", async (t) => { if (process.env.RUN_INTEGRATION !== "1") { t.skip("set RUN_INTEGRATION=1 to hit GitHub API"); diff --git a/test/statusline.test.js b/test/statusline.test.js new file mode 100644 index 0000000..4bba4f9 --- /dev/null +++ b/test/statusline.test.js @@ -0,0 +1,62 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { test } from "node:test"; +import { fileURLToPath } from "node:url"; + +// Smoke-test the status line the way Claude Code invokes it: sample session JSON on +// stdin, ANSI segments on stdout. Guards the brand-truecolor palette, the segment +// structure, and the truecolor->256 fallback so a shell edit can't silently break it. +const script = fileURLToPath(new URL("../global/statusline.sh", import.meta.url)); + +const SAMPLE = JSON.stringify({ + workspace: { current_dir: "/tmp/project" }, + model: { display_name: "Opus 4.8" }, + cost: { total_cost_usd: 0.42, total_lines_added: 12, total_lines_removed: 3 }, + exceeds_200k_tokens: true, + current_usage: { + cache_read_input_tokens: 900, + cache_creation_input_tokens: 100, + }, +}); + +const run = (json, env = {}) => + execFileSync("bash", [script], { + input: json, + encoding: "utf8", + env: { ...process.env, ...env }, + }); + +test("statusline renders every segment from sample JSON", () => { + const out = run(SAMPLE, { COLORTERM: "truecolor" }); + assert.match(out, /Opus 4\.8/, "model name"); + assert.match(out, /\$0\.420/, "session cost"); + assert.match(out, /\+12/, "lines added"); + assert.match(out, /-3/, "lines removed"); + assert.match(out, /90%/, "cache-hit rate = 900/(900+100)"); + assert.match(out, /ctx>200k/, "over-200k context warning"); + // Separators are " ·(dim) " — the middle dot is wrapped in ANSI, so match the char. + assert.ok((out.match(/·/g) ?? []).length >= 5, "middle-dot separators between segments"); +}); + +const ESC = String.fromCharCode(27); // avoid a control char in a regex literal + +test("statusline uses exact brand hexes in 24-bit truecolor", () => { + const out = run(SAMPLE, { COLORTERM: "truecolor" }); + // ember #f26430 = 242,100,48 — the brand accent, not the nearest xterm-256 index. + assert.ok(out.includes(`${ESC}[38;2;242;100;48m`), "ember truecolor"); + assert.ok(out.includes(`${ESC}[38;2;103;232;165m`), "ok/green truecolor #67e8a5"); + assert.ok(out.includes(`${ESC}[38;2;125;114;99m`), "faint taupe truecolor #7d7263"); +}); + +test("statusline falls back to 256-color when truecolor is unavailable", () => { + const out = run(SAMPLE, { COLORTERM: "" }); + assert.ok(out.includes(`${ESC}[38;5;209m`), "256-color ember fallback"); + assert.ok(!out.includes("38;2;242;100;48"), "no truecolor sequence without COLORTERM"); +}); + +test("statusline degrades gracefully on minimal input", () => { + // Missing cost/diff/cache fields must not crash or emit 'null' segments. + const out = run(JSON.stringify({ model: { display_name: "Haiku" } })); + assert.match(out, /Haiku/); + assert.doesNotMatch(out, /null/, "no literal null leaks into the line"); +});