adsa-cli 0.1.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/LICENSE +21 -0
- package/README.md +169 -0
- package/bin/adsa.mjs +411 -0
- package/lib/badge.mjs +28 -0
- package/lib/color.mjs +32 -0
- package/lib/config.mjs +103 -0
- package/lib/dense.mjs +21 -0
- package/lib/eval.mjs +140 -0
- package/lib/fix.mjs +260 -0
- package/lib/fsx.mjs +94 -0
- package/lib/history.mjs +41 -0
- package/lib/mcp.mjs +162 -0
- package/lib/report.mjs +716 -0
- package/lib/scan.mjs +607 -0
- package/lib/score.mjs +197 -0
- package/package.json +41 -0
- package/rubric/rubric.json +115 -0
- package/skills/ds-audit/SKILL.md +135 -0
- package/templates/AGENTS.android.md.tmpl +22 -0
- package/templates/AGENTS.md.tmpl +17 -0
- package/templates/AGENTS.react-native.md.tmpl +21 -0
- package/templates/AGENTS.swift.md.tmpl +23 -0
- package/templates/GAPS.md.tmpl +17 -0
- package/templates/briefs/a11y-docs.md +35 -0
- package/templates/briefs/coverage-gate.md +23 -0
- package/templates/briefs/examples-check.md +37 -0
- package/templates/briefs/patterns-doc.md +32 -0
- package/templates/briefs/prop-tables.md +41 -0
- package/templates/ci/adsa.yml +19 -0
- package/templates/mcp/mcp.json +8 -0
- package/templates/tokens.android.md.tmpl +32 -0
- package/templates/tokens.md.tmpl +31 -0
- package/templates/tokens.native.md.tmpl +32 -0
- package/templates/tokens.swift.md.tmpl +32 -0
package/lib/score.mjs
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns facts into a score. Every dimension returns the evidence it used, so the
|
|
3
|
+
* number is arguable: a maintainer who disagrees can point at the line that produced it.
|
|
4
|
+
*/
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
|
|
9
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
export const RUBRIC = JSON.parse(readFileSync(join(here, "..", "rubric", "rubric.json"), "utf8"));
|
|
11
|
+
|
|
12
|
+
const pct = (n) => `${Math.round(n * 100)}%`;
|
|
13
|
+
|
|
14
|
+
/** What "accessibility documentation" means in the vocabulary of each platform. */
|
|
15
|
+
const A11Y_LABEL = {
|
|
16
|
+
web: "keyboard or accessibility",
|
|
17
|
+
"react-native": "VoiceOver/TalkBack or accessibility",
|
|
18
|
+
swift: "VoiceOver/Dynamic Type",
|
|
19
|
+
android: "TalkBack/content description",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const SCORERS = {
|
|
23
|
+
"agent-instructions"(f) {
|
|
24
|
+
const files = f.agentFiles.filter((a) => a.lines > 3);
|
|
25
|
+
const main = files.find((a) => a.mentionsPackage) || files[0];
|
|
26
|
+
if (!main) return { score: 1, evidence: ["No AGENTS.md, CLAUDE.md or equivalent in the repository."] };
|
|
27
|
+
const ev = [`${main.file}, ${main.lines} lines.`];
|
|
28
|
+
if (!main.mentionsPackage) {
|
|
29
|
+
ev.push(`It never mentions ${f.name}, so an agent gets no design-system rules from it.`);
|
|
30
|
+
return { score: 1, evidence: ev };
|
|
31
|
+
}
|
|
32
|
+
const parts = [
|
|
33
|
+
["an import rule", main.hasImportRule],
|
|
34
|
+
["a lookup command", main.hasLookupCommand],
|
|
35
|
+
["token rules", main.hasTokenRule],
|
|
36
|
+
["a list of what is forbidden", main.hasForbidden],
|
|
37
|
+
["a check to run before finishing", main.hasFinishCheck],
|
|
38
|
+
];
|
|
39
|
+
const present = parts.filter(([, ok]) => ok).map(([label]) => label);
|
|
40
|
+
const absent = parts.filter(([, ok]) => !ok).map(([label]) => label);
|
|
41
|
+
ev.push(`Contains ${present.join(", ") || "none of the expected rules"}.`);
|
|
42
|
+
if (absent.length) ev.push(`Missing ${absent.join(", ")}.`);
|
|
43
|
+
return { score: absent.length === 0 ? 5 : present.length >= 2 ? 3 : 1, evidence: ev };
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"machine-surface"(f) {
|
|
47
|
+
const m = f.machine;
|
|
48
|
+
const ev = [];
|
|
49
|
+
if (m.mcpInPackage) ev.push(`An MCP server ships inside the package (bin: ${m.binNames.join(", ")}).`);
|
|
50
|
+
if (m.declaredServers.length) ev.push(`${m.mcpConfigs.join(", ")} registers ${m.declaredServers.join(", ")}.`);
|
|
51
|
+
if (m.cliShipped && !m.mcpInPackage) ev.push(`A CLI ships with the package (bin: ${m.binNames.join(", ")}).`);
|
|
52
|
+
if (m.llmsTxt.length) ev.push(`${m.llmsTxt.join(", ")} present.`);
|
|
53
|
+
if (m.skills.length) ev.push(`${m.skills.length} agent skill${m.skills.length === 1 ? "" : "s"} published.`);
|
|
54
|
+
if (m.storybookMcp) ev.push("@storybook/addon-mcp is installed, which answers from a running Storybook in this repo only.");
|
|
55
|
+
if (!ev.length) ev.push("No MCP server, no docs CLI, no llms.txt, no published skills: an agent has to read files.");
|
|
56
|
+
const score = m.mcpInPackage || m.declaredServers.length ? 5 : m.cliShipped || m.llmsTxt.length || m.skills.length || m.storybookMcp ? 3 : 1;
|
|
57
|
+
return { score, evidence: ev };
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
"docs-coverage"(f) {
|
|
61
|
+
const c = f.coverage;
|
|
62
|
+
if (!c.total) return { score: 1, evidence: ["No importable components detected — check `source` in adsa.config.json."] };
|
|
63
|
+
const gate = f.verification.docChecks || f.verification.scoreGate;
|
|
64
|
+
const ev = [`${c.documented} of ${c.total} importable components have a guide (${pct(c.ratio)}).`];
|
|
65
|
+
if (f.icons) ev.push(`${f.icons} icon or logo exports were set aside; nobody writes a guide per icon.`);
|
|
66
|
+
if (c.missing.length) ev.push(`Undocumented, first few: ${c.missing.slice(0, 8).join(", ")}.`);
|
|
67
|
+
ev.push(gate ? "CI runs a documentation check." : "Nothing in CI stops a new export from shipping without a guide.");
|
|
68
|
+
const score = c.ratio >= 0.9 && gate ? 5 : c.ratio >= 0.7 ? 3 : 1;
|
|
69
|
+
return { score, evidence: ev };
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
"docs-freshness"(f) {
|
|
73
|
+
const s = f.freshness;
|
|
74
|
+
const ev = [`${f.guides.length} guides, ${s.blocks} code blocks, ${s.guidesWithPropTable} with a prop table.`];
|
|
75
|
+
if (s.guidesGenerated) ev.push(`${s.guidesGenerated} guides carry a generated marker.`);
|
|
76
|
+
else ev.push("No guide is marked as generated, so every table is hand-maintained.");
|
|
77
|
+
if (s.unknownCount) ev.push(`${s.unknownCount} imports in the guides name symbols this repo does not export, e.g. ${s.unknownImports.slice(0, 3).map((u) => `${u.name} in ${u.guide}`).join("; ")}.`);
|
|
78
|
+
else ev.push("Every symbol imported in the guides exists in the source.");
|
|
79
|
+
ev.push(s.compiledInCi ? "CI checks the guides against the code." : "Nothing compares the guides to the code.");
|
|
80
|
+
let score = s.compiledInCi && s.guidesGenerated ? 5 : s.guidesGenerated || s.guidesWithPropTable ? 3 : 1;
|
|
81
|
+
if (s.unknownCount > 5) score = 1;
|
|
82
|
+
else if (s.unknownCount > 0) score = Math.min(score, 3);
|
|
83
|
+
return { score, evidence: ev };
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
tokens(f) {
|
|
87
|
+
const t = f.tokens;
|
|
88
|
+
const ev = [];
|
|
89
|
+
ev.push(t.docs.length ? `Token documentation: ${t.docs.slice(0, 4).join(", ")}.` : "No token documentation found in the guides.");
|
|
90
|
+
const source = t.source;
|
|
91
|
+
if (!t.docs.length && source && source.count) {
|
|
92
|
+
const noun = source.count === 1 ? "named token" : "named tokens";
|
|
93
|
+
ev.push(`${source.count} ${noun} defined in ${source.kind} (${source.files.slice(0, 2).join(", ")}), e.g. ${source.names.slice(0, 3).join(", ")} — real, but nothing in prose describes when to use which.`);
|
|
94
|
+
}
|
|
95
|
+
ev.push(`${t.motion ? "Motion documented" : "No motion documentation"}; ${t.spacing ? "spacing documented" : "no spacing documentation"}.`);
|
|
96
|
+
if (t.rawPaletteCount) ev.push(`${t.rawPaletteCount} raw palette classes in guide examples, e.g. ${t.rawPalette.slice(0, 3).map((h) => h.value).join(", ")}.`);
|
|
97
|
+
if (t.hexCount) ev.push(`${t.hexCount} raw hex values in guide examples.`);
|
|
98
|
+
const hasNamedTokens = t.docs.length > 0 || Boolean(source && source.count);
|
|
99
|
+
let score = t.docs.length && t.motion && t.spacing ? 5 : hasNamedTokens ? 3 : 1;
|
|
100
|
+
if (t.rawPaletteCount > 0) score = Math.min(score, 3);
|
|
101
|
+
return { score, evidence: ev };
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
patterns(f) {
|
|
105
|
+
const p = f.patterns;
|
|
106
|
+
const ev = [];
|
|
107
|
+
if (p.files.length) ev.push(`Pattern-level docs: ${p.files.slice(0, 4).join(", ")}.`);
|
|
108
|
+
if (p.mapFound) ev.push("A task-to-component table exists in the guides.");
|
|
109
|
+
if (!p.files.length && !p.mapFound) ev.push("Components only. Nothing describes how a page is assembled.");
|
|
110
|
+
if (p.files.length && !p.rich) ev.push("The pattern docs do not cover states or traps, so an agent still guesses the empty and error cases.");
|
|
111
|
+
return { score: p.rich ? 5 : p.files.length || p.mapFound ? 3 : 1, evidence: ev };
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
a11y(f) {
|
|
115
|
+
const a = f.a11y;
|
|
116
|
+
const label = A11Y_LABEL[a.platform] || A11Y_LABEL.web;
|
|
117
|
+
const ev = [`${a.withSection} of ${a.total} guides carry a ${label} section (${pct(a.ratio)}).`];
|
|
118
|
+
if (a.generated) ev.push("At least some of those sections are generated rather than hand-written.");
|
|
119
|
+
ev.push(a.automation ? "Accessibility checks run in CI." : "No automated accessibility check found.");
|
|
120
|
+
if (a.baseline.length) ev.push(`A known-violations baseline exists: ${a.baseline.join(", ")}.`);
|
|
121
|
+
return { score: a.ratio >= 0.9 && (a.generated || a.automation) ? 5 : a.ratio >= 0.3 ? 3 : 1, evidence: ev };
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
verification(f) {
|
|
125
|
+
const v = f.verification;
|
|
126
|
+
const ev = [`${v.testFiles} test files, ${v.storyFiles} stories.`];
|
|
127
|
+
const bits = [v.testScript && "test", v.typecheckScript && "typecheck", v.lintScript && "lint"].filter(Boolean);
|
|
128
|
+
ev.push(bits.length ? `Scripts: ${bits.join(", ")}.` : "No test, typecheck or lint script.");
|
|
129
|
+
if (v.workflows.length) ev.push(`CI: ${v.workflows.join(", ")}.`);
|
|
130
|
+
else ev.push("No CI workflow found, so nothing runs on a pull request.");
|
|
131
|
+
if (v.storyTests) ev.push("Stories are rendered and tested in CI.");
|
|
132
|
+
if (v.axe) ev.push("Accessibility assertions run as part of that.");
|
|
133
|
+
if (v.scoreGate) ev.push("The agent-readiness score itself is gated in CI.");
|
|
134
|
+
const strong = (v.storyTests && v.axe) || (v.testScript && v.docChecks && v.workflows.length);
|
|
135
|
+
return { score: strong ? 5 : v.testScript || v.lintScript || v.workflows.length ? 3 : 1, evidence: ev };
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
"gap-handling"(f) {
|
|
139
|
+
const g = f.gaps;
|
|
140
|
+
const ev = [];
|
|
141
|
+
ev.push(g.file ? `A list of known absences exists: ${g.file}.` : "Nothing lists what the system deliberately does not have.");
|
|
142
|
+
ev.push(g.stopAndAsk ? "The agent instructions tell the agent to stop and ask instead of inventing." : "No stop-and-ask rule, so an unlisted gap gets filled silently.");
|
|
143
|
+
ev.push(g.reportCommand ? `A gap can be reported with \`${g.reportCommand}\`.` : "No way for an agent to report a new gap where the next agent will read it.");
|
|
144
|
+
const score = g.file && g.stopAndAsk && g.reportCommand ? 5 : g.file || g.stopAndAsk ? 3 : 1;
|
|
145
|
+
return { score, evidence: ev };
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** @returns {{ total:number, max:number, dimensions:Array }} */
|
|
150
|
+
export function score(facts, config = {}) {
|
|
151
|
+
const skip = new Set(config.skip || []);
|
|
152
|
+
const dimensions = RUBRIC.dimensions.map((dim) => {
|
|
153
|
+
if (skip.has(dim.id)) {
|
|
154
|
+
return { id: dim.id, title: dim.title, score: null, max: 5, skipped: true, evidence: ["Skipped by configuration."], fixes: [] };
|
|
155
|
+
}
|
|
156
|
+
const result = SCORERS[dim.id](facts);
|
|
157
|
+
return {
|
|
158
|
+
id: dim.id,
|
|
159
|
+
title: dim.title,
|
|
160
|
+
question: dim.question,
|
|
161
|
+
why: dim.why,
|
|
162
|
+
score: result.score,
|
|
163
|
+
max: 5,
|
|
164
|
+
level: dim.levels[String(result.score)],
|
|
165
|
+
next: result.score < 5 ? dim.levels[String(result.score === 1 ? 3 : 5)] : null,
|
|
166
|
+
evidence: result.evidence,
|
|
167
|
+
fixes: result.score < 5 ? dim.fixes : [],
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
const scored = dimensions.filter((d) => !d.skipped);
|
|
171
|
+
return {
|
|
172
|
+
total: scored.reduce((n, d) => n + d.score, 0),
|
|
173
|
+
max: scored.length * 5,
|
|
174
|
+
dimensions,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The machine-readable artifact: committed to the repo so the next run can compare. */
|
|
179
|
+
export function scoreFile(facts, scored) {
|
|
180
|
+
return {
|
|
181
|
+
tool: "adsa",
|
|
182
|
+
rubric: RUBRIC.version,
|
|
183
|
+
generatedAt: new Date().toISOString(),
|
|
184
|
+
target: { name: facts.name, version: facts.version },
|
|
185
|
+
total: scored.total,
|
|
186
|
+
max: scored.max,
|
|
187
|
+
dimensions: Object.fromEntries(scored.dimensions.map((d) => [d.id, d.score])),
|
|
188
|
+
summary: {
|
|
189
|
+
components: facts.coverage.total,
|
|
190
|
+
documented: facts.coverage.documented,
|
|
191
|
+
guides: facts.guides.length,
|
|
192
|
+
codeBlocks: facts.freshness.blocks,
|
|
193
|
+
unknownImports: facts.freshness.unknownCount,
|
|
194
|
+
guidesWithA11y: facts.a11y.withSection,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "adsa-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Score a design system on how well coding agents can use it, then fix what is missing.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"adsa": "bin/adsa.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"lib",
|
|
12
|
+
"rubric",
|
|
13
|
+
"templates",
|
|
14
|
+
"skills"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "node --test test/*.test.mjs",
|
|
21
|
+
"audit:self": "node bin/adsa.mjs audit example/design-system"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"design-system",
|
|
25
|
+
"agents",
|
|
26
|
+
"ai",
|
|
27
|
+
"documentation",
|
|
28
|
+
"audit",
|
|
29
|
+
"mcp"
|
|
30
|
+
],
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/nrmk13/ADSA.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://adsa.space",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/nrmk13/ADSA/issues"
|
|
39
|
+
},
|
|
40
|
+
"author": "Nazar Melnyk"
|
|
41
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"max": 45,
|
|
4
|
+
"scale": [1, 3, 5],
|
|
5
|
+
"dimensions": [
|
|
6
|
+
{
|
|
7
|
+
"id": "agent-instructions",
|
|
8
|
+
"title": "Agent instructions",
|
|
9
|
+
"question": "When an agent opens this repo, does anything tell it how to use the system?",
|
|
10
|
+
"why": "An agent with no instructions falls back on habits from other libraries: shadcn imports, raw Tailwind palette classes, a local components/ui folder.",
|
|
11
|
+
"levels": {
|
|
12
|
+
"1": "No AGENTS.md or CLAUDE.md, or one that never mentions the design system.",
|
|
13
|
+
"3": "A file exists and names the package, but reads like a README: prose, no rules an agent can follow.",
|
|
14
|
+
"5": "Short and imperative: how to look a component up, how to import, which token classes are required, what is forbidden, what to run before finishing."
|
|
15
|
+
},
|
|
16
|
+
"fixes": ["agents-md"]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "machine-surface",
|
|
20
|
+
"title": "Machine surface",
|
|
21
|
+
"question": "Can an agent query the system, or must it read files and guess?",
|
|
22
|
+
"why": "Reading .d.ts files burns context and teaches the agent the type, not the rule. A queryable surface answers the question the agent actually has.",
|
|
23
|
+
"levels": {
|
|
24
|
+
"1": "Nothing. Docs are a website or markdown a human reads.",
|
|
25
|
+
"3": "One machine-readable entry point: llms.txt, a docs CLI, or published skills.",
|
|
26
|
+
"5": "An MCP server or CLI that ships inside the package and answers from the installed version, so answers match the version the project has."
|
|
27
|
+
},
|
|
28
|
+
"fixes": ["mcp-config"]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "docs-coverage",
|
|
32
|
+
"title": "Docs coverage",
|
|
33
|
+
"question": "Does every component a consumer can import have a guide?",
|
|
34
|
+
"why": "A component with no guide is a component the agent will either skip or reinvent, and undocumented exports are where invented APIs come from.",
|
|
35
|
+
"levels": {
|
|
36
|
+
"1": "Under half of the exported components have a guide.",
|
|
37
|
+
"3": "Most have one, and nothing stops a new component from shipping without one.",
|
|
38
|
+
"5": "Effectively all of them, and CI fails when a new export arrives without a guide."
|
|
39
|
+
},
|
|
40
|
+
"fixes": ["coverage-gate"]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "docs-freshness",
|
|
44
|
+
"title": "Docs freshness",
|
|
45
|
+
"question": "If a guide drifts from the code, does anything notice?",
|
|
46
|
+
"why": "Docs written next to the code look fine until something checks them. Guides that can only fail silently always drift.",
|
|
47
|
+
"levels": {
|
|
48
|
+
"1": "Guides are hand-written prose; nothing compares them to the code.",
|
|
49
|
+
"3": "Prop tables or examples are generated, but regenerating is a manual step somebody has to remember.",
|
|
50
|
+
"5": "CI regenerates the generated parts and compiles every code block in the guides against the built package. A guide that lies turns the build red."
|
|
51
|
+
},
|
|
52
|
+
"fixes": ["prop-tables", "examples-check"]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "tokens",
|
|
56
|
+
"title": "Tokens",
|
|
57
|
+
"question": "Are colour, spacing, radius and motion documented as named decisions?",
|
|
58
|
+
"why": "Without a token table the agent hardcodes hex values and raw palette classes, and every screen drifts a little further from the system.",
|
|
59
|
+
"levels": {
|
|
60
|
+
"1": "No token documentation; guides show raw hex or raw palette classes.",
|
|
61
|
+
"3": "Tokens exist and are documented, but examples still mix in raw values.",
|
|
62
|
+
"5": "Semantic tokens documented as tables, raw palette usage forbidden by lint, and motion and spacing covered as well as colour."
|
|
63
|
+
},
|
|
64
|
+
"fixes": ["tokens-doc"]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "patterns",
|
|
68
|
+
"title": "Patterns",
|
|
69
|
+
"question": "Is there anything above component level — how a real page is assembled?",
|
|
70
|
+
"why": "Components tell an agent what exists. Patterns tell it what to build. Without them each agent re-invents page structure from scratch.",
|
|
71
|
+
"levels": {
|
|
72
|
+
"1": "Components only. Nothing describes a page.",
|
|
73
|
+
"3": "A pattern-to-component map: for this task, reach for these parts.",
|
|
74
|
+
"5": "Page-level patterns with skeleton, states and traps, loaded on demand rather than always in context."
|
|
75
|
+
},
|
|
76
|
+
"fixes": ["patterns-doc"]
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "a11y",
|
|
80
|
+
"title": "Accessibility documentation",
|
|
81
|
+
"question": "Do the guides say how a component behaves for assistive technology — keys or gestures, focus, role and name?",
|
|
82
|
+
"why": "An agent cannot infer that behaviour from a prop table alone. On the web it ships a div that looks like a menu and answers to nothing; on mobile it ships a touchable with no accessible name and no role, silent to VoiceOver and TalkBack alike.",
|
|
83
|
+
"levels": {
|
|
84
|
+
"1": "Accessibility is not mentioned in the guides.",
|
|
85
|
+
"3": "Some guides mention it, written by hand and unevenly.",
|
|
86
|
+
"5": "Every guide carries the full interaction contract for its platform — keys or gestures, focus or touch-target behaviour, role and naming requirements — generated from the primitives the component is built on."
|
|
87
|
+
},
|
|
88
|
+
"fixes": ["a11y-docs"]
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": "verification",
|
|
92
|
+
"title": "Verification",
|
|
93
|
+
"question": "Can an agent check its own work before calling it done?",
|
|
94
|
+
"why": "Speed is not the problem. An agent that cannot verify produces confident output nobody can audit.",
|
|
95
|
+
"levels": {
|
|
96
|
+
"1": "Types only, or nothing. A human is the first check.",
|
|
97
|
+
"3": "Unit tests and lint, run by hand.",
|
|
98
|
+
"5": "Every story rendered and checked for accessibility in CI, with a baseline that can only shrink, and one documented command an agent runs before finishing."
|
|
99
|
+
},
|
|
100
|
+
"fixes": ["ci-workflow"]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "gap-handling",
|
|
104
|
+
"title": "Gap handling",
|
|
105
|
+
"question": "What happens when the system genuinely does not have the thing?",
|
|
106
|
+
"why": "An agent cannot tell 'this does not exist' from 'I have not found it yet', so it invents a component and moves on. More documentation cannot fix an absence.",
|
|
107
|
+
"levels": {
|
|
108
|
+
"1": "Nothing. The agent fills the gap silently with the nearest thing it knows.",
|
|
109
|
+
"3": "A written list of known absences and what to use instead.",
|
|
110
|
+
"5": "The list, plus an explicit stop-and-ask rule in the agent instructions, plus a way to report a new gap that lands where the next agent reads."
|
|
111
|
+
},
|
|
112
|
+
"fixes": ["gaps-file"]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-audit
|
|
3
|
+
description: Audit a design system for how well coding agents can use it. Use when someone asks to check whether a design system is agent-ready, why an agent keeps producing wrong UI with their component library, or asks for a design system audit, readiness score or agent-readiness report.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agent-readiness audit
|
|
7
|
+
|
|
8
|
+
You are auditing a design system for one question: **can a coding agent build a
|
|
9
|
+
correct page with it, without a human correcting the result?**
|
|
10
|
+
|
|
11
|
+
The tool does the counting. You do the judgement, the experiment, and the writing.
|
|
12
|
+
|
|
13
|
+
The tool audits React/web, React Native, Swift/SwiftUI/UIKit and Kotlin/Jetpack
|
|
14
|
+
Compose, detected from real evidence in the repo (`package.json` dependencies,
|
|
15
|
+
`Package.swift`/`.xcodeproj`, a Gradle build next to `.kt` files) — check
|
|
16
|
+
`facts.platform` in `adsa audit --json` if you are unsure which one it picked, and
|
|
17
|
+
say which platform you audited when you write it up. A dimension that genuinely
|
|
18
|
+
does not apply on that platform is skipped, not scored down — the "keyboard"
|
|
19
|
+
question becomes "VoiceOver/Dynamic Type" on Swift and "TalkBack/content
|
|
20
|
+
description" on Android rather than failing either for lacking a keyboard.
|
|
21
|
+
|
|
22
|
+
## Phase 0 — Intake
|
|
23
|
+
|
|
24
|
+
Ask these in **one message**, numbered, then wait. Do not ask anything the repo can
|
|
25
|
+
answer — the stack, the framework, the component count and the guide layout are all
|
|
26
|
+
detected. If someone says "just go", take the defaults and say which you took.
|
|
27
|
+
|
|
28
|
+
1. Where is the design system? A local path or a GitHub URL.
|
|
29
|
+
2. Is there a project that already uses it — a real product repo? (This is what
|
|
30
|
+
makes the experiment possible; without it the audit is static only.)
|
|
31
|
+
3. Which coding agent does your team use — Claude Code, Cursor, Copilot, other?
|
|
32
|
+
(Decides where instructions and MCP config get written.)
|
|
33
|
+
4. Is there a Figma library, and is Code Connect set up? A link if you have one.
|
|
34
|
+
5. Anything the system deliberately does not have, that you already know about?
|
|
35
|
+
|
|
36
|
+
> **From Nazar, who built this:** if you work with Figma, use the figma-console
|
|
37
|
+
> bridge rather than a screenshot or a static export. It reads the real component —
|
|
38
|
+
> sizes, paddings, tokens, variants — so parity checks are against node data instead
|
|
39
|
+
> of your impression of the design. Say so if the user has Figma but no bridge: an
|
|
40
|
+
> audit that guesses at design parity is worth less than one that skips it honestly.
|
|
41
|
+
|
|
42
|
+
If there is no Figma library, skip the design-parity discussion entirely rather than
|
|
43
|
+
scoring it badly. A dimension that does not apply is skipped, never failed.
|
|
44
|
+
|
|
45
|
+
## Phase 1 — Run the tool
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx adsa-cli audit <path> # score, report, .adsa/score.json
|
|
49
|
+
npx adsa-cli audit <path> --json # the same, machine-readable
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Read `.adsa/report.md` and the evidence under every dimension. The score is a
|
|
53
|
+
starting point, not the finding.
|
|
54
|
+
|
|
55
|
+
## Phase 2 — Read what the numbers cannot see
|
|
56
|
+
|
|
57
|
+
Open five or six guides yourself, including the biggest and the oldest. Look for:
|
|
58
|
+
|
|
59
|
+
- Rules stated as prose an agent cannot act on ("use sparingly", "be consistent").
|
|
60
|
+
- Examples that would not compile: props that no longer exist, invented compound
|
|
61
|
+
APIs, imports from the wrong subpath.
|
|
62
|
+
- The gap between what the guide says and what the type declares.
|
|
63
|
+
- Six files the agent must read before it can write one line — context it does not have.
|
|
64
|
+
- Anything that assumes the reader will ask a colleague.
|
|
65
|
+
|
|
66
|
+
## Phase 3 — The experiment (only with a consumer project)
|
|
67
|
+
|
|
68
|
+
This is the finding people believe. Everything else is opinion.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx adsa-cli eval init # writes the task
|
|
72
|
+
# hand the task to a fresh agent session, let it build
|
|
73
|
+
npx adsa-cli eval score <project> --system <ds> # measure what came out
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Record: components invented, imports from the system, forbidden packages, raw
|
|
77
|
+
palette classes, time to a working page. Invented components are the headline
|
|
78
|
+
number — an agent cannot tell "this does not exist" from "I have not found it yet",
|
|
79
|
+
so every invention is a gap the system never declared.
|
|
80
|
+
|
|
81
|
+
## Phase 4 — Compare
|
|
82
|
+
|
|
83
|
+
Say where the system stands against systems that have done this work, by trait, not
|
|
84
|
+
by vibe. The ones worth checking, and what each is known for:
|
|
85
|
+
|
|
86
|
+
| System | What to look at |
|
|
87
|
+
| :-- | :-- |
|
|
88
|
+
| [Astryx](https://astryx.atmeta.com) (Meta) | CLI-first: `templates`, `blocks`, docs answered by command, API designed for generated code |
|
|
89
|
+
| [Untitled UI](https://www.untitledui.com) | MCP inside the CLI, llms.txt, docs written for both readers |
|
|
90
|
+
| [Serendie](https://serendie.design) | Skills shipped as a Claude Code plugin, generated from the guides |
|
|
91
|
+
| [SmartHR](https://smarthr.design) | Over a hundred skills, generated rather than hand-written |
|
|
92
|
+
| [Board UI](https://boardui.com) | Docs CLI, machine-readable component manifest |
|
|
93
|
+
| [shadcn/ui](https://ui.shadcn.com) | Registry as a distribution format; components arrive as source |
|
|
94
|
+
| [Radix](https://www.radix-ui.com) / [React Aria](https://react-spectrum.adobe.com/react-aria/) | Behaviour and keyboard contracts documented per primitive |
|
|
95
|
+
| [Material](https://m3.material.io) / [Fluent](https://fluent2.microsoft.design) | Pattern and page-level documentation above component level |
|
|
96
|
+
|
|
97
|
+
Name the two or three traits the audited system is missing, and which of these
|
|
98
|
+
already ship them. Do not rank systems overall; rank traits.
|
|
99
|
+
|
|
100
|
+
## Phase 5 — Write it up
|
|
101
|
+
|
|
102
|
+
The tool has already written `.adsa/report.html`. Extend it, or write your own, but
|
|
103
|
+
keep these:
|
|
104
|
+
|
|
105
|
+
- The score out of 45 with the per-dimension breakdown and the evidence.
|
|
106
|
+
- Findings in severity order, each with the file and line that proves it.
|
|
107
|
+
- The to-do list, cheapest first, each item with its `adsa fix` command.
|
|
108
|
+
- The experiment result if you ran one, including what the agent invented.
|
|
109
|
+
|
|
110
|
+
Then say what to do first, in one sentence, and stop. Do not pad.
|
|
111
|
+
|
|
112
|
+
## Phase 6 — Fix
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx adsa-cli fix --list # what is automatic and what is a brief
|
|
116
|
+
npx adsa-cli fix agents-md # writes files
|
|
117
|
+
npx adsa-cli fix prop-tables # writes a task brief for you to execute
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Briefs are specifications, not suggestions: read the whole brief, follow the traps
|
|
121
|
+
section, and verify the check fails when you break something on purpose. A check
|
|
122
|
+
that never fails is not a check.
|
|
123
|
+
|
|
124
|
+
After the fixes: `npx adsa-cli audit` again. The delta is the deliverable.
|
|
125
|
+
|
|
126
|
+
## Rules
|
|
127
|
+
|
|
128
|
+
- Never invent a finding. Every claim names a file, a line or a command output.
|
|
129
|
+
- Report a dimension you could not assess as not assessed. Do not guess a number.
|
|
130
|
+
- Quote the repository, not your memory of similar repositories.
|
|
131
|
+
- The score belongs to a version. Say which one you audited.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
*From one designer to designers with love <3*
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!-- adsa:start -->
|
|
2
|
+
## Design system: {{name}}{{version}} ({{stack}})
|
|
3
|
+
|
|
4
|
+
This project uses {{name}}. Before writing any UI:
|
|
5
|
+
|
|
6
|
+
- Find the composable first: {{lookup}}. Do not read the compiled AAR and do not
|
|
7
|
+
reach for bare Material defaults or a copied composable from Stack Overflow —
|
|
8
|
+
this system has its own names.
|
|
9
|
+
- Import per composable: `{{importExample}}`. {{importRule}}
|
|
10
|
+
- Colour and typography come from the system's theme. {{paletteExample}} are not
|
|
11
|
+
allowed in product code — use the theme's color roles from `colors.xml`/`Theme.kt`
|
|
12
|
+
or `MaterialTheme.colorScheme`.
|
|
13
|
+
- Icons come only from the system's icon set. No second icon library, no local
|
|
14
|
+
re-implementation of a composable that already exists.
|
|
15
|
+
- Every clickable composable needs a `contentDescription` (via `Modifier.semantics`
|
|
16
|
+
or the component's own label parameter) and, where it is not obvious from the
|
|
17
|
+
content, a `Role`. A minimum touch target of 48dp per Material guidance.
|
|
18
|
+
- **If the composable you need does not exist, stop and ask.** Do not invent a
|
|
19
|
+
wrapper, do not approximate it with a bare `Text`/`Box`, and do not silently pick
|
|
20
|
+
the nearest thing. Known absences and what to use instead are listed in {{gapsFile}}.
|
|
21
|
+
- Before you call the work done: {{finishCheck}}.
|
|
22
|
+
<!-- adsa:end -->
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!-- adsa:start -->
|
|
2
|
+
## Design system: {{name}}{{version}}
|
|
3
|
+
|
|
4
|
+
This project uses {{name}}. Before writing any UI:
|
|
5
|
+
|
|
6
|
+
- Find the component first: {{lookup}}. Do not read `node_modules` types and do not
|
|
7
|
+
reach for habits from shadcn, MUI or Radix — this system has its own names.
|
|
8
|
+
- Import per component: `{{importExample}}`. {{importRule}}
|
|
9
|
+
- Colour, spacing, radius and typography come from the system's tokens. Raw palette
|
|
10
|
+
classes ({{paletteExample}}) and raw hex values are not allowed in product code.
|
|
11
|
+
- Icons come only from the system's icon set. No second icon library, no local
|
|
12
|
+
`components/ui` folder, no copied component source.
|
|
13
|
+
- **If the component you need does not exist, stop and ask.** Do not invent a
|
|
14
|
+
wrapper, do not approximate it with a div, and do not silently pick the nearest
|
|
15
|
+
thing. Known absences and what to use instead are listed in {{gapsFile}}.
|
|
16
|
+
- Before you call the work done: {{finishCheck}}.
|
|
17
|
+
<!-- adsa:end -->
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!-- adsa:start -->
|
|
2
|
+
## Design system: {{name}}{{version}} ({{stack}})
|
|
3
|
+
|
|
4
|
+
This project uses {{name}}. Before writing any UI:
|
|
5
|
+
|
|
6
|
+
- Find the component first: {{lookup}}. Do not read `node_modules` types and do not
|
|
7
|
+
reach for React Native Elements, NativeBase or a copy-pasted community snippet —
|
|
8
|
+
this system has its own names.
|
|
9
|
+
- Import per component: `{{importExample}}`. {{importRule}}
|
|
10
|
+
- Colour, spacing, radius and motion come from the system's tokens. {{paletteExample}}
|
|
11
|
+
are not allowed in product code — use the theme instead.
|
|
12
|
+
- Icons come only from the system's icon set. No second icon library, no local
|
|
13
|
+
`components/ui` folder, no copied component source.
|
|
14
|
+
- Every `Pressable`, `TouchableOpacity` or custom control needs `accessibilityRole`
|
|
15
|
+
and an `accessibilityLabel` (or `accessibilityLabelledBy`). A touchable with
|
|
16
|
+
neither answers to nothing on VoiceOver or TalkBack.
|
|
17
|
+
- **If the component you need does not exist, stop and ask.** Do not invent a
|
|
18
|
+
wrapper, do not approximate it with a bare `View`, and do not silently pick the
|
|
19
|
+
nearest thing. Known absences and what to use instead are listed in {{gapsFile}}.
|
|
20
|
+
- Before you call the work done: {{finishCheck}}.
|
|
21
|
+
<!-- adsa:end -->
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- adsa:start -->
|
|
2
|
+
## Design system: {{name}}{{version}} ({{stack}})
|
|
3
|
+
|
|
4
|
+
This project uses {{name}}. Before writing any UI:
|
|
5
|
+
|
|
6
|
+
- Find the view first: {{lookup}}. Do not read the compiled module's generated
|
|
7
|
+
interface and do not reach for UIKit boilerplate, a different design system, or
|
|
8
|
+
Apple's default control styles — this system has its own names.
|
|
9
|
+
- Import the module: `{{importExample}}`. {{importRule}}
|
|
10
|
+
- Colour and typography come from the system's tokens. {{paletteExample}} are not
|
|
11
|
+
allowed in product code — use the named colors and `ShapeStyle`s the system ships,
|
|
12
|
+
from its asset catalog or token file.
|
|
13
|
+
- Icons come only from the system's asset catalog. No second icon set, no copied
|
|
14
|
+
view source, no local re-implementation of a view that already exists.
|
|
15
|
+
- Every control needs an accessible name and role: `.accessibilityLabel(...)`,
|
|
16
|
+
`.accessibilityAddTraits(...)`, and Dynamic Type support (`.font(.body)` style
|
|
17
|
+
fonts, not fixed point sizes). A view with none of these answers to nothing on
|
|
18
|
+
VoiceOver.
|
|
19
|
+
- **If the view you need does not exist, stop and ask.** Do not invent a wrapper,
|
|
20
|
+
do not approximate it with a raw `Text`/`Button`, and do not silently pick the
|
|
21
|
+
nearest thing. Known absences and what to use instead are listed in {{gapsFile}}.
|
|
22
|
+
- Before you call the work done: {{finishCheck}}.
|
|
23
|
+
<!-- adsa:end -->
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# What this system does not have
|
|
2
|
+
|
|
3
|
+
An agent cannot tell "this does not exist" from "I have not found it yet", so it
|
|
4
|
+
invents a component and moves on. This file is the difference between those two.
|
|
5
|
+
|
|
6
|
+
Keep it short and current. Every row is a decision, not a backlog item.
|
|
7
|
+
|
|
8
|
+
| What is missing | Use instead | Why / status |
|
|
9
|
+
| :-- | :-- | :-- |
|
|
10
|
+
| _example:_ Date range picker | Two `DatePicker` fields with a shared label | Planned, no owner yet |
|
|
11
|
+
| _example:_ Toast / snackbar | The host application's notification layer | Deliberate: lives outside the system |
|
|
12
|
+
|
|
13
|
+
## Reporting a new gap
|
|
14
|
+
|
|
15
|
+
If you hit something that is not in this list and not in the system, add a row
|
|
16
|
+
here in the same change. Do not put it only in a ticket or a chat message: the next
|
|
17
|
+
agent reads this file, not your inbox.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Fix: keyboard and accessibility sections in every guide
|
|
2
|
+
|
|
3
|
+
**Dimension:** Accessibility documentation · **Size:** M · **Repo:** {{name}} · **Stack:** {{stack}}
|
|
4
|
+
|
|
5
|
+
An agent cannot infer keyboard behaviour from a prop table, so it ships a div that
|
|
6
|
+
looks like a menu and answers to nothing. Write the behaviour down — generated, so
|
|
7
|
+
it stays true.
|
|
8
|
+
|
|
9
|
+
## What to build
|
|
10
|
+
|
|
11
|
+
1. Map each component to the primitive it is built on ({{headless}}). If the system
|
|
12
|
+
is built on a headless library, the primitive already defines keys, focus
|
|
13
|
+
behaviour and roles: read that mapping from the component's own inherited
|
|
14
|
+
interfaces rather than guessing per component.
|
|
15
|
+
2. Write a table per primitive: keys and what they do, focus behaviour, the ARIA
|
|
16
|
+
role, and whether an accessible name is required.
|
|
17
|
+
3. Generate a `### Keyboard & accessibility` section into every guide between
|
|
18
|
+
markers. Components that are genuinely not interactive get one honest line
|
|
19
|
+
saying so — silence reads as an omission.
|
|
20
|
+
4. Keep a hand-written `### Notes` subsection that survives regeneration, for the
|
|
21
|
+
things the primitive does not capture.
|
|
22
|
+
5. Where the mapping is uncertain, print a confidence marker instead of inventing
|
|
23
|
+
a key list.
|
|
24
|
+
|
|
25
|
+
## Traps
|
|
26
|
+
|
|
27
|
+
- One primitive interface is often shared by two very different components. Check
|
|
28
|
+
the pairs before trusting the mapping.
|
|
29
|
+
- Components that pick props instead of extending an interface will not be detected
|
|
30
|
+
automatically; keep a small explicit override list.
|
|
31
|
+
|
|
32
|
+
## Done when
|
|
33
|
+
|
|
34
|
+
- Every guide has the section, generated from the map.
|
|
35
|
+
- The generator is idempotent and runs in CI.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Fix: fail CI on an undocumented export
|
|
2
|
+
|
|
3
|
+
**Dimension:** Docs coverage · **Size:** S · **Repo:** {{name}}
|
|
4
|
+
|
|
5
|
+
Coverage that nothing enforces goes down with every release. One gate keeps it.
|
|
6
|
+
|
|
7
|
+
## What to build
|
|
8
|
+
|
|
9
|
+
A check that lists what a consumer can import and asserts each has a guide in
|
|
10
|
+
`{{guidesDir}}`. Package `exports` subpaths are the reliable source of that list;
|
|
11
|
+
fall back to exported component symbols if the package ships one entry point.
|
|
12
|
+
|
|
13
|
+
1. Set aside icon and logo sets — nobody writes a guide per icon — and print how
|
|
14
|
+
many were set aside so the number is not a surprise.
|
|
15
|
+
2. Allow an explicit allowlist file for exports that genuinely need no guide
|
|
16
|
+
(sub-parts documented inside a parent guide), with a reason per entry.
|
|
17
|
+
3. Fail with the list of undocumented exports, not just a count.
|
|
18
|
+
4. Wire it into the same CI job as the other documentation checks.
|
|
19
|
+
|
|
20
|
+
## Done when
|
|
21
|
+
|
|
22
|
+
- A new exported component with no guide turns the build red.
|
|
23
|
+
- The allowlist is short and every entry has a reason.
|