@miller-tech/uap 1.64.3 → 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
- package/tools/agents/scripts/anthropic_proxy.py +22 -6
- package/tools/agents/tests/test_turn_count_breaker_periodic.py +64 -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
|
+
```
|
|
@@ -1005,6 +1005,14 @@ class SessionMonitor:
|
|
|
1005
1005
|
# back into a structured tool_use, which would continue the very loop the
|
|
1006
1006
|
# breaker is ending. Reset to False at the start of every request.
|
|
1007
1007
|
suppress_text_tool_extraction: bool = False
|
|
1008
|
+
# Tool-turn count at which the TURN-COUNT FINALIZE BREAKER last fired. The
|
|
1009
|
+
# count is derived from the (only-growing) conversation, so without this the
|
|
1010
|
+
# breaker would re-fire on EVERY turn once the ceiling is first crossed —
|
|
1011
|
+
# permanently stripping tools and stalling a legitimately long agentic task.
|
|
1012
|
+
# Gating on (last + ceiling) makes it a PERIODIC nudge (80, 160, 240, ...)
|
|
1013
|
+
# with tools restored in between, so long tasks complete while a true runaway
|
|
1014
|
+
# still gets bounded (and the contamination/prune/cycle breakers catch faster).
|
|
1015
|
+
last_hard_finalize_turn_count: int = 0
|
|
1008
1016
|
finalize_continuation_count: int = 0
|
|
1009
1017
|
finalize_hard_stop_count: int = 0 # monotonic, not reset by fresh user text
|
|
1010
1018
|
finalize_synthetic_tool_id: str = ""
|
|
@@ -4095,7 +4103,14 @@ def build_openai_request(
|
|
|
4095
4103
|
# genuine runaway trips it; see PROXY_HARD_FINALIZE_TURNS.
|
|
4096
4104
|
if PROXY_HARD_FINALIZE_TURNS > 0:
|
|
4097
4105
|
_agent_tool_turns = _count_agent_tool_turns(anthropic_body)
|
|
4098
|
-
|
|
4106
|
+
# PERIODIC, not permanent: fire once each time the count crosses
|
|
4107
|
+
# another `ceiling` worth of tool turns past the last firing. Without
|
|
4108
|
+
# the `last + ceiling` gate this fires on EVERY turn past the first
|
|
4109
|
+
# crossing (the count only grows), permanently denying tools and
|
|
4110
|
+
# stalling a long-but-legitimate task. Between fires tools are restored
|
|
4111
|
+
# so the agent keeps making progress.
|
|
4112
|
+
if _agent_tool_turns >= monitor.last_hard_finalize_turn_count + PROXY_HARD_FINALIZE_TURNS:
|
|
4113
|
+
monitor.last_hard_finalize_turn_count = _agent_tool_turns
|
|
4099
4114
|
openai_body.pop("tool_choice", None)
|
|
4100
4115
|
openai_body.pop("tools", None)
|
|
4101
4116
|
openai_body.pop("grammar", None)
|
|
@@ -4103,11 +4118,12 @@ def build_openai_request(
|
|
|
4103
4118
|
msgs.append({
|
|
4104
4119
|
"role": "user",
|
|
4105
4120
|
"content": (
|
|
4106
|
-
f"You have made {_agent_tool_turns} tool calls
|
|
4107
|
-
"
|
|
4108
|
-
"
|
|
4109
|
-
"
|
|
4110
|
-
"
|
|
4121
|
+
f"You have made {_agent_tool_turns} tool calls. Pause for a "
|
|
4122
|
+
"progress checkpoint: in a brief plain-text summary, state "
|
|
4123
|
+
"what is done and the single most important next step. No "
|
|
4124
|
+
"tools are available this turn — do NOT emit any tool call "
|
|
4125
|
+
"or tool-call-like syntax. If the task is complete, say so; "
|
|
4126
|
+
"otherwise you will continue on the next turn."
|
|
4111
4127
|
),
|
|
4112
4128
|
})
|
|
4113
4129
|
openai_body["messages"] = msgs
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""The TURN-COUNT FINALIZE BREAKER must fire PERIODICALLY, not on every turn once
|
|
3
|
+
the ceiling is first crossed.
|
|
4
|
+
|
|
5
|
+
`_count_agent_tool_turns` is derived from the (only-growing) conversation, so the
|
|
6
|
+
naive `count >= ceiling` check re-fires on every request past the first crossing —
|
|
7
|
+
permanently stripping tools and stalling a legitimately long agentic task (observed
|
|
8
|
+
live: msgs 206→208→…→214, breaker every turn, then the client gives up). Gating on
|
|
9
|
+
`count >= last_hard_finalize_turn_count + ceiling` makes it fire at ceiling, 2x,
|
|
10
|
+
3x… with tools restored in between.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _load():
|
|
19
|
+
p = Path(__file__).resolve().parents[1] / "scripts" / "anthropic_proxy.py"
|
|
20
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", p)
|
|
21
|
+
m = importlib.util.module_from_spec(spec)
|
|
22
|
+
spec.loader.exec_module(m)
|
|
23
|
+
return m
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
proxy = _load()
|
|
27
|
+
CEILING = 80
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _fire_turns(counts):
|
|
31
|
+
"""Replay the breaker's gating decision (as in build_openai_request) over a
|
|
32
|
+
sequence of cumulative tool-turn counts; return the counts at which it fired."""
|
|
33
|
+
m = proxy.SessionMonitor(context_window=100000)
|
|
34
|
+
fired = []
|
|
35
|
+
for count in counts:
|
|
36
|
+
if count >= m.last_hard_finalize_turn_count + CEILING:
|
|
37
|
+
m.last_hard_finalize_turn_count = count
|
|
38
|
+
fired.append(count)
|
|
39
|
+
return fired
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestTurnCountBreakerPeriodic(unittest.TestCase):
|
|
43
|
+
def test_monitor_has_the_field_defaulting_zero(self):
|
|
44
|
+
m = proxy.SessionMonitor(context_window=100000)
|
|
45
|
+
self.assertEqual(m.last_hard_finalize_turn_count, 0)
|
|
46
|
+
|
|
47
|
+
def test_fires_periodically_not_every_turn(self):
|
|
48
|
+
# cumulative tool turns 1..244 (grows by 1 each request)
|
|
49
|
+
fired = _fire_turns(range(1, 245))
|
|
50
|
+
# periodic at the ceiling multiples — NOT every turn past 80
|
|
51
|
+
self.assertEqual(fired, [80, 160, 240])
|
|
52
|
+
|
|
53
|
+
def test_does_not_refire_between_crossings(self):
|
|
54
|
+
# turns 80..159 -> fires once at 80, then silent until 160
|
|
55
|
+
fired = _fire_turns(range(80, 160))
|
|
56
|
+
self.assertEqual(fired, [80])
|
|
57
|
+
|
|
58
|
+
def test_first_fire_at_ceiling(self):
|
|
59
|
+
self.assertEqual(_fire_turns(range(1, 81)), [80])
|
|
60
|
+
self.assertEqual(_fire_turns(range(1, 80)), []) # never reaches ceiling
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
unittest.main()
|