@monoes/monodesign 1.2.5 → 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/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 +14 -10
- package/src/antipatterns.ts +0 -164
- package/src/rules.ts +0 -129
- package/src/tokens.ts +0 -99
|
@@ -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"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Design tokens as typed JS constants — mirrors tokens.css */
|
|
2
|
+
export const fontFamily = {
|
|
3
|
+
display: "'Cormorant Garamond', Georgia, serif",
|
|
4
|
+
body: "'Instrument Sans', system-ui, sans-serif",
|
|
5
|
+
mono: "'Space Grotesk', monospace",
|
|
6
|
+
};
|
|
7
|
+
export const fontSize = {
|
|
8
|
+
xs: '0.75rem',
|
|
9
|
+
sm: '0.875rem',
|
|
10
|
+
base: '1rem',
|
|
11
|
+
lg: '1.125rem',
|
|
12
|
+
xl: '1.25rem',
|
|
13
|
+
'2xl': '1.5rem',
|
|
14
|
+
'3xl': '1.875rem',
|
|
15
|
+
'4xl': '2.25rem',
|
|
16
|
+
'5xl': '3rem',
|
|
17
|
+
'6xl': '3.75rem',
|
|
18
|
+
'7xl': '4.5rem',
|
|
19
|
+
};
|
|
20
|
+
export const spacing = {
|
|
21
|
+
1: '4px',
|
|
22
|
+
2: '8px',
|
|
23
|
+
3: '12px',
|
|
24
|
+
4: '16px',
|
|
25
|
+
5: '20px',
|
|
26
|
+
6: '24px',
|
|
27
|
+
7: '28px',
|
|
28
|
+
8: '32px',
|
|
29
|
+
10: '40px',
|
|
30
|
+
12: '48px',
|
|
31
|
+
16: '64px',
|
|
32
|
+
20: '80px',
|
|
33
|
+
24: '96px',
|
|
34
|
+
30: '120px',
|
|
35
|
+
};
|
|
36
|
+
export const easing = {
|
|
37
|
+
out: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
38
|
+
outQuint: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
39
|
+
inOut: 'cubic-bezier(0.65, 0, 0.35, 1)',
|
|
40
|
+
in: 'cubic-bezier(0.4, 0, 1, 1)',
|
|
41
|
+
};
|
|
42
|
+
export const duration = {
|
|
43
|
+
instant: '0s',
|
|
44
|
+
fast: '0.15s',
|
|
45
|
+
base: '0.3s',
|
|
46
|
+
slow: '0.6s',
|
|
47
|
+
slower: '0.8s',
|
|
48
|
+
slowest: '1.2s',
|
|
49
|
+
};
|
|
50
|
+
/** OKLCH color palette */
|
|
51
|
+
export const color = {
|
|
52
|
+
ink: 'oklch(10% 0 0)',
|
|
53
|
+
charcoal: 'oklch(25% 0 0)',
|
|
54
|
+
ash: 'oklch(55% 0 0)',
|
|
55
|
+
mist: 'oklch(92% 0 0)',
|
|
56
|
+
cream: 'oklch(96% 0.005 350)',
|
|
57
|
+
paper: 'oklch(98% 0 0)',
|
|
58
|
+
white: 'oklch(99.5% 0 0)',
|
|
59
|
+
accent: 'oklch(60% 0.25 350)',
|
|
60
|
+
accentHover: 'oklch(52% 0.25 350)',
|
|
61
|
+
accentDim: 'oklch(60% 0.25 350 / 0.15)',
|
|
62
|
+
accentSoft: 'oklch(60% 0.25 350 / 0.25)',
|
|
63
|
+
accentText: 'oklch(45% 0.22 350)',
|
|
64
|
+
};
|
|
65
|
+
export const accentScale = {
|
|
66
|
+
1: 'oklch(22% 0.15 350)',
|
|
67
|
+
2: 'oklch(33% 0.20 350)',
|
|
68
|
+
3: 'oklch(42% 0.23 350)',
|
|
69
|
+
4: 'oklch(52% 0.25 350)',
|
|
70
|
+
5: 'oklch(60% 0.25 350)',
|
|
71
|
+
6: 'oklch(68% 0.22 350)',
|
|
72
|
+
7: 'oklch(76% 0.18 350)',
|
|
73
|
+
8: 'oklch(84% 0.12 350)',
|
|
74
|
+
9: 'oklch(94% 0.05 350)',
|
|
75
|
+
};
|
|
76
|
+
export const shadow = {
|
|
77
|
+
none: 'none',
|
|
78
|
+
sm: '0 1px 3px rgba(0, 0, 0, 0.06)',
|
|
79
|
+
md: '0 4px 24px -4px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.06)',
|
|
80
|
+
lg: '0 20px 40px rgba(0, 0, 0, 0.08)',
|
|
81
|
+
accent: '0 20px 60px oklch(60% 0.25 350 / 0.15)',
|
|
82
|
+
};
|
|
83
|
+
export const radius = {
|
|
84
|
+
none: '0',
|
|
85
|
+
sm: '4px',
|
|
86
|
+
md: '8px',
|
|
87
|
+
lg: '12px',
|
|
88
|
+
xl: '16px',
|
|
89
|
+
full: '9999px',
|
|
90
|
+
};
|
|
91
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,sCAAsC;IAC/C,IAAI,EAAE,0CAA0C;IAChD,IAAI,EAAE,4BAA4B;CAC1B,CAAC;AAEX,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,MAAM;IACZ,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,SAAS;IACb,KAAK,EAAE,QAAQ;IACf,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,QAAQ;CACP,CAAC;AAEX,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,CAAC,EAAE,KAAK;IACR,CAAC,EAAE,KAAK;IACR,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,CAAC,EAAE,MAAM;IACT,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,OAAO;CACH,CAAC;AAEX,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,GAAG,EAAE,+BAA+B;IACpC,QAAQ,EAAE,gCAAgC;IAC1C,KAAK,EAAE,gCAAgC;IACvC,EAAE,EAAE,4BAA4B;CACxB,CAAC;AAEX,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO,EAAE,IAAI;IACb,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,MAAM;IACZ,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,MAAM;IACd,OAAO,EAAE,MAAM;CACP,CAAC;AAEX,0BAA0B;AAC1B,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,GAAG,EAAE,gBAAgB;IACrB,QAAQ,EAAE,gBAAgB;IAC1B,GAAG,EAAE,gBAAgB;IACrB,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,gBAAgB;IACvB,KAAK,EAAE,kBAAkB;IACzB,MAAM,EAAE,qBAAqB;IAC7B,WAAW,EAAE,qBAAqB;IAClC,SAAS,EAAE,4BAA4B;IACvC,UAAU,EAAE,4BAA4B;IACxC,UAAU,EAAE,qBAAqB;CACzB,CAAC;AAEX,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;IACxB,CAAC,EAAE,qBAAqB;CAChB,CAAC;AAEX,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,MAAM;IACZ,EAAE,EAAE,+BAA+B;IACnC,EAAE,EAAE,oEAAoE;IACxE,EAAE,EAAE,iCAAiC;IACrC,MAAM,EAAE,wCAAwC;CACxC,CAAC;AAEX,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,GAAG;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,KAAK;IACT,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,QAAQ;CACN,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monodesign",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "Frontend design intelligence for AI agents — design tokens, antipattern detection, and the monodesign skill",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".": "./
|
|
9
|
+
".": "./dist/index.js",
|
|
9
10
|
"./tokens": "./src/tokens.css",
|
|
10
11
|
"./engine": "./cli/engine/detect-antipatterns.mjs",
|
|
11
12
|
"./engine/browser": "./cli/engine/detect-antipatterns-browser.js"
|
|
@@ -15,10 +16,18 @@
|
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
18
|
"cli/",
|
|
19
|
+
"dist/",
|
|
18
20
|
"skill/",
|
|
19
|
-
"src/",
|
|
21
|
+
"src/tokens.css",
|
|
20
22
|
"NOTICE.md"
|
|
21
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"prebuild": "node ../../../scripts/build-fs.mjs clean dist tsconfig.tsbuildinfo",
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"prepublishOnly": "npm run build",
|
|
28
|
+
"test": "node scripts/run-tests.mjs",
|
|
29
|
+
"sync": "node scripts/sync-skill.mjs"
|
|
30
|
+
},
|
|
22
31
|
"keywords": [
|
|
23
32
|
"design-system",
|
|
24
33
|
"design-tokens",
|
|
@@ -41,10 +50,5 @@
|
|
|
41
50
|
"optionalDependencies": {
|
|
42
51
|
"@monoes/monobrowse": "^1.0.7",
|
|
43
52
|
"puppeteer": "^25.1.0"
|
|
44
|
-
},
|
|
45
|
-
"scripts": {
|
|
46
|
-
"build": "tsc",
|
|
47
|
-
"test": "node scripts/run-tests.mjs",
|
|
48
|
-
"sync": "node scripts/sync-skill.mjs"
|
|
49
53
|
}
|
|
50
|
-
}
|
|
54
|
+
}
|
package/src/antipatterns.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { extname } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export type AntipatternCategory = 'slop' | 'quality' | 'performance' | 'accessibility';
|
|
5
|
-
|
|
6
|
-
export interface AntipatternFinding {
|
|
7
|
-
id: string;
|
|
8
|
-
category: AntipatternCategory;
|
|
9
|
-
name: string;
|
|
10
|
-
severity: 'error' | 'warning' | 'info';
|
|
11
|
-
message: string;
|
|
12
|
-
file?: string;
|
|
13
|
-
line?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DetectionResult {
|
|
17
|
-
findings: AntipatternFinding[];
|
|
18
|
-
fileCount: number;
|
|
19
|
-
durationMs: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Shape of the bundled engine (cli/engine/detect-antipatterns.mjs).
|
|
23
|
-
interface DetectorEngine {
|
|
24
|
-
detectText: (content: string, filePath: string, options?: Record<string, unknown>) => RawFinding[];
|
|
25
|
-
detectHtml: (filePath: string, options?: Record<string, unknown>) => Promise<RawFinding[]>;
|
|
26
|
-
detectUrl: (url: string, options?: Record<string, unknown>) => Promise<RawFinding[]>;
|
|
27
|
-
walkDir: (dir: string) => string[];
|
|
28
|
-
ANTIPATTERNS: Array<{ id: string; category?: string; severity?: string }>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface RawFinding {
|
|
32
|
-
antipattern?: string;
|
|
33
|
-
id?: string;
|
|
34
|
-
name?: string;
|
|
35
|
-
description?: string;
|
|
36
|
-
severity?: string;
|
|
37
|
-
file?: string;
|
|
38
|
-
line?: number;
|
|
39
|
-
snippet?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Typed as plain string so tsc emits a true dynamic import instead of trying
|
|
43
|
-
// to type-resolve the untyped .mjs engine module.
|
|
44
|
-
const ENGINE_SPECIFIER: string = '../cli/engine/detect-antipatterns.mjs';
|
|
45
|
-
|
|
46
|
-
let enginePromise: Promise<DetectorEngine | null> | undefined;
|
|
47
|
-
|
|
48
|
-
async function loadEngine(): Promise<DetectorEngine | null> {
|
|
49
|
-
enginePromise ??= import(ENGINE_SPECIFIER).then(
|
|
50
|
-
(mod) => mod as unknown as DetectorEngine,
|
|
51
|
-
() => null,
|
|
52
|
-
);
|
|
53
|
-
return enginePromise;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** True when the bundled detection engine can be loaded. */
|
|
57
|
-
export async function detectorAvailable(): Promise<boolean> {
|
|
58
|
-
return (await loadEngine()) !== null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const HTML_EXTENSIONS = new Set(['.html', '.htm']);
|
|
62
|
-
|
|
63
|
-
function mapSeverity(severity: string | undefined): AntipatternFinding['severity'] {
|
|
64
|
-
if (severity === 'error') return 'error';
|
|
65
|
-
if (severity === 'advisory' || severity === 'info') return 'info';
|
|
66
|
-
return 'warning';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function toFinding(raw: RawFinding, categoryById: Map<string, string>): AntipatternFinding {
|
|
70
|
-
const id = String(raw.antipattern ?? raw.id ?? '');
|
|
71
|
-
return {
|
|
72
|
-
id,
|
|
73
|
-
category: (categoryById.get(id) ?? 'quality') as AntipatternCategory,
|
|
74
|
-
name: String(raw.name ?? id),
|
|
75
|
-
severity: mapSeverity(raw.severity),
|
|
76
|
-
message: String(raw.description ?? raw.snippet ?? ''),
|
|
77
|
-
file: raw.file ? String(raw.file) : undefined,
|
|
78
|
-
line: raw.line ? Number(raw.line) : undefined,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function detectFile(engine: DetectorEngine, filePath: string): Promise<RawFinding[]> {
|
|
83
|
-
if (HTML_EXTENSIONS.has(extname(filePath).toLowerCase())) {
|
|
84
|
-
return engine.detectHtml(filePath);
|
|
85
|
-
}
|
|
86
|
-
return engine.detectText(readFileSync(filePath, 'utf-8'), filePath);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Run antipattern detection on a path (file, directory, or URL) using the
|
|
91
|
-
* bundled detection engine (cli/engine/detect-antipatterns.mjs). URL targets
|
|
92
|
-
* require the optional puppeteer dependency.
|
|
93
|
-
*/
|
|
94
|
-
export async function detectAntipatterns(target: string): Promise<DetectionResult> {
|
|
95
|
-
const engine = await loadEngine();
|
|
96
|
-
if (!engine) {
|
|
97
|
-
throw new Error(
|
|
98
|
-
'The monodesign detection engine could not be loaded. ' +
|
|
99
|
-
'Reinstall @monoes/monodesign (its cli/engine/ directory is missing or its dependencies are not installed).',
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const start = Date.now();
|
|
104
|
-
const categoryById = new Map(
|
|
105
|
-
engine.ANTIPATTERNS.map((ap) => [ap.id, ap.category ?? 'quality']),
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
let raw: RawFinding[] = [];
|
|
109
|
-
let fileCount = 0;
|
|
110
|
-
|
|
111
|
-
if (/^https?:\/\//i.test(target)) {
|
|
112
|
-
raw = await engine.detectUrl(target);
|
|
113
|
-
fileCount = 1;
|
|
114
|
-
} else if (existsSync(target) && statSync(target).isDirectory()) {
|
|
115
|
-
const files = engine.walkDir(target);
|
|
116
|
-
fileCount = files.length;
|
|
117
|
-
for (const file of files) {
|
|
118
|
-
raw.push(...(await detectFile(engine, file)));
|
|
119
|
-
}
|
|
120
|
-
} else if (existsSync(target)) {
|
|
121
|
-
raw = await detectFile(engine, target);
|
|
122
|
-
fileCount = 1;
|
|
123
|
-
} else {
|
|
124
|
-
throw new Error(`Target not found: ${target}`);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
findings: raw.map((f) => toFinding(f, categoryById)),
|
|
129
|
-
fileCount,
|
|
130
|
-
durationMs: Date.now() - start,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Format findings as a human-readable string for display in agent output.
|
|
136
|
-
*/
|
|
137
|
-
export function formatFindings(result: DetectionResult): string {
|
|
138
|
-
if (result.findings.length === 0) {
|
|
139
|
-
return `No antipatterns detected in ${result.fileCount} file(s). (${result.durationMs}ms)`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const byCategory = result.findings.reduce<Record<string, AntipatternFinding[]>>((acc, f) => {
|
|
143
|
-
(acc[f.category] ??= []).push(f);
|
|
144
|
-
return acc;
|
|
145
|
-
}, {});
|
|
146
|
-
|
|
147
|
-
const lines: string[] = [
|
|
148
|
-
`${result.findings.length} finding(s) in ${result.fileCount} file(s) — ${result.durationMs}ms`,
|
|
149
|
-
'',
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
for (const [cat, items] of Object.entries(byCategory)) {
|
|
153
|
-
lines.push(`[${cat.toUpperCase()}]`);
|
|
154
|
-
for (const f of items) {
|
|
155
|
-
const loc = f.file ? ` ${f.file}${f.line ? `:${f.line}` : ''}` : '';
|
|
156
|
-
lines.push(` ${f.severity === 'error' ? '✗' : '⚠'} ${f.name}`);
|
|
157
|
-
lines.push(` ${f.message}`);
|
|
158
|
-
if (loc) lines.push(loc);
|
|
159
|
-
}
|
|
160
|
-
lines.push('');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return lines.join('\n');
|
|
164
|
-
}
|
package/src/rules.ts
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
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
|
-
|
|
14
|
-
// Typed as plain string so tsc emits a true dynamic import instead of trying
|
|
15
|
-
// to type-resolve the untyped .mjs engine module (matches antipatterns.ts).
|
|
16
|
-
const REGISTRY_SPECIFIER: string = '../cli/engine/registry/antipatterns.mjs';
|
|
17
|
-
|
|
18
|
-
interface EngineRuleEntry {
|
|
19
|
-
id: string;
|
|
20
|
-
name: string;
|
|
21
|
-
description: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let registryPromise: Promise<EngineRuleEntry[] | null> | undefined;
|
|
25
|
-
|
|
26
|
-
async function loadRegistry(): Promise<EngineRuleEntry[] | null> {
|
|
27
|
-
registryPromise ??= import(REGISTRY_SPECIFIER).then(
|
|
28
|
-
(mod) => (mod as { ANTIPATTERNS: EngineRuleEntry[] }).ANTIPATTERNS,
|
|
29
|
-
() => null,
|
|
30
|
-
);
|
|
31
|
-
return registryPromise;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface AbsoluteBan {
|
|
35
|
-
id: string;
|
|
36
|
-
rule: string;
|
|
37
|
-
description: string;
|
|
38
|
-
/** Engine registry rule id this ban is mechanically detected by, if any. */
|
|
39
|
-
engineRuleId?: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const ABSOLUTE_BANS_STATIC: AbsoluteBan[] = [
|
|
43
|
-
{
|
|
44
|
-
id: 'side-tab',
|
|
45
|
-
rule: 'No side-stripe borders',
|
|
46
|
-
description:
|
|
47
|
-
'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.',
|
|
48
|
-
engineRuleId: 'side-tab',
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
id: 'gradient-text',
|
|
52
|
-
rule: 'No gradient text',
|
|
53
|
-
description:
|
|
54
|
-
'background-clip:text with a gradient is decorative and never meaningful. Use a single solid color; emphasis via weight or size.',
|
|
55
|
-
engineRuleId: 'gradient-text',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: 'glassmorphism-default',
|
|
59
|
-
rule: 'No glassmorphism as default',
|
|
60
|
-
description:
|
|
61
|
-
'Blurs and glass cards used decoratively. Rare and purposeful, or nothing.',
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
id: 'hero-metric-template',
|
|
65
|
-
rule: 'No hero-metric template',
|
|
66
|
-
description:
|
|
67
|
-
'Big number + small label + supporting stats + gradient accent is a SaaS cliché. Find a distinctive composition instead.',
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
id: 'identical-card-grid',
|
|
71
|
-
rule: 'No identical card grids',
|
|
72
|
-
description:
|
|
73
|
-
'Same-sized cards with icon + heading + text, repeated endlessly, signals a lack of hierarchy. Vary size, weight, or content structure.',
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: 'modal-first',
|
|
77
|
-
rule: 'Modal is not the first thought',
|
|
78
|
-
description:
|
|
79
|
-
'Modals are usually laziness. Exhaust inline / progressive disclosure alternatives first.',
|
|
80
|
-
},
|
|
81
|
-
];
|
|
82
|
-
|
|
83
|
-
/** 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). */
|
|
84
|
-
export const ABSOLUTE_BANS = ABSOLUTE_BANS_STATIC;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Same ban list, with descriptions for detector-backed bans (`engineRuleId`
|
|
88
|
-
* set) replaced by the live text from the engine registry. Falls back to the
|
|
89
|
-
* static description when the registry can't be loaded (e.g. package not
|
|
90
|
-
* fully installed) or the referenced rule id is missing.
|
|
91
|
-
*/
|
|
92
|
-
export async function getAbsoluteBans(): Promise<AbsoluteBan[]> {
|
|
93
|
-
const registry = await loadRegistry();
|
|
94
|
-
if (!registry) return ABSOLUTE_BANS_STATIC;
|
|
95
|
-
const byId = new Map(registry.map((r) => [r.id, r]));
|
|
96
|
-
return ABSOLUTE_BANS_STATIC.map((ban) => {
|
|
97
|
-
const engineRule = ban.engineRuleId ? byId.get(ban.engineRuleId) : undefined;
|
|
98
|
-
return engineRule ? { ...ban, description: engineRule.description } : ban;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export const COLOR_STRATEGIES = ['restrained', 'committed', 'full-palette', 'drenched'] as const;
|
|
103
|
-
export type ColorStrategy = (typeof COLOR_STRATEGIES)[number];
|
|
104
|
-
|
|
105
|
-
export const SHADOW_MAX_ALPHA = 0.15;
|
|
106
|
-
|
|
107
|
-
/** Validates a shadow string does not exceed the max alpha. */
|
|
108
|
-
export function shadowAlphaComplies(shadow: string): boolean {
|
|
109
|
-
const matches = shadow.match(/rgba?\([^)]+\)/g) ?? [];
|
|
110
|
-
for (const m of matches) {
|
|
111
|
-
const parts = m.replace(/rgba?\(/, '').replace(')', '').split(',').map(Number);
|
|
112
|
-
const alpha = parts.length === 4 ? parts[3] : 1;
|
|
113
|
-
if (alpha > SHADOW_MAX_ALPHA) return false;
|
|
114
|
-
}
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export type Register = 'brand' | 'product';
|
|
119
|
-
|
|
120
|
-
/** Infer register from surface description heuristic. */
|
|
121
|
-
export function inferRegister(surface: string): Register {
|
|
122
|
-
const lower = surface.toLowerCase();
|
|
123
|
-
const brandKeywords = ['landing', 'marketing', 'campaign', 'homepage', 'portfolio', 'blog'];
|
|
124
|
-
const productKeywords = ['dashboard', 'admin', 'app', 'settings', 'onboard', 'console'];
|
|
125
|
-
|
|
126
|
-
if (brandKeywords.some((k) => lower.includes(k))) return 'brand';
|
|
127
|
-
if (productKeywords.some((k) => lower.includes(k))) return 'product';
|
|
128
|
-
return 'product'; // safe default
|
|
129
|
-
}
|
package/src/tokens.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/** Design tokens as typed JS constants — mirrors tokens.css */
|
|
2
|
-
|
|
3
|
-
export const fontFamily = {
|
|
4
|
-
display: "'Cormorant Garamond', Georgia, serif",
|
|
5
|
-
body: "'Instrument Sans', system-ui, sans-serif",
|
|
6
|
-
mono: "'Space Grotesk', monospace",
|
|
7
|
-
} as const;
|
|
8
|
-
|
|
9
|
-
export const fontSize = {
|
|
10
|
-
xs: '0.75rem',
|
|
11
|
-
sm: '0.875rem',
|
|
12
|
-
base: '1rem',
|
|
13
|
-
lg: '1.125rem',
|
|
14
|
-
xl: '1.25rem',
|
|
15
|
-
'2xl': '1.5rem',
|
|
16
|
-
'3xl': '1.875rem',
|
|
17
|
-
'4xl': '2.25rem',
|
|
18
|
-
'5xl': '3rem',
|
|
19
|
-
'6xl': '3.75rem',
|
|
20
|
-
'7xl': '4.5rem',
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
export const spacing = {
|
|
24
|
-
1: '4px',
|
|
25
|
-
2: '8px',
|
|
26
|
-
3: '12px',
|
|
27
|
-
4: '16px',
|
|
28
|
-
5: '20px',
|
|
29
|
-
6: '24px',
|
|
30
|
-
7: '28px',
|
|
31
|
-
8: '32px',
|
|
32
|
-
10: '40px',
|
|
33
|
-
12: '48px',
|
|
34
|
-
16: '64px',
|
|
35
|
-
20: '80px',
|
|
36
|
-
24: '96px',
|
|
37
|
-
30: '120px',
|
|
38
|
-
} as const;
|
|
39
|
-
|
|
40
|
-
export const easing = {
|
|
41
|
-
out: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
42
|
-
outQuint: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
43
|
-
inOut: 'cubic-bezier(0.65, 0, 0.35, 1)',
|
|
44
|
-
in: 'cubic-bezier(0.4, 0, 1, 1)',
|
|
45
|
-
} as const;
|
|
46
|
-
|
|
47
|
-
export const duration = {
|
|
48
|
-
instant: '0s',
|
|
49
|
-
fast: '0.15s',
|
|
50
|
-
base: '0.3s',
|
|
51
|
-
slow: '0.6s',
|
|
52
|
-
slower: '0.8s',
|
|
53
|
-
slowest: '1.2s',
|
|
54
|
-
} as const;
|
|
55
|
-
|
|
56
|
-
/** OKLCH color palette */
|
|
57
|
-
export const color = {
|
|
58
|
-
ink: 'oklch(10% 0 0)',
|
|
59
|
-
charcoal: 'oklch(25% 0 0)',
|
|
60
|
-
ash: 'oklch(55% 0 0)',
|
|
61
|
-
mist: 'oklch(92% 0 0)',
|
|
62
|
-
cream: 'oklch(96% 0.005 350)',
|
|
63
|
-
paper: 'oklch(98% 0 0)',
|
|
64
|
-
white: 'oklch(99.5% 0 0)',
|
|
65
|
-
accent: 'oklch(60% 0.25 350)',
|
|
66
|
-
accentHover: 'oklch(52% 0.25 350)',
|
|
67
|
-
accentDim: 'oklch(60% 0.25 350 / 0.15)',
|
|
68
|
-
accentSoft: 'oklch(60% 0.25 350 / 0.25)',
|
|
69
|
-
accentText: 'oklch(45% 0.22 350)',
|
|
70
|
-
} as const;
|
|
71
|
-
|
|
72
|
-
export const accentScale = {
|
|
73
|
-
1: 'oklch(22% 0.15 350)',
|
|
74
|
-
2: 'oklch(33% 0.20 350)',
|
|
75
|
-
3: 'oklch(42% 0.23 350)',
|
|
76
|
-
4: 'oklch(52% 0.25 350)',
|
|
77
|
-
5: 'oklch(60% 0.25 350)',
|
|
78
|
-
6: 'oklch(68% 0.22 350)',
|
|
79
|
-
7: 'oklch(76% 0.18 350)',
|
|
80
|
-
8: 'oklch(84% 0.12 350)',
|
|
81
|
-
9: 'oklch(94% 0.05 350)',
|
|
82
|
-
} as const;
|
|
83
|
-
|
|
84
|
-
export const shadow = {
|
|
85
|
-
none: 'none',
|
|
86
|
-
sm: '0 1px 3px rgba(0, 0, 0, 0.06)',
|
|
87
|
-
md: '0 4px 24px -4px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.06)',
|
|
88
|
-
lg: '0 20px 40px rgba(0, 0, 0, 0.08)',
|
|
89
|
-
accent: '0 20px 60px oklch(60% 0.25 350 / 0.15)',
|
|
90
|
-
} as const;
|
|
91
|
-
|
|
92
|
-
export const radius = {
|
|
93
|
-
none: '0',
|
|
94
|
-
sm: '4px',
|
|
95
|
-
md: '8px',
|
|
96
|
-
lg: '12px',
|
|
97
|
-
xl: '16px',
|
|
98
|
-
full: '9999px',
|
|
99
|
-
} as const;
|