@miller-tech/uap 1.64.4 → 1.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +15 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/design.d.ts +9 -0
- package/dist/cli/design.d.ts.map +1 -0
- package/dist/cli/design.js +187 -0
- package/dist/cli/design.js.map +1 -0
- package/dist/cli/react.d.ts.map +1 -1
- package/dist/cli/react.js +4 -0
- package/dist/cli/react.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +36 -0
- package/dist/cli/setup.js.map +1 -1
- package/dist/coordination/reactor.d.ts.map +1 -1
- package/dist/coordination/reactor.js +25 -2
- package/dist/coordination/reactor.js.map +1 -1
- package/dist/design/gate.d.ts +16 -0
- package/dist/design/gate.d.ts.map +1 -0
- package/dist/design/gate.js +79 -0
- package/dist/design/gate.js.map +1 -0
- package/dist/design/interrogate.d.ts +17 -0
- package/dist/design/interrogate.d.ts.map +1 -0
- package/dist/design/interrogate.js +258 -0
- package/dist/design/interrogate.js.map +1 -0
- package/dist/design/reactor-inject.d.ts +8 -0
- package/dist/design/reactor-inject.d.ts.map +1 -0
- package/dist/design/reactor-inject.js +37 -0
- package/dist/design/reactor-inject.js.map +1 -0
- package/dist/design/tokens.d.ts +62 -0
- package/dist/design/tokens.d.ts.map +1 -0
- package/dist/design/tokens.js +185 -0
- package/dist/design/tokens.js.map +1 -0
- package/dist/mcp-router/tools/react.d.ts.map +1 -1
- package/dist/mcp-router/tools/react.js +2 -0
- package/dist/mcp-router/tools/react.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/design_token_gate.py +145 -0
- package/src/policies/schemas/policies/design-token-gate.md +38 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface TypographyToken {
|
|
2
|
+
fontFamily?: string;
|
|
3
|
+
fontSize?: string;
|
|
4
|
+
fontWeight?: string | number;
|
|
5
|
+
lineHeight?: string | number;
|
|
6
|
+
letterSpacing?: string;
|
|
7
|
+
fontFeature?: string;
|
|
8
|
+
fontVariation?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DesignTokens {
|
|
11
|
+
version?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
colors?: Record<string, string>;
|
|
15
|
+
typography?: Record<string, TypographyToken>;
|
|
16
|
+
rounded?: Record<string, string | number>;
|
|
17
|
+
spacing?: Record<string, string | number>;
|
|
18
|
+
components?: Record<string, Record<string, string>>;
|
|
19
|
+
}
|
|
20
|
+
export interface ParsedDesign {
|
|
21
|
+
tokens: DesignTokens;
|
|
22
|
+
/** Markdown body after the front matter. */
|
|
23
|
+
prose: string;
|
|
24
|
+
/** Section heading -> body text, keyed by lowercased `##` title. */
|
|
25
|
+
sections: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/** Flat, JSON-serializable allow-list consumed by the Python gate. */
|
|
28
|
+
export interface TokenAllowList {
|
|
29
|
+
name: string;
|
|
30
|
+
colors: string[];
|
|
31
|
+
spacing: string[];
|
|
32
|
+
radii: string[];
|
|
33
|
+
fontSizes: string[];
|
|
34
|
+
fontFamilies: string[];
|
|
35
|
+
generatedFrom: string;
|
|
36
|
+
}
|
|
37
|
+
/** Locate a DESIGN.md in `dir` (project root). Returns absolute path or null. */
|
|
38
|
+
export declare function findDesignFile(dir: string): string | null;
|
|
39
|
+
/** Split a DESIGN.md into YAML front matter + markdown body. */
|
|
40
|
+
export declare function parseDesignMd(content: string): ParsedDesign;
|
|
41
|
+
/** Resolve a `{path.to.token}` reference against the token tree; returns the
|
|
42
|
+
* raw string for non-references unchanged. */
|
|
43
|
+
export declare function resolveRef(tokens: DesignTokens, value: unknown): string;
|
|
44
|
+
/** Normalize a CSS color for set-membership comparison: lowercase, trim, and
|
|
45
|
+
* expand `#rgb`/`#rgba` shorthand to the 6/8-digit form. */
|
|
46
|
+
export declare function normalizeColor(c: string): string;
|
|
47
|
+
/** Derive the flat allow-list from a parsed DESIGN.md. */
|
|
48
|
+
export declare function buildAllowList(parsed: ParsedDesign, designPath: string, projectDir: string): TokenAllowList;
|
|
49
|
+
export declare function allowListPath(projectDir: string): string;
|
|
50
|
+
/** Write `.uap/design-tokens.json` (the gate's source of truth). */
|
|
51
|
+
export declare function writeAllowList(projectDir: string, allow: TokenAllowList): string;
|
|
52
|
+
/** Load + parse the project DESIGN.md, or null if none exists. */
|
|
53
|
+
export declare function loadDesign(projectDir: string): {
|
|
54
|
+
parsed: ParsedDesign;
|
|
55
|
+
path: string;
|
|
56
|
+
} | null;
|
|
57
|
+
/**
|
|
58
|
+
* Compact guidance string injected by the reactor when UI/UX work is detected.
|
|
59
|
+
* Keeps the agent on-token for new UI without re-reading the whole DESIGN.md.
|
|
60
|
+
*/
|
|
61
|
+
export declare function summarizeForReactor(parsed: ParsedDesign, maxChars?: number): string;
|
|
62
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/design/tokens.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACrD;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,4CAA4C;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAID,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMzD;AAED,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CA2B3D;AAED;8CAC8C;AAC9C,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAcvE;AAED;4DAC4D;AAC5D,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAQhD;AAQD,0DAA0D;AAC1D,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,GACjB,cAAc,CA2ChB;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,MAAM,CAKhF;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI5F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,SAAM,GAAG,MAAM,CA0BhF"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DESIGN.md token model.
|
|
3
|
+
*
|
|
4
|
+
* Parses a project's DESIGN.md (the Google Labs DESIGN.md format: YAML token
|
|
5
|
+
* front matter + markdown prose) into a structured token set, resolves
|
|
6
|
+
* `{token.refs}`, and derives a flat allow-list that the (pure-stdlib) Python
|
|
7
|
+
* design-token gate consumes from `.uap/design-tokens.json`.
|
|
8
|
+
*
|
|
9
|
+
* Spec: https://github.com/google-labs-code/design.md (docs/spec.md).
|
|
10
|
+
*/
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
12
|
+
import { join, dirname, relative } from 'path';
|
|
13
|
+
import yaml from 'js-yaml';
|
|
14
|
+
const DESIGN_FILENAMES = ['DESIGN.md', 'design.md', 'Design.md'];
|
|
15
|
+
/** Locate a DESIGN.md in `dir` (project root). Returns absolute path or null. */
|
|
16
|
+
export function findDesignFile(dir) {
|
|
17
|
+
for (const name of DESIGN_FILENAMES) {
|
|
18
|
+
const p = join(dir, name);
|
|
19
|
+
if (existsSync(p))
|
|
20
|
+
return p;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/** Split a DESIGN.md into YAML front matter + markdown body. */
|
|
25
|
+
export function parseDesignMd(content) {
|
|
26
|
+
let tokens = {};
|
|
27
|
+
let prose = content;
|
|
28
|
+
// Front matter must open and close with a line that is exactly `---`.
|
|
29
|
+
const fm = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
30
|
+
if (fm) {
|
|
31
|
+
try {
|
|
32
|
+
tokens = yaml.load(fm[1]) ?? {};
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
tokens = {};
|
|
36
|
+
}
|
|
37
|
+
prose = content.slice(fm[0].length);
|
|
38
|
+
}
|
|
39
|
+
const sections = {};
|
|
40
|
+
// Split on `##` headings (not `###`).
|
|
41
|
+
const parts = prose.split(/^##\s+(?!#)/m);
|
|
42
|
+
for (let i = 1; i < parts.length; i++) {
|
|
43
|
+
const block = parts[i];
|
|
44
|
+
const nl = block.indexOf('\n');
|
|
45
|
+
const heading = (nl === -1 ? block : block.slice(0, nl)).trim().toLowerCase();
|
|
46
|
+
const body = nl === -1 ? '' : block.slice(nl + 1).trim();
|
|
47
|
+
sections[heading] = body;
|
|
48
|
+
}
|
|
49
|
+
return { tokens, prose: prose.trim(), sections };
|
|
50
|
+
}
|
|
51
|
+
/** Resolve a `{path.to.token}` reference against the token tree; returns the
|
|
52
|
+
* raw string for non-references unchanged. */
|
|
53
|
+
export function resolveRef(tokens, value) {
|
|
54
|
+
if (typeof value !== 'string')
|
|
55
|
+
return String(value ?? '');
|
|
56
|
+
const m = value.match(/^\{([^}]+)\}$/);
|
|
57
|
+
if (!m)
|
|
58
|
+
return value;
|
|
59
|
+
const path = m[1].split('.');
|
|
60
|
+
let cur = tokens;
|
|
61
|
+
for (const key of path) {
|
|
62
|
+
if (cur && typeof cur === 'object' && key in cur) {
|
|
63
|
+
cur = cur[key];
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
return value; // unresolved — leave as-is for the linter to flag
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return typeof cur === 'string' ? cur : value;
|
|
70
|
+
}
|
|
71
|
+
/** Normalize a CSS color for set-membership comparison: lowercase, trim, and
|
|
72
|
+
* expand `#rgb`/`#rgba` shorthand to the 6/8-digit form. */
|
|
73
|
+
export function normalizeColor(c) {
|
|
74
|
+
let s = c.trim().toLowerCase();
|
|
75
|
+
const short = s.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/);
|
|
76
|
+
if (short) {
|
|
77
|
+
const [, r, g, b, a] = short;
|
|
78
|
+
s = `#${r}${r}${g}${g}${b}${b}${a ? a + a : ''}`;
|
|
79
|
+
}
|
|
80
|
+
return s.replace(/\s+/g, '');
|
|
81
|
+
}
|
|
82
|
+
function dimStr(v) {
|
|
83
|
+
if (v === undefined || v === null)
|
|
84
|
+
return null;
|
|
85
|
+
if (typeof v === 'number')
|
|
86
|
+
return `${v}px`;
|
|
87
|
+
return String(v).trim();
|
|
88
|
+
}
|
|
89
|
+
/** Derive the flat allow-list from a parsed DESIGN.md. */
|
|
90
|
+
export function buildAllowList(parsed, designPath, projectDir) {
|
|
91
|
+
const t = parsed.tokens;
|
|
92
|
+
const colors = new Set();
|
|
93
|
+
const spacing = new Set();
|
|
94
|
+
const radii = new Set();
|
|
95
|
+
const fontSizes = new Set();
|
|
96
|
+
const fontFamilies = new Set();
|
|
97
|
+
for (const v of Object.values(t.colors ?? {})) {
|
|
98
|
+
const resolved = resolveRef(t, v);
|
|
99
|
+
if (resolved)
|
|
100
|
+
colors.add(normalizeColor(resolved));
|
|
101
|
+
}
|
|
102
|
+
for (const v of Object.values(t.spacing ?? {})) {
|
|
103
|
+
const d = dimStr(v);
|
|
104
|
+
if (d)
|
|
105
|
+
spacing.add(d);
|
|
106
|
+
}
|
|
107
|
+
for (const v of Object.values(t.rounded ?? {})) {
|
|
108
|
+
const d = dimStr(v);
|
|
109
|
+
if (d)
|
|
110
|
+
radii.add(d);
|
|
111
|
+
}
|
|
112
|
+
for (const ty of Object.values(t.typography ?? {})) {
|
|
113
|
+
if (ty?.fontSize)
|
|
114
|
+
fontSizes.add(dimStr(ty.fontSize) ?? '');
|
|
115
|
+
if (ty?.fontFamily)
|
|
116
|
+
fontFamilies.add(String(ty.fontFamily).trim().toLowerCase());
|
|
117
|
+
}
|
|
118
|
+
// Component tokens may inline literal colors/dimensions too.
|
|
119
|
+
for (const comp of Object.values(t.components ?? {})) {
|
|
120
|
+
for (const v of Object.values(comp ?? {})) {
|
|
121
|
+
const resolved = resolveRef(t, v);
|
|
122
|
+
if (/^#|^rgb|^hsl|^oklch|^oklab|^lab|^lch|^hwb|^color-mix/i.test(resolved.trim())) {
|
|
123
|
+
colors.add(normalizeColor(resolved));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
name: t.name ?? 'Untitled',
|
|
129
|
+
colors: [...colors].filter(Boolean).sort(),
|
|
130
|
+
spacing: [...spacing].filter(Boolean).sort(),
|
|
131
|
+
radii: [...radii].filter(Boolean).sort(),
|
|
132
|
+
fontSizes: [...fontSizes].filter(Boolean).sort(),
|
|
133
|
+
fontFamilies: [...fontFamilies].filter(Boolean).sort(),
|
|
134
|
+
generatedFrom: relative(projectDir, designPath) || 'DESIGN.md',
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function allowListPath(projectDir) {
|
|
138
|
+
return join(projectDir, '.uap', 'design-tokens.json');
|
|
139
|
+
}
|
|
140
|
+
/** Write `.uap/design-tokens.json` (the gate's source of truth). */
|
|
141
|
+
export function writeAllowList(projectDir, allow) {
|
|
142
|
+
const out = allowListPath(projectDir);
|
|
143
|
+
mkdirSync(dirname(out), { recursive: true });
|
|
144
|
+
writeFileSync(out, JSON.stringify(allow, null, 2) + '\n');
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
/** Load + parse the project DESIGN.md, or null if none exists. */
|
|
148
|
+
export function loadDesign(projectDir) {
|
|
149
|
+
const p = findDesignFile(projectDir);
|
|
150
|
+
if (!p)
|
|
151
|
+
return null;
|
|
152
|
+
return { parsed: parseDesignMd(readFileSync(p, 'utf-8')), path: p };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Compact guidance string injected by the reactor when UI/UX work is detected.
|
|
156
|
+
* Keeps the agent on-token for new UI without re-reading the whole DESIGN.md.
|
|
157
|
+
*/
|
|
158
|
+
export function summarizeForReactor(parsed, maxChars = 900) {
|
|
159
|
+
const t = parsed.tokens;
|
|
160
|
+
const lines = [];
|
|
161
|
+
lines.push(`Apply the project design system "${t.name ?? 'DESIGN.md'}". Use ONLY these tokens for new UI:`);
|
|
162
|
+
const colorRoles = Object.entries(t.colors ?? {})
|
|
163
|
+
.slice(0, 8)
|
|
164
|
+
.map(([k, v]) => `${k}=${resolveRef(t, v)}`);
|
|
165
|
+
if (colorRoles.length)
|
|
166
|
+
lines.push(` colors: ${colorRoles.join(', ')}`);
|
|
167
|
+
const fams = [...new Set(Object.values(t.typography ?? {}).map((x) => x.fontFamily).filter(Boolean))];
|
|
168
|
+
if (fams.length)
|
|
169
|
+
lines.push(` fonts: ${fams.join(', ')}`);
|
|
170
|
+
const sp = Object.values(t.spacing ?? {}).map((v) => (typeof v === 'number' ? `${v}px` : v));
|
|
171
|
+
if (sp.length)
|
|
172
|
+
lines.push(` spacing: ${sp.join(', ')}`);
|
|
173
|
+
const donts = parsed.sections["do's and don'ts"] || parsed.sections['dos and donts'];
|
|
174
|
+
if (donts) {
|
|
175
|
+
const firstDont = donts.split('\n').find((l) => /don'?t|avoid|never/i.test(l));
|
|
176
|
+
if (firstDont)
|
|
177
|
+
lines.push(` rule: ${firstDont.replace(/^[-*]\s*/, '').trim()}`);
|
|
178
|
+
}
|
|
179
|
+
lines.push(' Hardcoding off-token colors/spacing in UI files is BLOCKED by the design gate.');
|
|
180
|
+
let out = lines.join('\n');
|
|
181
|
+
if (out.length > maxChars)
|
|
182
|
+
out = out.slice(0, maxChars - 1) + '…';
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/design/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../src/mcp-router/tools/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBjC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../../src/mcp-router/tools/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,SAAS;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuBjC,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,GAAG,eAAe,CAuB5D"}
|
|
@@ -41,6 +41,8 @@ export function handleReact(args) {
|
|
|
41
41
|
promptText: args.promptText,
|
|
42
42
|
changedFiles: args.changedFiles,
|
|
43
43
|
surfaced: args.surfaced,
|
|
44
|
+
// Locate the project DESIGN.md for design-system injection.
|
|
45
|
+
cwd: process.cwd(),
|
|
44
46
|
};
|
|
45
47
|
const opts = {
|
|
46
48
|
injectThreshold: args.injectThreshold,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../../src/mcp-router/tools/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,OAAO,IAAI,cAAc,GAG1B,MAAM,+BAA+B,CAAC;AAavC,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,OAAO;IACb,WAAW,EAAE;;yVAE0U;IACvV,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;YAC5E,YAAY,EAAE;gBACZ,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EAAE,iCAAiC;aAC/C;YACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE;YACjF,QAAQ,EAAE;gBACR,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EAAE,uEAAuE;aACrF;SACF;QACD,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;CACF,CAAC;AAQF,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;QACxD,CAAC;QACD,MAAM,GAAG,GAAmB;YAC1B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,aAAa;YAClC,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../../src/mcp-router/tools/react.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,OAAO,IAAI,cAAc,GAG1B,MAAM,+BAA+B,CAAC;AAavC,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,OAAO;IACb,WAAW,EAAE;;yVAE0U;IACvV,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,UAAU,EAAE;YACV,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,+BAA+B,EAAE;YAC5E,YAAY,EAAE;gBACZ,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EAAE,iCAAiC;aAC/C;YACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE;YACjF,QAAQ,EAAE;gBACR,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACzB,WAAW,EAAE,uEAAuE;aACrF;SACF;QACD,QAAQ,EAAE,CAAC,YAAY,CAAC;KACzB;CACF,CAAC;AAQF,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,IAAI,CAAC;QACH,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;YAC5E,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;QACxD,CAAC;QACD,MAAM,GAAG,GAAmB;YAC1B,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,aAAa;YAClC,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,4DAA4D;YAC5D,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;SACnB,CAAC;QACF,MAAM,IAAI,GAAmB;YAC3B,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;YAC3C,cAAc,EAAE,IAAI,CAAC,cAAc;SACpC,CAAC;QACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1E,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
Binary file
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""design-token gate: UI edits must stay on the project's DESIGN.md token system.
|
|
3
|
+
|
|
4
|
+
A Write/Edit/MultiEdit to a UI file (.css/.scss/.tsx/.jsx/.vue/.svelte/.html/...)
|
|
5
|
+
whose proposed content introduces a hardcoded color or spacing value NOT present
|
|
6
|
+
in the project's design tokens is **blocked**. The agent must use a DESIGN.md
|
|
7
|
+
token (or a CSS var), add the value to DESIGN.md (`uap design sync`), or bypass.
|
|
8
|
+
|
|
9
|
+
The token allow-list is generated by `uap design interrogate|sync` into
|
|
10
|
+
`.uap/design-tokens.json` (so this enforcer needs no YAML parser). If that file
|
|
11
|
+
is absent, the gate is INACTIVE and fails open — DESIGN.md is opt-in per project.
|
|
12
|
+
|
|
13
|
+
Mirrors src/design/gate.ts. Escape hatch: UAP_DESIGN_GATE_OFF=1.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
sys.path.insert(0, str(Path(__file__).parent))
|
|
24
|
+
from _common import emit, parse_cli, repo_root, worktree_root # noqa: E402
|
|
25
|
+
|
|
26
|
+
PATH_WRITE_OPS = {"Write", "Edit", "MultiEdit", "write", "edit", "multiedit"}
|
|
27
|
+
UI_EXT = {".css", ".scss", ".sass", ".less", ".tsx", ".jsx", ".vue", ".svelte", ".html", ".astro"}
|
|
28
|
+
SPACING_IGNORE = {"0px", "1px", "2px", "3px", "4px"}
|
|
29
|
+
|
|
30
|
+
HEX_RE = re.compile(r"#[0-9a-fA-F]{3,8}\b")
|
|
31
|
+
FUNC_COLOR_RE = re.compile(r"\b(?:rgba?|hsla?|hwb|oklch|oklab|lch|lab)\([^)]*\)", re.IGNORECASE)
|
|
32
|
+
PX_RE = re.compile(r"\b\d{1,4}px\b")
|
|
33
|
+
VAR_RE = re.compile(r"var\(--[^)]*\)")
|
|
34
|
+
# A DESIGN.md token reference is a dotted path in braces (e.g. {colors.primary}).
|
|
35
|
+
# Must NOT match CSS declaration blocks like `{color:#fff;}` (they contain
|
|
36
|
+
# colons/semicolons), or the scanner would strip whole rules before checking.
|
|
37
|
+
REF_RE = re.compile(r"\{[\w.-]+\}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _norm_color(c: str) -> str:
|
|
41
|
+
s = c.strip().lower()
|
|
42
|
+
m = re.match(r"^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$", s)
|
|
43
|
+
if m:
|
|
44
|
+
r, g, b, a = m.groups()
|
|
45
|
+
s = f"#{r}{r}{g}{g}{b}{b}{(a + a) if a else ''}"
|
|
46
|
+
return re.sub(r"\s+", "", s)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _allow_list() -> dict | None:
|
|
50
|
+
for root in (worktree_root(), repo_root()):
|
|
51
|
+
p = root / ".uap" / "design-tokens.json"
|
|
52
|
+
if p.exists():
|
|
53
|
+
try:
|
|
54
|
+
return json.loads(p.read_text())
|
|
55
|
+
except Exception: # noqa: BLE001
|
|
56
|
+
return None
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _target_path(args: dict) -> str:
|
|
61
|
+
for key in ("file_path", "path", "notebook_path", "filePath", "target"):
|
|
62
|
+
if args.get(key):
|
|
63
|
+
return str(args[key])
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _proposed_content(op: str, args: dict) -> str:
|
|
68
|
+
# Write: full content. Edit: the inserted text. MultiEdit: all inserts.
|
|
69
|
+
if args.get("content"):
|
|
70
|
+
return str(args["content"])
|
|
71
|
+
if args.get("new_string"):
|
|
72
|
+
return str(args["new_string"])
|
|
73
|
+
edits = args.get("edits")
|
|
74
|
+
if isinstance(edits, list):
|
|
75
|
+
return "\n".join(str(e.get("new_string", "")) for e in edits if isinstance(e, dict))
|
|
76
|
+
return ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _scan(content: str, allow: dict) -> tuple[list[str], list[str]]:
|
|
80
|
+
allow_colors = {_norm_color(c) for c in allow.get("colors", [])}
|
|
81
|
+
allow_spacing = set(allow.get("spacing", [])) | set(allow.get("radii", [])) | set(allow.get("fontSizes", []))
|
|
82
|
+
off_colors: list[str] = []
|
|
83
|
+
off_spacing: list[str] = []
|
|
84
|
+
|
|
85
|
+
for raw in content.split("\n"):
|
|
86
|
+
t = raw.strip()
|
|
87
|
+
if t.startswith("//") or t.startswith("*") or t.startswith("/*"):
|
|
88
|
+
continue
|
|
89
|
+
line = REF_RE.sub("", VAR_RE.sub("", raw))
|
|
90
|
+
for m in HEX_RE.finditer(line):
|
|
91
|
+
if _norm_color(m.group(0)) not in allow_colors:
|
|
92
|
+
off_colors.append(m.group(0))
|
|
93
|
+
for m in FUNC_COLOR_RE.finditer(line):
|
|
94
|
+
if re.sub(r"\s+", "", m.group(0).lower()) not in allow_colors:
|
|
95
|
+
off_colors.append(m.group(0))
|
|
96
|
+
for m in PX_RE.finditer(line):
|
|
97
|
+
px = m.group(0)
|
|
98
|
+
if px not in SPACING_IGNORE and px not in allow_spacing:
|
|
99
|
+
off_spacing.append(px)
|
|
100
|
+
|
|
101
|
+
return off_colors, off_spacing
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main() -> None:
|
|
105
|
+
op, args = parse_cli()
|
|
106
|
+
|
|
107
|
+
if os.environ.get("UAP_DESIGN_GATE_OFF") == "1":
|
|
108
|
+
emit(True, "UAP_DESIGN_GATE_OFF override set")
|
|
109
|
+
if op not in PATH_WRITE_OPS:
|
|
110
|
+
emit(True, "not a file-write operation")
|
|
111
|
+
|
|
112
|
+
target = _target_path(args)
|
|
113
|
+
if Path(target).suffix.lower() not in UI_EXT:
|
|
114
|
+
emit(True, "not a UI file")
|
|
115
|
+
|
|
116
|
+
allow = _allow_list()
|
|
117
|
+
if not allow:
|
|
118
|
+
emit(True, "no DESIGN.md token allow-list (.uap/design-tokens.json) — gate inactive")
|
|
119
|
+
|
|
120
|
+
content = _proposed_content(op, args)
|
|
121
|
+
if not content:
|
|
122
|
+
emit(True, "no proposed content to scan")
|
|
123
|
+
|
|
124
|
+
off_colors, off_spacing = _scan(content, allow)
|
|
125
|
+
if not off_colors and not off_spacing:
|
|
126
|
+
emit(True, "on-token UI edit")
|
|
127
|
+
|
|
128
|
+
name = allow.get("name", "DESIGN.md")
|
|
129
|
+
palette = ", ".join(allow.get("colors", [])[:5])
|
|
130
|
+
spacing_scale = ", ".join(allow.get("spacing", []))
|
|
131
|
+
lines = [f'design-token gate: this UI edit introduces values not in the "{name}" design system.']
|
|
132
|
+
if off_colors:
|
|
133
|
+
uniq = list(dict.fromkeys(off_colors))[:6]
|
|
134
|
+
lines.append(f" off-token colors: {', '.join(uniq)}")
|
|
135
|
+
lines.append(f" → use a DESIGN.md color token ({palette}{', …' if len(allow.get('colors', [])) > 5 else ''}) or a CSS var.")
|
|
136
|
+
if off_spacing:
|
|
137
|
+
uniq = list(dict.fromkeys(off_spacing))[:6]
|
|
138
|
+
lines.append(f" off-scale spacing: {', '.join(uniq)}")
|
|
139
|
+
lines.append(f" → use a spacing token ({spacing_scale or 'define spacing in DESIGN.md'}).")
|
|
140
|
+
lines.append(" Add the value to DESIGN.md (then `uap design sync`), reference a token, or bypass with UAP_DESIGN_GATE_OFF=1.")
|
|
141
|
+
emit(False, "\n".join(lines))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
main()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# design-token-gate
|
|
2
|
+
|
|
3
|
+
**Category**: quality
|
|
4
|
+
**Level**: REQUIRED
|
|
5
|
+
**Enforcement Stage**: pre-exec
|
|
6
|
+
**Tags**: design, ui, ux, tokens, design-system
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
A `Write`, `Edit`, or `MultiEdit` to a UI file (`.css`, `.scss`, `.tsx`, `.jsx`,
|
|
11
|
+
`.vue`, `.svelte`, `.html`, `.astro`) whose proposed content hardcodes a color
|
|
12
|
+
or spacing value that is NOT part of the project's DESIGN.md token system is
|
|
13
|
+
**blocked**. The agent must use an existing design token (or CSS var), add the
|
|
14
|
+
value to `DESIGN.md` and re-sync, or bypass explicitly.
|
|
15
|
+
|
|
16
|
+
On-token (allowed): values present in the token allow-list, `var(--…)` usages,
|
|
17
|
+
`{token.ref}` references, and structural hairlines (`0–4px`). The gate is
|
|
18
|
+
**inactive** (fails open) when a project has no `DESIGN.md` / token allow-list.
|
|
19
|
+
|
|
20
|
+
## Why
|
|
21
|
+
|
|
22
|
+
DESIGN.md gives agents a persistent, structured understanding of a project's
|
|
23
|
+
visual identity. Without enforcement, new UI drifts off-system — ad-hoc hex
|
|
24
|
+
colors and off-scale spacing accumulate until the design system is fiction. The
|
|
25
|
+
gate keeps new UI on-token so the design system stays the real source of truth.
|
|
26
|
+
|
|
27
|
+
## Enforcement
|
|
28
|
+
|
|
29
|
+
Python enforcer `design_token_gate.py` reads `.uap/design-tokens.json` (generated
|
|
30
|
+
by `uap design interrogate|sync` from the project DESIGN.md — no YAML parser
|
|
31
|
+
needed) and scans the proposed content for off-token colors/spacing. Mirrors
|
|
32
|
+
`src/design/gate.ts`. Escape hatch: `UAP_DESIGN_GATE_OFF=1`.
|
|
33
|
+
|
|
34
|
+
```rules
|
|
35
|
+
- title: "New UI must use DESIGN.md design tokens, not hardcoded colors/spacing"
|
|
36
|
+
keywords: [css, color, theme, ui, ux, button, component, style, tailwind, design]
|
|
37
|
+
antiPatterns: ["#hex", "rgb(", "hardcoded color", "off-scale spacing"]
|
|
38
|
+
```
|