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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to `@fusengine/harness`. Format: [Keep a Changelog](https://

## [Unreleased]

## [0.1.85] - 2026-07-29

### Fixed

- **Typo gate missed quoted font names in non-English custom properties** (`src/policy/design/design-system-rules.ts`) — the gate only recognized typography custom properties by an English keyword in the property name (`--*font*`/`--*ff*`/`--*type*`), so a token named in French like the corpus's `--police-texte: "Inter", …` (`refs-design/linear-recode/styles.css`) slipped through unbanned. `FONT_QUOTED_PROP_RE` now flags any `--*` custom property whose value quotes a forbidden name, using the quote itself as the usage signal instead of an English keyword.

## [0.1.84] - 2026-07-29

### Added
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fusengine/harness",
"version": "0.1.84",
"version": "0.1.85",
"description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, per-harness adapters (Claude/Cursor/Cline/Gemini) and a cli-mode harness-check binary. Bun-native, with a built dist for Node + bundlers.",
"type": "module",
"module": "src/index.ts",
Expand Down
18 changes: 13 additions & 5 deletions src/policy/design/design-system-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@ const OKLCH_RE = /oklch\(\s*[\d.]+%?\s+0\.0*[1-9]/;
* system may SAY "Inter" ("## Interaction states", "we never use Inter"), it
* may not USE it as a font. Covered forms: CSS declarations (`font:`,
* `font-family:` in any case, multiline values), JS config (`fontFamily:`),
* custom properties (`--*font*`/`--*ff*`/`--*type*`), `@import` URLs, and
* Markdown table cells (`| Body | Inter |`). Explicitly OUT of scope (chosen,
* not overlooked): unquoted prose in bullet lines — distinguishing "usage"
* from "mention" there is prose parsing, not a regex's job.
* custom properties named in English (`--*font*`/`--*ff*`/`--*type*`, value
* unquoted or quoted), ANY custom property whose value quotes a forbidden
* name (`--police-texte: "Inter", …` — a non-English property name has no
* keyword to key off, so the quote itself is the usage signal: a quoted
* token in a `--*` value is a font-family entry, not prose), `@import`
* URLs, and Markdown table cells (`| Body | Inter |`). Explicitly OUT of
* scope (chosen, not overlooked): unquoted prose in bullet lines —
* distinguishing "usage" from "mention" there is prose parsing, not a
* regex's job. Accepted trade-off of the quote signal: `--comment: "we
* never use Inter"` is flagged too — a quoted mention reads identically to
* a quoted usage once the property name carries no keyword to disambiguate.
*/
const FONT_DECL_RE = /(?:font(?:-family)?|fontFamily|--[\w-]*(?:font|ff|type)[\w-]*)\s*:[^;\n]*(?:\n\s*)?[^;\n]*\b(?:Inter|Roboto|Arial|Open Sans)\b/i;
const FONT_IMPORT_RE = /family=[^&"')]*\b(?:Inter|Roboto|Arial|Open Sans)\b/i;
const FONT_TABLE_RE = /\|\s*\*{0,2}(?:Inter|Roboto|Arial|Open Sans)\*{0,2}\s*\|/;
const FONT_QUOTED_PROP_RE = /--[\w-]+\s*:[^;\n]*["'][^;\n]*(?:\n\s*)?[^;\n]*\b(?:Inter|Roboto|Arial|Open Sans)\b/i;

/**
* Return the requirements missing from a design-system.md (empty = valid).
Expand All @@ -32,6 +40,6 @@ export function validateDesignSystem(content: string, corpusCitationOk = false):
if (!content.includes("## Design Reference")) missing.push("## Design Reference section");
if (!/https?:\/\//.test(content) && !(corpusCitationOk && hasCorpusCitation(content))) missing.push("reference URL (https://…) or Corpus citation");
if (!OKLCH_RE.test(content)) missing.push("oklch() color with chroma > 0");
if (FONT_DECL_RE.test(content) || FONT_IMPORT_RE.test(content) || FONT_TABLE_RE.test(content)) missing.push("forbidden font (Inter/Roboto/Arial/Open Sans)");
if (FONT_DECL_RE.test(content) || FONT_IMPORT_RE.test(content) || FONT_TABLE_RE.test(content) || FONT_QUOTED_PROP_RE.test(content)) missing.push("forbidden font (Inter/Roboto/Arial/Open Sans)");
return missing;
}
40 changes: 40 additions & 0 deletions test/design-font-custom-prop.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { test, expect } from "bun:test";
import { validateDesignSystem } from "../src/policy/design/gates";

const FORBIDDEN_FONT_MSG = "forbidden font (Inter/Roboto/Arial/Open Sans)";

/**
* Change 2 only: any custom property (English-named or not) whose value
* quotes a forbidden font name must be caught. The quote is the signal —
* `--police-texte: "Inter"` has no English keyword to key off, so without
* this branch it silently escaped detection. Real-world two-line fixture
* below is inspired by the corpus's `linear-recode/styles.css` font-stack.
*/
test("quoted custom prop: two-line font-stack with a non-English name is caught", () => {
const twoLine =
'--police-texte: "Inter", "Inter Variable", "SF Pro Display", -apple-system,\n BlinkMacSystemFont, sans-serif;';
expect(validateDesignSystem(twoLine)).toContain(FORBIDDEN_FONT_MSG);
});

test("quoted custom prop: single-line non-English name is caught", () => {
expect(validateDesignSystem('--police-texte: "Inter";')).toContain(FORBIDDEN_FONT_MSG);
});

test("quoted custom prop: all four forbidden names are caught", () => {
const names = ["Inter", "Roboto", "Arial", "Open Sans"];
for (const name of names) {
expect(validateDesignSystem(`--police-texte: "${name}";`)).toContain(FORBIDDEN_FONT_MSG);
}
});

test("regression: the existing unquoted English-keyword branch still catches --font-body: Inter", () => {
expect(validateDesignSystem("--font-body: Inter")).toContain(FORBIDDEN_FONT_MSG);
});

test("quoted custom prop: an allowed font name is not flagged", () => {
expect(validateDesignSystem('--police-texte: "Fraunces";')).not.toContain(FORBIDDEN_FONT_MSG);
});

test("accepted trade-off: a quoted PROSE mention in a non-keyword custom prop is also caught", () => {
expect(validateDesignSystem(`--commentaire: "on n'utilise jamais Inter"`)).toContain(FORBIDDEN_FONT_MSG);
});