@monoes/monodesign 1.2.4 → 1.2.6
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/README.md +6 -6
- package/cli/engine/engines/browser/drivers.mjs +82 -1
- package/cli/engine/rules/checks.mjs +24 -1
- package/dist/antipatterns.d.ts +28 -0
- package/dist/antipatterns.d.ts.map +1 -0
- package/dist/antipatterns.js +108 -0
- package/dist/antipatterns.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +1 -1
- package/dist/index.js.map +1 -0
- package/dist/rules.d.ts +37 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +97 -0
- package/dist/rules.js.map +1 -0
- package/dist/tokens.d.ts +91 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +91 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +9 -5
- package/skill/reference/hooks.md +2 -2
- package/skill/scripts/hook-admin.mjs +51 -18
- package/skill/scripts/live/sveltekit-adapter.mjs +11 -6
- package/skill/scripts/live-inject.mjs +20 -7
- package/skill/scripts/live-server.mjs +38 -2
- package/src/antipatterns.ts +0 -164
- package/src/rules.ts +0 -129
- package/src/tokens.ts +0 -99
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
**Frontend design intelligence for AI agents** — OKLCH design tokens, a 51-rule AI-slop
|
|
12
12
|
antipattern detector, and the `/monodesign` agent skill for Claude Code and Gemini agy.
|
|
13
13
|
|
|
14
|
-
> Part of the [Monomind](https://github.com/monoes/monomind) ecosystem. v1.2.
|
|
14
|
+
> Part of the [Monomind](https://github.com/monoes/monomind) ecosystem. v1.2.4 · Apache-2.0
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
@@ -36,7 +36,7 @@ npm install @monoes/monodesign
|
|
|
36
36
|
|
|
37
37
|
## Design Tokens
|
|
38
38
|
|
|
39
|
-
Source: [`src/tokens.ts`](
|
|
39
|
+
Source: [`src/tokens.ts`](packages/@monoes/monodesign/src/tokens.ts) + [`src/tokens.css`](packages/@monoes/monodesign/src/tokens.css)
|
|
40
40
|
|
|
41
41
|
All color tokens are in **OKLCH** — no hex colors in the token system.
|
|
42
42
|
|
|
@@ -81,7 +81,7 @@ console.log(radius.lg); // 12px
|
|
|
81
81
|
|
|
82
82
|
## Antipattern Detection (51 Rules)
|
|
83
83
|
|
|
84
|
-
Source: [`cli/engine/registry/antipatterns.mjs`](
|
|
84
|
+
Source: [`cli/engine/registry/antipatterns.mjs`](packages/@monoes/monodesign/cli/engine/registry/antipatterns.mjs) — 564 lines, 51 entries.
|
|
85
85
|
|
|
86
86
|
### Rule Categories
|
|
87
87
|
|
|
@@ -112,7 +112,7 @@ Source: [`cli/engine/registry/antipatterns.mjs`](file:///Users/morteza/Desktop/t
|
|
|
112
112
|
|
|
113
113
|
## OKLCH Palette Seed Library
|
|
114
114
|
|
|
115
|
-
Source: [`packages/@monomind/cli/src/commands/design-palette.ts:29–417`](
|
|
115
|
+
Source: [`packages/@monomind/cli/src/commands/design-palette.ts:29–417`](packages/@monomind/cli/src/commands/design-palette.ts#L29-L417)
|
|
116
116
|
|
|
117
117
|
- **129 hand-curated seeds** spanning the full hue wheel (0°–360°)
|
|
118
118
|
- Each seed: `{ id, oklch: [L, C, H], mood: string, strategy: string }`
|
|
@@ -123,7 +123,7 @@ Source: [`packages/@monomind/cli/src/commands/design-palette.ts:29–417`](file:
|
|
|
123
123
|
|
|
124
124
|
## The `/monodesign` Agent Skill
|
|
125
125
|
|
|
126
|
-
Source: [`skill/SKILL.src.md`](
|
|
126
|
+
Source: [`skill/SKILL.src.md`](packages/@monoes/monodesign/skill/SKILL.src.md) · v2.0.0 · Apache-2.0
|
|
127
127
|
|
|
128
128
|
Triggers: `/monodesign`, `/design`, `/frontend-design`, plus 40+ natural-language triggers.
|
|
129
129
|
|
|
@@ -178,7 +178,7 @@ Manage via `monomind design ignores` or `/monodesign hooks <on|off|status|ignore
|
|
|
178
178
|
|
|
179
179
|
## Design System Consistency
|
|
180
180
|
|
|
181
|
-
Source: [`cli/engine/design-system.mjs`](
|
|
181
|
+
Source: [`cli/engine/design-system.mjs`](packages/@monoes/monodesign/cli/engine/design-system.mjs) — 815 lines
|
|
182
182
|
|
|
183
183
|
Parses `DESIGN.md` / `.monodesign/design.json` to enforce consistency rules (`design-system-font`, `design-system-color`, `design-system-radius`, `design-system-font-size`). The skill writes `DESIGN.md`; the detector reads it for drift detection.
|
|
184
184
|
|
|
@@ -28,7 +28,20 @@
|
|
|
28
28
|
// Selection: MONODESIGN_BROWSER_DRIVER=monobrowse|puppeteer forces one;
|
|
29
29
|
// otherwise monobrowse is tried first and puppeteer is the fallback.
|
|
30
30
|
|
|
31
|
+
import { createServer } from 'node:net';
|
|
32
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
33
|
+
import { tmpdir } from 'node:os';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
|
|
31
36
|
const DRIVER_ENV = 'MONODESIGN_BROWSER_DRIVER';
|
|
37
|
+
// Windows CI teardown is the slow case: Chrome acknowledges Browser.close long
|
|
38
|
+
// before the process exits and hands the socket back, and closeBrowser()'s
|
|
39
|
+
// force-kill safety net is itself a deferred 1s timer. 5s is comfortable
|
|
40
|
+
// locally but cuts into a loaded runner's teardown, so give CI more runway —
|
|
41
|
+
// same reasoning as launchTimeoutMs below.
|
|
42
|
+
const CDP_PORT_RELEASE_TIMEOUT_MS = process.env.CI ? 15_000 : 5_000;
|
|
43
|
+
const CDP_PORT_RELEASE_POLL_MS = 25;
|
|
44
|
+
const PROFILE_CLEANUP_RETRY_MS = 2_000;
|
|
32
45
|
|
|
33
46
|
// Ports we launch headless Chrome on for detection runs. Deliberately away
|
|
34
47
|
// from 9222 (the default `monomind browse` port): we must never attach to a
|
|
@@ -56,6 +69,41 @@ function withTimeout(promise, ms, label) {
|
|
|
56
69
|
return Promise.race([promise, timeout]).finally(() => clearTimeout(handle));
|
|
57
70
|
}
|
|
58
71
|
|
|
72
|
+
// "Released" has to mean BINDABLE, not merely unreachable. A connect probe
|
|
73
|
+
// stops succeeding as soon as Chrome's listener socket closes, which happens
|
|
74
|
+
// early in its shutdown — while the process is still alive holding both the
|
|
75
|
+
// port and the --user-data-dir singleton lock for this port. On Windows that
|
|
76
|
+
// gap is wide and the socket stays unbindable across it (node does not set
|
|
77
|
+
// SO_REUSEADDR there), so close() reported success and the next launch on the
|
|
78
|
+
// same forced port sat until its timeout: "Chrome failed to start on port N
|
|
79
|
+
// within 30000ms". Binding is the question the next launch actually asks, so
|
|
80
|
+
// ask it directly. A listener that never accepted a connection leaves no
|
|
81
|
+
// TIME_WAIT behind, so probing this way does not itself hold the port.
|
|
82
|
+
function isPortBindable(port) {
|
|
83
|
+
return new Promise((resolve) => {
|
|
84
|
+
const server = createServer();
|
|
85
|
+
server.once('error', () => resolve(false));
|
|
86
|
+
server.listen(port, '127.0.0.1', () => server.close(() => resolve(true)));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function removeProfileDir(dir) {
|
|
91
|
+
try {
|
|
92
|
+
rmSync(dir, { recursive: true, force: true });
|
|
93
|
+
} catch {
|
|
94
|
+
/* reclaimed by the OS's temp cleanup instead */
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function waitForCdpPortRelease(port) {
|
|
99
|
+
const deadline = Date.now() + CDP_PORT_RELEASE_TIMEOUT_MS;
|
|
100
|
+
while (Date.now() < deadline) {
|
|
101
|
+
if (await isPortBindable(port)) return;
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, CDP_PORT_RELEASE_POLL_MS));
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Chrome did not release CDP port ${port} within ${CDP_PORT_RELEASE_TIMEOUT_MS}ms`);
|
|
105
|
+
}
|
|
106
|
+
|
|
59
107
|
// ---------------------------------------------------------------------------
|
|
60
108
|
// monobrowse driver
|
|
61
109
|
// ---------------------------------------------------------------------------
|
|
@@ -143,7 +191,27 @@ async function launchMonobrowseBrowser(options = {}) {
|
|
|
143
191
|
if (port === null) throw new Error('Could not find a free CDP port for monobrowse');
|
|
144
192
|
}
|
|
145
193
|
|
|
146
|
-
|
|
194
|
+
// CI runners (observed on the Windows GH Actions runner) are slower to get
|
|
195
|
+
// Chrome's CDP port open than the 10s default — a batch of sequential
|
|
196
|
+
// headless launches in the same job intermittently misses that window and
|
|
197
|
+
// fails with "Chrome failed to start... within 10000ms" even though the
|
|
198
|
+
// browser starts fine, just later. Give CI more runway; local dev keeps
|
|
199
|
+
// the tighter default so a genuinely broken launch still fails fast.
|
|
200
|
+
const launchTimeoutMs = options.launchTimeoutMs ?? (process.env.CI ? 30000 : undefined);
|
|
201
|
+
// Throwaway profile per launch. monobrowse defaults --user-data-dir to
|
|
202
|
+
// tmpdir()/monomind-browser-<port>, which is right for `monomind browse`
|
|
203
|
+
// (a later process reattaches to the same session by port) but wrong here:
|
|
204
|
+
// detection launches are stateless, and keying the profile on the port
|
|
205
|
+
// means two launches on the SAME port share one profile. Chrome enforces
|
|
206
|
+
// single-instance-per-profile, so the second launch rendezvouses with the
|
|
207
|
+
// first instead of starting its own — it hands over its command line and
|
|
208
|
+
// exits without ever opening a debugging port, which surfaces as "Chrome
|
|
209
|
+
// failed to start on port N" after the full launch timeout. Windows is
|
|
210
|
+
// where this bites: its singleton lock survives the previous Chrome being
|
|
211
|
+
// force-killed, and outlives the port being released. A unique dir removes
|
|
212
|
+
// the sharing entirely rather than racing the lock's cleanup.
|
|
213
|
+
const userDataDir = mkdtempSync(join(tmpdir(), 'monodesign-cdp-'));
|
|
214
|
+
const launchedPort = await mb.launchBrowser({ port, headless, args: launchArgs, launchTimeoutMs, userDataDir });
|
|
147
215
|
// Control connection (to the initial about:blank tab) — used for lifecycle
|
|
148
216
|
// commands; detection pages get their own dedicated connections.
|
|
149
217
|
const control = await mb.connectToTarget(launchedPort);
|
|
@@ -175,8 +243,21 @@ async function launchMonobrowseBrowser(options = {}) {
|
|
|
175
243
|
// Browser.close command directly (best effort, short timeout).
|
|
176
244
|
await withTimeout(control.client.send('Browser.close', {}), 3000, 'Browser.close').catch(() => {});
|
|
177
245
|
}
|
|
246
|
+
// Browser.close is acknowledged before Chrome has actually exited. A
|
|
247
|
+
// following detection run on a forced port can otherwise attach to the
|
|
248
|
+
// shutting-down process and fail creating its first target. Do not
|
|
249
|
+
// expose close() as complete until that CDP listener is gone.
|
|
250
|
+
await waitForCdpPortRelease(launchedPort);
|
|
178
251
|
} finally {
|
|
179
252
|
control.client.close();
|
|
253
|
+
// Best effort, twice: Chrome is still flushing profile data when the
|
|
254
|
+
// port comes back, so an immediate delete gets a few files recreated
|
|
255
|
+
// under it. The unref'd retry lands after that (same pattern as
|
|
256
|
+
// monobrowse's own post-close force-kill net) and never holds the
|
|
257
|
+
// process open. Whatever still survives is a few KB under tmpdir that
|
|
258
|
+
// nothing reuses, so neither attempt may fail close().
|
|
259
|
+
removeProfileDir(userDataDir);
|
|
260
|
+
setTimeout(() => removeProfileDir(userDataDir), PROFILE_CLEANUP_RETRY_MS).unref?.();
|
|
180
261
|
}
|
|
181
262
|
},
|
|
182
263
|
};
|
|
@@ -2635,6 +2635,29 @@ function checkElementTextOverflowDOM(el) {
|
|
|
2635
2635
|
// conditions can't be read off a resting element. Shared by the regex, static
|
|
2636
2636
|
// and browser engines (each hands over its own CSS text).
|
|
2637
2637
|
|
|
2638
|
+
// Split a selector list on top-level commas only — i.e. commas inside a
|
|
2639
|
+
// functional pseudo-class's parens (`:is(a, button)`, `:where(a, b)`,
|
|
2640
|
+
// `:not(a, b)`, `:has(x, y)`) don't split. A naive `str.split(',')` breaks
|
|
2641
|
+
// `:is(a, button):focus { outline: none }` into `:is(a` and `button):focus`,
|
|
2642
|
+
// which both selectorTargetsInteractive() and the dark/light selector maps
|
|
2643
|
+
// below then treat as two unrelated (and malformed) selectors — producing
|
|
2644
|
+
// false-positive missing-focus-visible findings and double-counted rules.
|
|
2645
|
+
function splitTopLevelCommas(str) {
|
|
2646
|
+
const parts = [];
|
|
2647
|
+
let depth = 0, start = 0;
|
|
2648
|
+
for (let k = 0; k < str.length; k++) {
|
|
2649
|
+
const ch = str[k];
|
|
2650
|
+
if (ch === '(') depth++;
|
|
2651
|
+
else if (ch === ')') depth = Math.max(0, depth - 1);
|
|
2652
|
+
else if (ch === ',' && depth === 0) {
|
|
2653
|
+
parts.push(str.slice(start, k));
|
|
2654
|
+
start = k + 1;
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
parts.push(str.slice(start));
|
|
2658
|
+
return parts;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2638
2661
|
// Parse flat CSS into { selector, body, dark } tuples, tracking one level of
|
|
2639
2662
|
// dark-scheme context (a `@media (prefers-color-scheme: dark)` ancestor).
|
|
2640
2663
|
// @supports / @layer / @container / @scope are transparently descended.
|
|
@@ -2664,7 +2687,7 @@ function collectCssRules(css, dark = false, depth = 0) {
|
|
|
2664
2687
|
}
|
|
2665
2688
|
continue;
|
|
2666
2689
|
}
|
|
2667
|
-
for (const sel of prelude
|
|
2690
|
+
for (const sel of splitTopLevelCommas(prelude).map(s => s.trim()).filter(Boolean)) {
|
|
2668
2691
|
out.push({ selector: sel, body, dark });
|
|
2669
2692
|
}
|
|
2670
2693
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type AntipatternCategory = 'slop' | 'quality' | 'performance' | 'accessibility';
|
|
2
|
+
export interface AntipatternFinding {
|
|
3
|
+
id: string;
|
|
4
|
+
category: AntipatternCategory;
|
|
5
|
+
name: string;
|
|
6
|
+
severity: 'error' | 'warning' | 'info';
|
|
7
|
+
message: string;
|
|
8
|
+
file?: string;
|
|
9
|
+
line?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface DetectionResult {
|
|
12
|
+
findings: AntipatternFinding[];
|
|
13
|
+
fileCount: number;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
}
|
|
16
|
+
/** True when the bundled detection engine can be loaded. */
|
|
17
|
+
export declare function detectorAvailable(): Promise<boolean>;
|
|
18
|
+
/**
|
|
19
|
+
* Run antipattern detection on a path (file, directory, or URL) using the
|
|
20
|
+
* bundled detection engine (cli/engine/detect-antipatterns.mjs). URL targets
|
|
21
|
+
* require the optional puppeteer dependency.
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectAntipatterns(target: string): Promise<DetectionResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Format findings as a human-readable string for display in agent output.
|
|
26
|
+
*/
|
|
27
|
+
export declare function formatFindings(result: DetectionResult): string;
|
|
28
|
+
//# sourceMappingURL=antipatterns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"antipatterns.d.ts","sourceRoot":"","sources":["../src/antipatterns.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,eAAe,CAAC;AAEvF,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAoCD,4DAA4D;AAC5D,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AA8BD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAsCjF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA2B9D"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { extname } from 'node:path';
|
|
3
|
+
// Typed as plain string so tsc emits a true dynamic import instead of trying
|
|
4
|
+
// to type-resolve the untyped .mjs engine module.
|
|
5
|
+
const ENGINE_SPECIFIER = '../cli/engine/detect-antipatterns.mjs';
|
|
6
|
+
let enginePromise;
|
|
7
|
+
async function loadEngine() {
|
|
8
|
+
enginePromise ??= import(ENGINE_SPECIFIER).then((mod) => mod, () => null);
|
|
9
|
+
return enginePromise;
|
|
10
|
+
}
|
|
11
|
+
/** True when the bundled detection engine can be loaded. */
|
|
12
|
+
export async function detectorAvailable() {
|
|
13
|
+
return (await loadEngine()) !== null;
|
|
14
|
+
}
|
|
15
|
+
const HTML_EXTENSIONS = new Set(['.html', '.htm']);
|
|
16
|
+
function mapSeverity(severity) {
|
|
17
|
+
if (severity === 'error')
|
|
18
|
+
return 'error';
|
|
19
|
+
if (severity === 'advisory' || severity === 'info')
|
|
20
|
+
return 'info';
|
|
21
|
+
return 'warning';
|
|
22
|
+
}
|
|
23
|
+
function toFinding(raw, categoryById) {
|
|
24
|
+
const id = String(raw.antipattern ?? raw.id ?? '');
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
category: (categoryById.get(id) ?? 'quality'),
|
|
28
|
+
name: String(raw.name ?? id),
|
|
29
|
+
severity: mapSeverity(raw.severity),
|
|
30
|
+
message: String(raw.description ?? raw.snippet ?? ''),
|
|
31
|
+
file: raw.file ? String(raw.file) : undefined,
|
|
32
|
+
line: raw.line ? Number(raw.line) : undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function detectFile(engine, filePath) {
|
|
36
|
+
if (HTML_EXTENSIONS.has(extname(filePath).toLowerCase())) {
|
|
37
|
+
return engine.detectHtml(filePath);
|
|
38
|
+
}
|
|
39
|
+
return engine.detectText(readFileSync(filePath, 'utf-8'), filePath);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run antipattern detection on a path (file, directory, or URL) using the
|
|
43
|
+
* bundled detection engine (cli/engine/detect-antipatterns.mjs). URL targets
|
|
44
|
+
* require the optional puppeteer dependency.
|
|
45
|
+
*/
|
|
46
|
+
export async function detectAntipatterns(target) {
|
|
47
|
+
const engine = await loadEngine();
|
|
48
|
+
if (!engine) {
|
|
49
|
+
throw new Error('The monodesign detection engine could not be loaded. ' +
|
|
50
|
+
'Reinstall @monoes/monodesign (its cli/engine/ directory is missing or its dependencies are not installed).');
|
|
51
|
+
}
|
|
52
|
+
const start = Date.now();
|
|
53
|
+
const categoryById = new Map(engine.ANTIPATTERNS.map((ap) => [ap.id, ap.category ?? 'quality']));
|
|
54
|
+
let raw = [];
|
|
55
|
+
let fileCount = 0;
|
|
56
|
+
if (/^https?:\/\//i.test(target)) {
|
|
57
|
+
raw = await engine.detectUrl(target);
|
|
58
|
+
fileCount = 1;
|
|
59
|
+
}
|
|
60
|
+
else if (existsSync(target) && statSync(target).isDirectory()) {
|
|
61
|
+
const files = engine.walkDir(target);
|
|
62
|
+
fileCount = files.length;
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
raw.push(...(await detectFile(engine, file)));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (existsSync(target)) {
|
|
68
|
+
raw = await detectFile(engine, target);
|
|
69
|
+
fileCount = 1;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`Target not found: ${target}`);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
findings: raw.map((f) => toFinding(f, categoryById)),
|
|
76
|
+
fileCount,
|
|
77
|
+
durationMs: Date.now() - start,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Format findings as a human-readable string for display in agent output.
|
|
82
|
+
*/
|
|
83
|
+
export function formatFindings(result) {
|
|
84
|
+
if (result.findings.length === 0) {
|
|
85
|
+
return `No antipatterns detected in ${result.fileCount} file(s). (${result.durationMs}ms)`;
|
|
86
|
+
}
|
|
87
|
+
const byCategory = result.findings.reduce((acc, f) => {
|
|
88
|
+
(acc[f.category] ??= []).push(f);
|
|
89
|
+
return acc;
|
|
90
|
+
}, {});
|
|
91
|
+
const lines = [
|
|
92
|
+
`${result.findings.length} finding(s) in ${result.fileCount} file(s) — ${result.durationMs}ms`,
|
|
93
|
+
'',
|
|
94
|
+
];
|
|
95
|
+
for (const [cat, items] of Object.entries(byCategory)) {
|
|
96
|
+
lines.push(`[${cat.toUpperCase()}]`);
|
|
97
|
+
for (const f of items) {
|
|
98
|
+
const loc = f.file ? ` ${f.file}${f.line ? `:${f.line}` : ''}` : '';
|
|
99
|
+
lines.push(` ${f.severity === 'error' ? '✗' : '⚠'} ${f.name}`);
|
|
100
|
+
lines.push(` ${f.message}`);
|
|
101
|
+
if (loc)
|
|
102
|
+
lines.push(loc);
|
|
103
|
+
}
|
|
104
|
+
lines.push('');
|
|
105
|
+
}
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=antipatterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"antipatterns.js","sourceRoot":"","sources":["../src/antipatterns.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* monodesign — frontend design intelligence for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Design tokens (OKLCH color system, spacing, motion, shadows)
|
|
6
|
+
* - Antipattern detection (bundled engine at cli/engine/)
|
|
7
|
+
* - Design rule constants
|
|
8
|
+
* - Skill invocation helpers
|
|
9
|
+
*/
|
|
10
|
+
export * from './tokens.js';
|
|
11
|
+
export * from './antipatterns.js';
|
|
12
|
+
export * from './rules.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared design rules — the invariants every monodesign generation must satisfy.
|
|
3
|
+
* These mirror the "Absolute bans" and "Shared design laws" in the skill.
|
|
4
|
+
*
|
|
5
|
+
* Where a ban has a corresponding mechanical detector rule in the engine
|
|
6
|
+
* registry (cli/engine/registry/antipatterns.mjs), its id and description are
|
|
7
|
+
* pulled from that registry at load time instead of being hand-copied here —
|
|
8
|
+
* one source of truth, no drift between "what generation avoids" and "what
|
|
9
|
+
* detection flags". Bans with no `engineRuleId` are composition/judgment
|
|
10
|
+
* calls the detector cannot mechanically check (holistic layout patterns);
|
|
11
|
+
* they exist here for generation-time guidance only.
|
|
12
|
+
*/
|
|
13
|
+
export interface AbsoluteBan {
|
|
14
|
+
id: string;
|
|
15
|
+
rule: string;
|
|
16
|
+
description: string;
|
|
17
|
+
/** Engine registry rule id this ban is mechanically detected by, if any. */
|
|
18
|
+
engineRuleId?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Static ban list — id/rule/description as authored here (may drift from the engine registry's wording for bans that also have a detector rule). */
|
|
21
|
+
export declare const ABSOLUTE_BANS: AbsoluteBan[];
|
|
22
|
+
/**
|
|
23
|
+
* Same ban list, with descriptions for detector-backed bans (`engineRuleId`
|
|
24
|
+
* set) replaced by the live text from the engine registry. Falls back to the
|
|
25
|
+
* static description when the registry can't be loaded (e.g. package not
|
|
26
|
+
* fully installed) or the referenced rule id is missing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getAbsoluteBans(): Promise<AbsoluteBan[]>;
|
|
29
|
+
export declare const COLOR_STRATEGIES: readonly ['restrained', 'committed', 'full-palette', 'drenched'];
|
|
30
|
+
export type ColorStrategy = (typeof COLOR_STRATEGIES)[number];
|
|
31
|
+
export declare const SHADOW_MAX_ALPHA = 0.15;
|
|
32
|
+
/** Validates a shadow string does not exceed the max alpha. */
|
|
33
|
+
export declare function shadowAlphaComplies(shadow: string): boolean;
|
|
34
|
+
export type Register = 'brand' | 'product';
|
|
35
|
+
/** Infer register from surface description heuristic. */
|
|
36
|
+
export declare function inferRegister(surface: string): Register;
|
|
37
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAsBH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA2CD,qJAAqJ;AACrJ,eAAO,MAAM,aAAa,eAAuB,CAAC;AAElD;;;;;GAKG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAQ9D;AAED,eAAO,MAAM,gBAAgB,YAAI,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,CAAU,CAAC;AACjG,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9D,eAAO,MAAM,gBAAgB,OAAO,CAAC;AAErC,+DAA+D;AAC/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAQ3D;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE3C,yDAAyD;AACzD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAQvD"}
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared design rules — the invariants every monodesign generation must satisfy.
|
|
3
|
+
* These mirror the "Absolute bans" and "Shared design laws" in the skill.
|
|
4
|
+
*
|
|
5
|
+
* Where a ban has a corresponding mechanical detector rule in the engine
|
|
6
|
+
* registry (cli/engine/registry/antipatterns.mjs), its id and description are
|
|
7
|
+
* pulled from that registry at load time instead of being hand-copied here —
|
|
8
|
+
* one source of truth, no drift between "what generation avoids" and "what
|
|
9
|
+
* detection flags". Bans with no `engineRuleId` are composition/judgment
|
|
10
|
+
* calls the detector cannot mechanically check (holistic layout patterns);
|
|
11
|
+
* they exist here for generation-time guidance only.
|
|
12
|
+
*/
|
|
13
|
+
// Typed as plain string so tsc emits a true dynamic import instead of trying
|
|
14
|
+
// to type-resolve the untyped .mjs engine module (matches antipatterns.ts).
|
|
15
|
+
const REGISTRY_SPECIFIER = '../cli/engine/registry/antipatterns.mjs';
|
|
16
|
+
let registryPromise;
|
|
17
|
+
async function loadRegistry() {
|
|
18
|
+
registryPromise ??= import(REGISTRY_SPECIFIER).then((mod) => mod.ANTIPATTERNS, () => null);
|
|
19
|
+
return registryPromise;
|
|
20
|
+
}
|
|
21
|
+
const ABSOLUTE_BANS_STATIC = [
|
|
22
|
+
{
|
|
23
|
+
id: 'side-tab',
|
|
24
|
+
rule: 'No side-stripe borders',
|
|
25
|
+
description: 'border-left or border-right > 1px as a colored accent on cards/list items/callouts is never intentional. Rewrite with full borders, background tints, leading numbers/icons, or nothing.',
|
|
26
|
+
engineRuleId: 'side-tab',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'gradient-text',
|
|
30
|
+
rule: 'No gradient text',
|
|
31
|
+
description: 'background-clip:text with a gradient is decorative and never meaningful. Use a single solid color; emphasis via weight or size.',
|
|
32
|
+
engineRuleId: 'gradient-text',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'glassmorphism-default',
|
|
36
|
+
rule: 'No glassmorphism as default',
|
|
37
|
+
description: 'Blurs and glass cards used decoratively. Rare and purposeful, or nothing.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'hero-metric-template',
|
|
41
|
+
rule: 'No hero-metric template',
|
|
42
|
+
description: 'Big number + small label + supporting stats + gradient accent is a SaaS cliché. Find a distinctive composition instead.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'identical-card-grid',
|
|
46
|
+
rule: 'No identical card grids',
|
|
47
|
+
description: 'Same-sized cards with icon + heading + text, repeated endlessly, signals a lack of hierarchy. Vary size, weight, or content structure.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'modal-first',
|
|
51
|
+
rule: 'Modal is not the first thought',
|
|
52
|
+
description: 'Modals are usually laziness. Exhaust inline / progressive disclosure alternatives first.',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
/** Static ban list — id/rule/description as authored here (may drift from the engine registry's wording for bans that also have a detector rule). */
|
|
56
|
+
export const ABSOLUTE_BANS = ABSOLUTE_BANS_STATIC;
|
|
57
|
+
/**
|
|
58
|
+
* Same ban list, with descriptions for detector-backed bans (`engineRuleId`
|
|
59
|
+
* set) replaced by the live text from the engine registry. Falls back to the
|
|
60
|
+
* static description when the registry can't be loaded (e.g. package not
|
|
61
|
+
* fully installed) or the referenced rule id is missing.
|
|
62
|
+
*/
|
|
63
|
+
export async function getAbsoluteBans() {
|
|
64
|
+
const registry = await loadRegistry();
|
|
65
|
+
if (!registry)
|
|
66
|
+
return ABSOLUTE_BANS_STATIC;
|
|
67
|
+
const byId = new Map(registry.map((r) => [r.id, r]));
|
|
68
|
+
return ABSOLUTE_BANS_STATIC.map((ban) => {
|
|
69
|
+
const engineRule = ban.engineRuleId ? byId.get(ban.engineRuleId) : undefined;
|
|
70
|
+
return engineRule ? { ...ban, description: engineRule.description } : ban;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export const COLOR_STRATEGIES = ['restrained', 'committed', 'full-palette', 'drenched'];
|
|
74
|
+
export const SHADOW_MAX_ALPHA = 0.15;
|
|
75
|
+
/** Validates a shadow string does not exceed the max alpha. */
|
|
76
|
+
export function shadowAlphaComplies(shadow) {
|
|
77
|
+
const matches = shadow.match(/rgba?\([^)]+\)/g) ?? [];
|
|
78
|
+
for (const m of matches) {
|
|
79
|
+
const parts = m.replace(/rgba?\(/, '').replace(')', '').split(',').map(Number);
|
|
80
|
+
const alpha = parts.length === 4 ? parts[3] : 1;
|
|
81
|
+
if (alpha > SHADOW_MAX_ALPHA)
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/** Infer register from surface description heuristic. */
|
|
87
|
+
export function inferRegister(surface) {
|
|
88
|
+
const lower = surface.toLowerCase();
|
|
89
|
+
const brandKeywords = ['landing', 'marketing', 'campaign', 'homepage', 'portfolio', 'blog'];
|
|
90
|
+
const productKeywords = ['dashboard', 'admin', 'app', 'settings', 'onboard', 'console'];
|
|
91
|
+
if (brandKeywords.some((k) => lower.includes(k)))
|
|
92
|
+
return 'brand';
|
|
93
|
+
if (productKeywords.some((k) => lower.includes(k)))
|
|
94
|
+
return 'product';
|
|
95
|
+
return 'product'; // safe default
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Design tokens as typed JS constants — mirrors tokens.css */
|
|
2
|
+
export declare const fontFamily: {
|
|
3
|
+
readonly display: "'Cormorant Garamond', Georgia, serif";
|
|
4
|
+
readonly body: "'Instrument Sans', system-ui, sans-serif";
|
|
5
|
+
readonly mono: "'Space Grotesk', monospace";
|
|
6
|
+
};
|
|
7
|
+
export declare const fontSize: {
|
|
8
|
+
readonly xs: '0.75rem';
|
|
9
|
+
readonly sm: '0.875rem';
|
|
10
|
+
readonly base: '1rem';
|
|
11
|
+
readonly lg: '1.125rem';
|
|
12
|
+
readonly xl: '1.25rem';
|
|
13
|
+
readonly '2xl': '1.5rem';
|
|
14
|
+
readonly '3xl': '1.875rem';
|
|
15
|
+
readonly '4xl': '2.25rem';
|
|
16
|
+
readonly '5xl': '3rem';
|
|
17
|
+
readonly '6xl': '3.75rem';
|
|
18
|
+
readonly '7xl': '4.5rem';
|
|
19
|
+
};
|
|
20
|
+
export declare const spacing: {
|
|
21
|
+
readonly 1: '4px';
|
|
22
|
+
readonly 2: '8px';
|
|
23
|
+
readonly 3: '12px';
|
|
24
|
+
readonly 4: '16px';
|
|
25
|
+
readonly 5: '20px';
|
|
26
|
+
readonly 6: '24px';
|
|
27
|
+
readonly 7: '28px';
|
|
28
|
+
readonly 8: '32px';
|
|
29
|
+
readonly 10: '40px';
|
|
30
|
+
readonly 12: '48px';
|
|
31
|
+
readonly 16: '64px';
|
|
32
|
+
readonly 20: '80px';
|
|
33
|
+
readonly 24: '96px';
|
|
34
|
+
readonly 30: '120px';
|
|
35
|
+
};
|
|
36
|
+
export declare const easing: {
|
|
37
|
+
readonly out: 'cubic-bezier(0.16, 1, 0.3, 1)';
|
|
38
|
+
readonly outQuint: 'cubic-bezier(0.22, 1, 0.36, 1)';
|
|
39
|
+
readonly inOut: 'cubic-bezier(0.65, 0, 0.35, 1)';
|
|
40
|
+
readonly in: 'cubic-bezier(0.4, 0, 1, 1)';
|
|
41
|
+
};
|
|
42
|
+
export declare const duration: {
|
|
43
|
+
readonly instant: '0s';
|
|
44
|
+
readonly fast: '0.15s';
|
|
45
|
+
readonly base: '0.3s';
|
|
46
|
+
readonly slow: '0.6s';
|
|
47
|
+
readonly slower: '0.8s';
|
|
48
|
+
readonly slowest: '1.2s';
|
|
49
|
+
};
|
|
50
|
+
/** OKLCH color palette */
|
|
51
|
+
export declare const color: {
|
|
52
|
+
readonly ink: 'oklch(10% 0 0)';
|
|
53
|
+
readonly charcoal: 'oklch(25% 0 0)';
|
|
54
|
+
readonly ash: 'oklch(55% 0 0)';
|
|
55
|
+
readonly mist: 'oklch(92% 0 0)';
|
|
56
|
+
readonly cream: 'oklch(96% 0.005 350)';
|
|
57
|
+
readonly paper: 'oklch(98% 0 0)';
|
|
58
|
+
readonly white: 'oklch(99.5% 0 0)';
|
|
59
|
+
readonly accent: 'oklch(60% 0.25 350)';
|
|
60
|
+
readonly accentHover: 'oklch(52% 0.25 350)';
|
|
61
|
+
readonly accentDim: 'oklch(60% 0.25 350 / 0.15)';
|
|
62
|
+
readonly accentSoft: 'oklch(60% 0.25 350 / 0.25)';
|
|
63
|
+
readonly accentText: 'oklch(45% 0.22 350)';
|
|
64
|
+
};
|
|
65
|
+
export declare const accentScale: {
|
|
66
|
+
readonly 1: 'oklch(22% 0.15 350)';
|
|
67
|
+
readonly 2: 'oklch(33% 0.20 350)';
|
|
68
|
+
readonly 3: 'oklch(42% 0.23 350)';
|
|
69
|
+
readonly 4: 'oklch(52% 0.25 350)';
|
|
70
|
+
readonly 5: 'oklch(60% 0.25 350)';
|
|
71
|
+
readonly 6: 'oklch(68% 0.22 350)';
|
|
72
|
+
readonly 7: 'oklch(76% 0.18 350)';
|
|
73
|
+
readonly 8: 'oklch(84% 0.12 350)';
|
|
74
|
+
readonly 9: 'oklch(94% 0.05 350)';
|
|
75
|
+
};
|
|
76
|
+
export declare const shadow: {
|
|
77
|
+
readonly none: 'none';
|
|
78
|
+
readonly sm: '0 1px 3px rgba(0, 0, 0, 0.06)';
|
|
79
|
+
readonly md: '0 4px 24px -4px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.06)';
|
|
80
|
+
readonly lg: '0 20px 40px rgba(0, 0, 0, 0.08)';
|
|
81
|
+
readonly accent: '0 20px 60px oklch(60% 0.25 350 / 0.15)';
|
|
82
|
+
};
|
|
83
|
+
export declare const radius: {
|
|
84
|
+
readonly none: '0';
|
|
85
|
+
readonly sm: '4px';
|
|
86
|
+
readonly md: '8px';
|
|
87
|
+
readonly lg: '12px';
|
|
88
|
+
readonly xl: '16px';
|
|
89
|
+
readonly full: '9999px';
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,eAAO,MAAM,UAAU;aACrB,OAAO,EAAE,sCAAsC;aAC/C,IAAI,EAAE,0CAA0C;aAChD,IAAI,EAAE,4BAA4B;CAC1B,CAAC;AAEX,eAAO,MAAM,QAAQ;aACnB,EAAE,EAAE,SAAS;aACb,EAAE,EAAE,UAAU;aACd,IAAI,EAAE,MAAM;aACZ,EAAE,EAAE,UAAU;aACd,EAAE,EAAE,SAAS;aACb,KAAK,EAAE,QAAQ;aACf,KAAK,EAAE,UAAU;aACjB,KAAK,EAAE,SAAS;aAChB,KAAK,EAAE,MAAM;aACb,KAAK,EAAE,SAAS;aAChB,KAAK,EAAE,QAAQ;CACP,CAAC;AAEX,eAAO,MAAM,OAAO;aAClB,CAAC,EAAE,KAAK;aACR,CAAC,EAAE,KAAK;aACR,CAAC,EAAE,MAAM;aACT,CAAC,EAAE,MAAM;aACT,CAAC,EAAE,MAAM;aACT,CAAC,EAAE,MAAM;aACT,CAAC,EAAE,MAAM;aACT,CAAC,EAAE,MAAM;aACT,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,OAAO;CACH,CAAC;AAEX,eAAO,MAAM,MAAM;aACjB,GAAG,EAAE,+BAA+B;aACpC,QAAQ,EAAE,gCAAgC;aAC1C,KAAK,EAAE,gCAAgC;aACvC,EAAE,EAAE,4BAA4B;CACxB,CAAC;AAEX,eAAO,MAAM,QAAQ;aACnB,OAAO,EAAE,IAAI;aACb,IAAI,EAAE,OAAO;aACb,IAAI,EAAE,MAAM;aACZ,IAAI,EAAE,MAAM;aACZ,MAAM,EAAE,MAAM;aACd,OAAO,EAAE,MAAM;CACP,CAAC;AAEX,0BAA0B;AAC1B,eAAO,MAAM,KAAK;aAChB,GAAG,EAAE,gBAAgB;aACrB,QAAQ,EAAE,gBAAgB;aAC1B,GAAG,EAAE,gBAAgB;aACrB,IAAI,EAAE,gBAAgB;aACtB,KAAK,EAAE,sBAAsB;aAC7B,KAAK,EAAE,gBAAgB;aACvB,KAAK,EAAE,kBAAkB;aACzB,MAAM,EAAE,qBAAqB;aAC7B,WAAW,EAAE,qBAAqB;aAClC,SAAS,EAAE,4BAA4B;aACvC,UAAU,EAAE,4BAA4B;aACxC,UAAU,EAAE,qBAAqB;CACzB,CAAC;AAEX,eAAO,MAAM,WAAW;aACtB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;aACxB,CAAC,EAAE,qBAAqB;CAChB,CAAC;AAEX,eAAO,MAAM,MAAM;aACjB,IAAI,EAAE,MAAM;aACZ,EAAE,EAAE,+BAA+B;aACnC,EAAE,EAAE,oEAAoE;aACxE,EAAE,EAAE,iCAAiC;aACrC,MAAM,EAAE,wCAAwC;CACxC,CAAC;AAEX,eAAO,MAAM,MAAM;aACjB,IAAI,EAAE,GAAG;aACT,EAAE,EAAE,KAAK;aACT,EAAE,EAAE,KAAK;aACT,EAAE,EAAE,MAAM;aACV,EAAE,EAAE,MAAM;aACV,IAAI,EAAE,QAAQ;CACN,CAAC"}
|