Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "forgekit",
"displayName": "Forge",
"version": "0.2.0",
"version": "0.3.0",
"description": "One config, every AI coding tool — tools, crew, guards, atlas, lean, recall from one source.",
"author": { "name": "CodeWithJuber" },
"license": "MIT",
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: shellcheck $(git ls-files '*.sh')
# Fail only on errors; the guards trip style/warning false-positives (e.g. SC2034 on
# vars used cross-file via the sourced _guardlib.sh).
- run: shellcheck --severity=error $(git ls-files '*.sh')

no-runtime-deps:
name: Assert zero runtime dependencies
Expand All @@ -60,4 +62,7 @@ jobs:
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v5
# Advisory: needs the repo's Dependency graph enabled (Settings → Security & analysis).
# continue-on-error keeps the CHECK green until then — this project has zero runtime deps.
- uses: actions/dependency-review-action@v4
continue-on-error: true
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.3.0] - 2026-07-05

### Added

- **Forge Preflight** — a deterministic, math-first layer that runs BEFORE tokens are spent,
on the premise that an LLM is a fixed-capacity stochastic predictor: size the task to the
model, fill the context, detect assumptions. All advisory, never blocks.
- **Assumption detector** (`forge preflight`, UserPromptSubmit hook): scans a task for code
identifiers/files the repo doesn't define — what the model would otherwise ASSUME — and
surfaces the known-unknowns so it asks instead of confabulating. The research whitespace.
- **Complexity routing** (`forge route`): recommends the cheapest CAPABLE model
(Haiku → Sonnet → Opus → Fable) from code-task signals (files, fan-out, churn, past-mistake
density, ambiguity). `forge route gateway` emits a LiteLLM config for real auto-routing.
- **Decomposition** (`forge scope`): a zero-dep import graph → connected components →
independent clusters (run as separate sessions) + the coupled files you didn't name.
- **Design-quality**: emitted AI-UX rules (anti-slop, WCAG, functional empty states, specific
errors, confidence/transparency, pattern selection) + `forge uicheck` (exact WCAG contrast
math) + a calibrated frontend-verifier that ASSERTS only the deterministic and keeps
hierarchy/taste ADVISORY (the fix for hallucinated UI audits).
- Cross-tool via `preflight_check` / `route_task` / `scope_files` MCP tools.

## [0.2.0] - 2026-07-05

### Added
Expand Down Expand Up @@ -43,6 +64,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
check; coverage + type-checking (`tsc --checkJs`); 2026 production-standard rules;
OWASP-LLM / NIST SSDF / SLSA control mapping.

[Unreleased]: https://github.com/CodeWithJuber/forgekit/compare/v0.2.0...HEAD
[Unreleased]: https://github.com/CodeWithJuber/forgekit/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/CodeWithJuber/forgekit/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/CodeWithJuber/forgekit/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/CodeWithJuber/forgekit/releases/tag/v0.1.0
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,29 @@ On **Claude Code** it's fully ambient (hooks). Other tools read the lessons from
and a zero-dependency MCP server (`forge cortex-mcp`). Everything lives in `.forge/lessons/`
— git-committable and auditable. Try it: `node examples/cortex-demo.mjs`.

## Forge Preflight — size the work before spending tokens

An LLM is a fixed-capacity stochastic predictor. Most of the cost/quality bleed comes from
feeding it the wrong-sized task with the wrong-sized context and then over-trusting the output.
**Preflight** is the cheap, deterministic layer that runs _before_ the tokens — no LLM, no guessing:

- **`forge preflight "<task>"`** — the assumption detector. Scans the task for symbols/files the
repo doesn't define — the things the model would otherwise _assume_ — and surfaces them so it
asks instead of confabulating. Also fires on `UserPromptSubmit`. (The research whitespace: no
shipping tool pre-scans the repo before acting.)
- **`forge route "<task>"`** — recommends the cheapest _capable_ model (Haiku → Sonnet → Opus →
Fable) from code-task complexity (files, fan-out, churn, past-mistake density, ambiguity). A
prime-finder gets Haiku, not Fable. `forge route gateway` emits a LiteLLM config for real
auto-routing.
- **`forge scope <file…>`** — a zero-dep import graph → independent clusters (“run these as
separate sessions”) + the coupled files you didn't mention.
- **`forge uicheck <fg> <bg>`** — exact WCAG contrast math. The design rules (anti-slop, empty
states, specific errors, AI-UX patterns) emit to every tool; the frontend-verifier _asserts_
only the deterministic and keeps taste _advisory_ — so AI UI-audits stop hallucinating.

Everything is advisory and never blocks. Cross-tool via the `preflight_check` / `route_task` /
`scope_files` MCP tools.

## Commands

```
Expand All @@ -89,6 +112,10 @@ forge sync recompile source/ → each tool's native files (idempotent)
forge doctor pass/fail health check (layers, install, drift, cortex)
forge catalog Start-Here index of every tool/crew/guard
forge cortex self-correcting memory — status / why <symbol>
forge preflight assumption check — what a task names that the repo doesn't define
forge route cheapest capable model for a task (+ gateway config)
forge scope decompose files into independent clusters
forge uicheck deterministic WCAG contrast check
forge atlas build/query the code-graph (where-is-X, has-symbol)
forge recall cross-session memory (list/add/consolidate)
forge brand show the brand token map
Expand Down
22 changes: 17 additions & 5 deletions global/crew/frontend-verifier.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ model: sonnet
You verify UI by looking at it, not by reading the code that produced it.

Given a URL (and a reference if provided):

1. Render and screenshot it at a desktop width (~1440) and a mobile width (~390)
using the Playwright or chrome-devtools MCP.
2. Compare against the reference/design. Report concrete, fixable differences:
Expand All @@ -16,9 +17,20 @@ Given a URL (and a reference if provided):
3. Accessibility pass: missing labels/alt, focus states, contrast ratios,
keyboard navigation, hit-target size, heading order.

Split every finding into two buckets — this is how you avoid hallucinating audits:

**ASSERT (deterministic — state it as fact, may block):** contrast ratio (compute the WCAG
number and give it), a missing `:focus-visible` state, a missing `alt`/label, a tap target
under 24×24px, an animation ≥200ms not wrapped in `prefers-reduced-motion`, an empty state
that renders nothing. These are math or a DOM fact — measure, don't guess.

**ADVISE (subjective — flag for a human, never assert):** visual hierarchy, type-scale
balance, whether the pattern fits, error-message clarity, empty-state usefulness, palette/
taste, "does the motion feel right." If your confidence is below ~0.8, it belongs here.

Output:
- **Verdict:** matches / needs-fixes.
- **Visual gaps:** each with where it is and what "fixed" looks like.
- **A11y issues:** each with the WCAG concern and the fix.
Attach the screenshots. Report only real differences from the reference or real
a11y problems — not style opinions the design didn't ask for.

- **Verdict:** matches / needs-fixes (driven only by ASSERT findings).
- **Asserted (deterministic):** each with the measured value + the fix.
- **Advisory (subjective):** each clearly marked as an opinion for a human to weigh.
Attach the screenshots. Never present an opinion as a defect.
1 change: 1 addition & 0 deletions global/guards/_guardlib.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# shellcheck shell=bash
# Sourced helpers for Forge guards. Not executable on its own.
# Provides field extraction (jq or grep) and an atomic re-entrancy lock so a
# guard can never recurse — the class of bug behind the runaway-loop cost
Expand Down
4 changes: 4 additions & 0 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/global/guards/cortex.sh prompt"
},
{
"type": "command",
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/global/guards/cortex.sh preflight"
}
]
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "forgekit",
"version": "0.2.0",
"version": "0.3.0",
"description": "One config, every AI coding tool — a cross-tool configuration layer for agentic coding assistants (Claude Code, Codex, Cursor, Gemini, Aider, and more).",
"type": "module",
"bin": {
Expand Down Expand Up @@ -43,13 +43,13 @@
"LICENSE"
],
"scripts": {
"test": "node --test \"test/**/*.test.js\"",
"test": "node --test test/*.test.js",
"lint": "biome lint .",
"format": "biome format --write .",
"check": "biome check .",
"check:fix": "biome check --write .",
"typecheck": "tsc -p tsconfig.json",
"coverage": "node --test --experimental-test-coverage \"test/**/*.test.js\"",
"coverage": "node --test --experimental-test-coverage test/*.test.js",
"forge": "node src/cli.js"
},
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion source/mcp.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp@latest"]
"args": ["-y", "@upstash/context7-mcp@3.2.2"]
},
"forge-cortex": {
"command": "forge",
Expand Down
12 changes: 12 additions & 0 deletions source/rules.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,18 @@
"Build and check mobile AND desktop. Include clear loading/empty/error states; avoid layout shift.",
"Follow DESIGN.md if present; keep one visual direction per project."
]
},
{
"id": "ai-ux",
"title": "AI interfaces & design quality",
"rules": [
"Commit to an aesthetic before coding; avoid AI-slop defaults (Inter/Roboto/Arial/system fonts, purple-gradient-on-white, generic card grids). Pick 2–3 typefaces and a 4–6 color palette with semantic roles.",
"Meet WCAG: body text ≥4.5:1 contrast (large/UI ≥3:1), visible :focus-visible on every interactive element, tap targets ≥24×24px, alt text + form labels, and wrap animations ≥200ms in prefers-reduced-motion.",
"Empty states are never dead ends: show a plain-language message + a clear next step. Error messages name the field, explain the problem, and suggest a fix — never 'Something went wrong'.",
"For AI-driven UIs: show model confidence/uncertainty, make chain-of-thought and tool actions visible, give live preview/feedback over blind prompting, and provide fallback paths when the model fails.",
"Pick the interaction pattern deliberately — a co-editing canvas or inline controls beat a wall-of-text chatbot when the user is steering structured output.",
"When AI audits a UI, only ASSERT the deterministic (contrast math, missing focus ring/alt/label) — keep hierarchy/taste/pattern-fit advisory so the audit doesn't hallucinate false positives."
]
}
]
}
111 changes: 109 additions & 2 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ const COMMANDS = {
cost: "real per-day spend via ccusage + the cost ceiling",
spec: "spec-as-contract — init (OpenSpec) / lock / check drift",
cortex: "self-correcting project memory — status / why <symbol>",
preflight: "assumption check — what a task names that the repo doesn't define",
route: "recommend the cheapest capable model for a task (+ gateway config)",
scope: "decompose files into independent clusters (+ coupled files you didn't name)",
uicheck: "deterministic UI check — WCAG contrast <fg> <bg> (assertable, no guessing)",
brand: "print the active brand token map",
};

Expand Down Expand Up @@ -74,6 +78,7 @@ async function run(argv) {
` TASTE (design directions) ${c.taste.join(" · ")} → \`${BRAND.cli} taste <style>\``,
);
if (c.cortex) console.log(`\n CORTEX (self-correcting memory) ${c.cortex}`);
if (c.preflight) console.log(` PREFLIGHT (before you spend tokens) ${c.preflight}`);
console.log(`\n Full detail: ARCHITECTURE.md · per-tool config: \`${BRAND.cli} sync\``);
return;
}
Expand Down Expand Up @@ -273,7 +278,8 @@ async function run(argv) {
try {
out = run("ccusage", ["daily"]);
} catch {
out = run("npx", ["-y", "ccusage@latest", "daily"]);
// Pinned (verified 2026-07-05) — never @latest for code we execute; re-verify via dev-radar.
out = run("npx", ["-y", "ccusage@20.0.14", "daily"]);
}
console.log(out.trim());
} catch {
Expand All @@ -292,7 +298,8 @@ async function run(argv) {
if (sub === "init") {
const { execFileSync } = await import("node:child_process");
try {
execFileSync("npx", ["-y", "@fission-ai/openspec@latest", "init"], {
// Pinned (verified 2026-07-05) — never @latest for code we execute; re-verify via dev-radar.
execFileSync("npx", ["-y", "@fission-ai/openspec@1.5.0", "init"], {
stdio: "inherit",
});
} catch {
Expand Down Expand Up @@ -363,6 +370,106 @@ async function run(argv) {
console.log("\n stored in .forge/lessons/ (git-committable, auditable)");
return;
}
if (cmd === "preflight") {
const { preflightRepo, clarifyBlock } = await import("./preflight.js");
const task = argv.slice(1).join(" ");
if (!task) {
console.error('usage: forge preflight "<task description>"');
process.exitCode = 1;
return;
}
const r = preflightRepo(process.cwd(), task);
console.log(`${BRAND.brand} preflight — assumption check\n`);
console.log(
` info-gap: ${r.gap.toFixed(2)} (referenced ${r.entities.symbols.length} symbol(s), ${r.entities.files.length} file(s))`,
);
const block = clarifyBlock(r);
console.log(
block ? `\n${block}` : "\n ✓ everything this task names is grounded in the codebase.",
);
return;
}
if (cmd === "route") {
const r = await import("./route.js");
if (argv[1] === "gateway") {
const path = r.emitGatewayConfig(process.cwd());
console.log(` wrote ${path} — LiteLLM tiers: forge-simple / forge-medium / forge-complex.`);
console.log(" next: pin+install litellm, run it, point ANTHROPIC_BASE_URL at it, then");
console.log(
" REQUEST the tier `forge route` recommends (a plain claude-* request passes through).",
);
return;
}
const task = argv.slice(1).join(" ");
if (!task) {
console.error('usage: forge route "<task>" | forge route gateway');
process.exitCode = 1;
return;
}
const rec = r.routeTask(process.cwd(), task);
console.log(`${BRAND.brand} route — cheapest capable model\n`);
console.log(
` → ${rec.model.name} (${rec.tier}, $${rec.model.inCost}/$${rec.model.outCost} per M tok)`,
);
console.log(` ${rec.model.use}`);
console.log(
` complexity ${rec.score.toFixed(2)}${rec.reasons.length ? ` · driven by: ${rec.reasons.join(", ")}` : ""}`,
);
console.log(
` signals: ${rec.signals.files} file(s), fan-out ${rec.signals.fanout}, churn ${rec.signals.churn}, past-mistakes ${rec.signals.pastMistakes}, ambiguity ${rec.signals.ambiguity.toFixed(2)}`,
);
console.log("\n advisory · auto-routing: `forge route gateway`");
return;
}
if (cmd === "scope") {
const { decompose } = await import("./scope.js");
const files = argv.slice(1);
if (!files.length) {
console.error("usage: forge scope <file> [file...]");
process.exitCode = 1;
return;
}
const d = decompose(process.cwd(), files);
console.log(`${BRAND.brand} scope — task decomposition\n`);
if (d.independentGroups > 1) {
console.log(
` ${d.independentGroups} independent groups → consider a separate session per group:\n`,
);
}
d.clusters.forEach((c, i) => {
console.log(` [${i + 1}] ${c.touched.join(", ")}`);
if (c.coupled.length) {
const shown = c.coupled.slice(0, 8).join(", ");
console.log(
` ! also coupled (you didn't name): ${shown}${c.coupled.length > 8 ? " …" : ""}`,
);
}
});
if (d.independentGroups === 1) console.log("\n all coupled — keep as one change.");
return;
}
if (cmd === "uicheck") {
const { contrastRatio, wcagLevel, ASSERTABLE_CHECKS, ADVISORY_ONLY } = await import(
"./uicheck.js"
);
const [fg, bg] = [argv[1], argv[2]];
console.log(`${BRAND.brand} uicheck — deterministic UI review\n`);
if (fg && bg) {
try {
const g = wcagLevel(contrastRatio(fg, bg));
console.log(
` contrast ${fg} on ${bg}: ${g.ratio}:1 → ${g.level}${g.passesAA ? " (passes AA)" : " (FAILS AA)"}`,
);
} catch (e) {
console.error(` ${e.message}`);
process.exitCode = 1;
return;
}
}
console.log(`\n ASSERT (deterministic): ${ASSERTABLE_CHECKS.map((c) => c.id).join(", ")}`);
console.log(` ADVISE (subjective, human-only): ${ADVISORY_ONLY.slice(0, 4).join(", ")} …`);
return;
}
if (!(cmd in COMMANDS)) {
console.error(`Unknown command: ${cmd}\nRun \`${BRAND.cli} --help\` to see commands.`);
process.exitCode = 1;
Expand Down
7 changes: 7 additions & 0 deletions src/cortex_hook_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
readSession,
} from "./cortex_hook.js";
import { load } from "./lessons_store.js";
import { clarifyBlock, preflightRepo } from "./preflight.js";

// Opt-in: distill newly-created lessons into real prose via a cheap model call. Off by
// default (deterministic template is used); fail-safe (any error → keep the template).
Expand Down Expand Up @@ -68,6 +69,12 @@ async function main() {
} else if (mode === "pre-edit") {
const advice = await preEditAdvisory(root, hook.tool_input?.file_path, today);
if (advice) emit("PreToolUse", advice);
} else if (mode === "preflight") {
// Assumption detector: does the task name things the repo doesn't define?
if (typeof hook.prompt === "string" && hook.prompt.trim()) {
const block = clarifyBlock(preflightRepo(root, hook.prompt, { allowBuild: false }));
if (block) emit("UserPromptSubmit", block);
}
}
}

Expand Down
Loading
Loading