@meyverick/agentic 5.0.2
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/AGENTS.md +234 -0
- package/CHANGELOG.md +236 -0
- package/README.md +50 -0
- package/install.ts +349 -0
- package/package.json +37 -0
- package/scripts/check-deps.mjs +587 -0
- package/scripts/git-dl.mjs +100 -0
- package/skills/check/SKILL.md +108 -0
- package/skills/check/evals/benchmark.json +40 -0
- package/skills/check/evals/evals.json +38 -0
- package/skills/check/references/diagnostic-matrix.md +170 -0
- package/skills/check/references/script-anatomy.md +154 -0
- package/skills/create-skill/SKILL.md +291 -0
- package/skills/create-skill/assets/templates/SKILL.md.template +118 -0
- package/skills/create-skill/assets/templates/evals.json.template +36 -0
- package/skills/create-skill/assets/templates/grading.json.template +26 -0
- package/skills/create-skill/evals/benchmark.json +41 -0
- package/skills/create-skill/evals/evals.json +50 -0
- package/skills/create-skill/evals/grading-template.json +36 -0
- package/skills/create-skill/evals/near-misses.json +35 -0
- package/skills/create-skill/evals/trigger-queries.json +80 -0
- package/skills/create-skill/references/antipatterns.md +123 -0
- package/skills/create-skill/references/component-decomposition.md +130 -0
- package/skills/create-skill/references/content-quality-criteria.md +61 -0
- package/skills/create-skill/references/description-optimization.md +90 -0
- package/skills/create-skill/references/eval-methodology.md +100 -0
- package/skills/create-skill/references/fragility-matching.md +88 -0
- package/skills/create-skill/references/gotchas-patterns.md +80 -0
- package/skills/create-skill/references/specification.md +77 -0
- package/skills/create-skill/scripts/audit-antipatterns.mjs +164 -0
- package/skills/create-skill/scripts/compute-benchmark.mjs +111 -0
- package/skills/create-skill/scripts/run-cold-eval.mjs +118 -0
- package/skills/create-skill/scripts/scaffold-skill.mjs +86 -0
- package/skills/create-skill/scripts/validate-routing.mjs +137 -0
- package/skills/create-skill/scripts/validate-structure.mjs +223 -0
- package/skills/design-craft/SKILL.md +134 -0
- package/skills/design-craft/evals/benchmark.json +41 -0
- package/skills/design-craft/evals/evals.json +81 -0
- package/skills/design-craft/references/anti-slop-patterns.md +49 -0
- package/skills/design-craft/references/art-direction.md +89 -0
- package/skills/design-craft/references/design-engineering.md +122 -0
- package/skills/design-craft/references/motion-craft.md +124 -0
- package/skills/design-craft/references/process.md +47 -0
- package/skills/design-craft/references/review-checklist.md +121 -0
- package/skills/guardrails/SKILL.md +118 -0
- package/skills/guardrails/evals/benchmark.json +40 -0
- package/skills/guardrails/evals/evals.json +49 -0
- package/skills/guardrails/references/guardrails-patterns.md +43 -0
- package/skills/okf-docs/SKILL.md +79 -0
- package/skills/okf-docs/evals/benchmark.json +21 -0
- package/skills/okf-docs/evals/evals.json +37 -0
- package/skills/okf-docs/references/okf-spec.md +56 -0
- package/skills/okf-docs/scripts/validate-frontmatter.mjs +130 -0
- package/skills/openspec-harden/SKILL.md +138 -0
- package/skills/openspec-harden/evals/benchmark.json +40 -0
- package/skills/openspec-harden/evals/evals.json +38 -0
- package/skills/openspec-learn/SKILL.md +216 -0
- package/skills/openspec-learn/evals/benchmark.json +44 -0
- package/skills/openspec-learn/evals/evals.json +48 -0
- package/skills/openspec-learn/evals/retrieval-bench.json +27 -0
- package/skills/openspec-learn/references/conflict-handling.md +20 -0
- package/skills/openspec-learn/references/evaluation-methodology.md +126 -0
- package/skills/openspec-learn/references/examples.md +37 -0
- package/skills/openspec-learn/references/improvement-patterns.md +155 -0
- package/skills/openspec-learn/references/report-analysis.md +104 -0
- package/skills/openspec-learn/references/skill-quality.md +103 -0
- package/skills/openspec-learn/references/tool-type-detection.md +30 -0
- package/skills/openspec-report/SKILL.md +104 -0
- package/skills/openspec-report/assets/templates/assessment.md.template +84 -0
- package/skills/openspec-report/assets/templates/report.md.template +92 -0
- package/skills/openspec-report/evals/benchmark.json +44 -0
- package/skills/openspec-report/evals/evals.json +46 -0
- package/skills/qmd-research/SKILL.md +89 -0
- package/skills/qmd-research/evals/benchmark.json +40 -0
- package/skills/qmd-research/evals/evals.json +38 -0
- package/skills/qmd-research/references/index-management.md +69 -0
- package/skills/qmd-research/references/query-craft.md +82 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-routing.mjs — Semantic routing validation for skills
|
|
4
|
+
* Usage: node validate-routing.mjs <skill-dir>
|
|
5
|
+
* Output: unified JSON envelope {target, pass, checks:[{id,status,detail}], summary}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
const skillDir = process.argv[2];
|
|
12
|
+
|
|
13
|
+
if (!skillDir) {
|
|
14
|
+
console.error(JSON.stringify({
|
|
15
|
+
error: 'Usage: node validate-routing.mjs <skill-dir>'
|
|
16
|
+
}));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
21
|
+
|
|
22
|
+
if (!existsSync(skillFile)) {
|
|
23
|
+
console.log(JSON.stringify({
|
|
24
|
+
target: skillDir,
|
|
25
|
+
pass: false,
|
|
26
|
+
checks: [{ id: 'routing.skill-file', status: 'FAIL', detail: 'SKILL.md not found' }],
|
|
27
|
+
summary: { total: 1, pass: 0, fail: 1, warn: 0, skip: 0 }
|
|
28
|
+
}));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const skillMd = readFileSync(skillFile, 'utf-8');
|
|
33
|
+
const checks = [];
|
|
34
|
+
|
|
35
|
+
function add(id, passed, detail) {
|
|
36
|
+
checks.push({ id, status: passed ? 'PASS' : 'FAIL', detail });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Extract frontmatter
|
|
40
|
+
const frontmatterMatch = skillMd.match(/^---\n([\s\S]*?)\n---/);
|
|
41
|
+
const frontmatter = frontmatterMatch ? frontmatterMatch[1] : '';
|
|
42
|
+
|
|
43
|
+
// Extract body (everything after frontmatter)
|
|
44
|
+
const bodyStart = skillMd.indexOf('---', 3);
|
|
45
|
+
const body = bodyStart !== -1 ? skillMd.slice(bodyStart + 3) : skillMd;
|
|
46
|
+
|
|
47
|
+
// Extract description
|
|
48
|
+
const descMatch = frontmatter.match(/^description:\s*([\s\S]*?)(?=\n\w+:|\n---)/m);
|
|
49
|
+
const description = descMatch ? descMatch[1].replace(/^>\s*\n?/, '').trim() : '';
|
|
50
|
+
|
|
51
|
+
// CHECK 1: positive_triggers coverage (min 3 entries)
|
|
52
|
+
const ptMatch = frontmatter.match(/^positive_triggers:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
53
|
+
let ptCount = 0;
|
|
54
|
+
if (ptMatch) {
|
|
55
|
+
const triggers = ptMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
56
|
+
ptCount = triggers.length;
|
|
57
|
+
}
|
|
58
|
+
add('routing.positive-triggers', ptCount >= 3,
|
|
59
|
+
`${ptCount} entries found (minimum 3 required) — improves semantic routing accuracy`);
|
|
60
|
+
|
|
61
|
+
// CHECK 2: anti_triggers coverage (min 2 entries)
|
|
62
|
+
const atMatch = frontmatter.match(/^anti_triggers:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
63
|
+
let atCount = 0;
|
|
64
|
+
if (atMatch) {
|
|
65
|
+
const triggers = atMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
66
|
+
atCount = triggers.length;
|
|
67
|
+
}
|
|
68
|
+
add('routing.anti-triggers', atCount >= 2,
|
|
69
|
+
`${atCount} entries found (minimum 2 required) — boosts routing precision by 31.8%`);
|
|
70
|
+
|
|
71
|
+
// CHECK 3: Description contains "Use when" phrasing
|
|
72
|
+
const hasUseWhen = /use when/i.test(description);
|
|
73
|
+
add('routing.use-when', hasUseWhen,
|
|
74
|
+
hasUseWhen ? 'Found "Use when" phrasing' : 'Missing "Use when" phrasing in description — imperative phrasing helps agents decide activation');
|
|
75
|
+
|
|
76
|
+
// CHECK 4: Description contains "Do NOT use when" phrasing
|
|
77
|
+
const hasNotUse = /do not use when|don't use when/i.test(description);
|
|
78
|
+
add('routing.negative-scope', hasNotUse,
|
|
79
|
+
hasNotUse ? 'Found "Do NOT use when" phrasing' : 'Missing negative scope in description — prevents over-firing on similar-domain queries');
|
|
80
|
+
|
|
81
|
+
// CHECK 5: Description-body alignment (keywords in description appear in body)
|
|
82
|
+
let alignmentScore = 0;
|
|
83
|
+
let alignmentTotal = 0;
|
|
84
|
+
if (description) {
|
|
85
|
+
const stopWords = new Set(['the', 'and', 'for', 'with', 'this', 'that', 'when', 'not', 'use', 'from', 'are', 'was', 'have', 'has', 'will', 'can', 'should', 'does', 'its']);
|
|
86
|
+
const words = description.toLowerCase()
|
|
87
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
88
|
+
.split(/\s+/)
|
|
89
|
+
.filter(w => w.length > 3 && !stopWords.has(w));
|
|
90
|
+
|
|
91
|
+
const uniqueWords = [...new Set(words)];
|
|
92
|
+
alignmentTotal = Math.min(uniqueWords.length, 10);
|
|
93
|
+
|
|
94
|
+
for (const word of uniqueWords.slice(0, 10)) {
|
|
95
|
+
if (body.toLowerCase().includes(word)) {
|
|
96
|
+
alignmentScore++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const alignmentRatio = alignmentTotal > 0 ? alignmentScore / alignmentTotal : 0;
|
|
102
|
+
add('routing.alignment', alignmentRatio >= 0.5,
|
|
103
|
+
`${alignmentScore}/${alignmentTotal} description keywords found in body (${Math.round(alignmentRatio * 100)}% alignment) — frontmatter-only indexing loses 29-44% recall`);
|
|
104
|
+
|
|
105
|
+
// CHECK 6: Single-responsibility verification
|
|
106
|
+
let singleResponsibility = true;
|
|
107
|
+
let srDetail = 'Single atomic intent detected';
|
|
108
|
+
if (description) {
|
|
109
|
+
const compoundMarkers = /\b(and also\b|\badditionally\b|\bas well as\b)/i;
|
|
110
|
+
if (compoundMarkers.test(description)) {
|
|
111
|
+
singleResponsibility = false;
|
|
112
|
+
srDetail = 'Compound intent detected. Description contains multiple operations joined by "and also" or "additionally".';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const actionVerbs = description.match(/\b(?:create|delete|update|modify|analyze|generate|process|manage|handle|configure|deploy|monitor)\b/gi);
|
|
116
|
+
if (actionVerbs && new Set(actionVerbs.map(v => v.toLowerCase())).size > 2) {
|
|
117
|
+
singleResponsibility = false;
|
|
118
|
+
srDetail = `Multiple distinct action verbs detected (${[...new Set(actionVerbs.map(v => v.toLowerCase()))].join(', ')}). Consider splitting into separate skills.`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
add('routing.atomic-intent', singleResponsibility,
|
|
122
|
+
`${srDetail} — multi-domain descriptions cause trigger dilution`);
|
|
123
|
+
|
|
124
|
+
// Unified envelope
|
|
125
|
+
const passCount = checks.filter(c => c.status === 'PASS').length;
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
target: skillDir,
|
|
128
|
+
pass: passCount === checks.length,
|
|
129
|
+
checks,
|
|
130
|
+
summary: {
|
|
131
|
+
total: checks.length,
|
|
132
|
+
pass: passCount,
|
|
133
|
+
fail: checks.filter(c => c.status === 'FAIL').length,
|
|
134
|
+
warn: checks.filter(c => c.status === 'WARN').length,
|
|
135
|
+
skip: checks.filter(c => c.status === 'SKIP').length
|
|
136
|
+
}
|
|
137
|
+
}));
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* validate-structure.mjs — Check skill directory structure and SKILL.md compliance
|
|
4
|
+
* Usage: node validate-structure.mjs <skill-dir>
|
|
5
|
+
* Output: unified JSON envelope {target, pass, checks:[{id,status,detail}], summary}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
9
|
+
import { join, basename } from 'path';
|
|
10
|
+
|
|
11
|
+
const skillDir = process.argv[2];
|
|
12
|
+
|
|
13
|
+
if (!skillDir) {
|
|
14
|
+
console.error(JSON.stringify({
|
|
15
|
+
error: 'Usage: node validate-structure.mjs <skill-dir>'
|
|
16
|
+
}));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const checks = [];
|
|
21
|
+
|
|
22
|
+
function add(id, severity, detail) {
|
|
23
|
+
checks.push({ id, status: severity, detail });
|
|
24
|
+
}
|
|
25
|
+
const fail = (id, detail) => add(id, 'FAIL', detail);
|
|
26
|
+
const warn = (id, detail) => add(id, 'WARN', detail);
|
|
27
|
+
const pass = (id, detail) => add(id, 'PASS', detail);
|
|
28
|
+
|
|
29
|
+
// Check SKILL.md exists
|
|
30
|
+
if (!existsSync(join(skillDir, 'SKILL.md'))) {
|
|
31
|
+
console.log(JSON.stringify({
|
|
32
|
+
target: skillDir,
|
|
33
|
+
pass: false,
|
|
34
|
+
checks: [{ id: 'structure.skill-file', status: 'FAIL', detail: 'SKILL.md not found' }],
|
|
35
|
+
summary: { total: 1, pass: 0, fail: 1, warn: 0, skip: 0 }
|
|
36
|
+
}));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Read SKILL.md
|
|
41
|
+
const skillMd = readFileSync(join(skillDir, 'SKILL.md'), 'utf-8');
|
|
42
|
+
|
|
43
|
+
// Extract frontmatter
|
|
44
|
+
const frontmatterMatch = skillMd.match(/^---\n([\s\S]*?)\n---/);
|
|
45
|
+
let frontmatter = '';
|
|
46
|
+
|
|
47
|
+
if (frontmatterMatch) {
|
|
48
|
+
frontmatter = frontmatterMatch[1];
|
|
49
|
+
} else {
|
|
50
|
+
fail('structure.frontmatter', 'No frontmatter found in SKILL.md');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check name field
|
|
54
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
55
|
+
const name = nameMatch ? nameMatch[1].replace(/"/g, '').trim() : '';
|
|
56
|
+
|
|
57
|
+
if (!name) {
|
|
58
|
+
fail('structure.name', "Missing required 'name' field in frontmatter");
|
|
59
|
+
} else if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name)) {
|
|
60
|
+
fail('structure.name', `Invalid name format: '${name}'. Must be lowercase letters, numbers, hyphens only. No leading/trailing hyphens.`);
|
|
61
|
+
} else if (name.length > 64) {
|
|
62
|
+
fail('structure.name', `Name too long: ${name.length} chars. Maximum 64 characters.`);
|
|
63
|
+
} else if (name.includes('--')) {
|
|
64
|
+
fail('structure.name', `Name contains consecutive hyphens: '${name}'`);
|
|
65
|
+
} else {
|
|
66
|
+
pass('structure.name', `Valid name: '${name}'`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check name matches directory
|
|
70
|
+
const dirName = basename(skillDir);
|
|
71
|
+
if (name && name !== dirName) {
|
|
72
|
+
warn('structure.name-dir-match', `Name '${name}' does not match directory name '${dirName}'. Pi allows this, but the Agent Skills standard requires matching.`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check description field (handles both single-line and multi-line YAML)
|
|
76
|
+
let desc = '';
|
|
77
|
+
let descStarted = false;
|
|
78
|
+
for (const fmLine of frontmatter.split('\n')) {
|
|
79
|
+
if (fmLine.startsWith('description:')) {
|
|
80
|
+
descStarted = true;
|
|
81
|
+
const inline = fmLine.replace(/^description:\s*>?-?\s*/, '');
|
|
82
|
+
if (inline.trim()) desc = inline.trim();
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (descStarted) {
|
|
86
|
+
if (/^[a-zA-Z_-]+\s*:/.test(fmLine) || fmLine.startsWith('---')) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
const trimmed = fmLine.trim();
|
|
90
|
+
if (trimmed) {
|
|
91
|
+
desc = desc ? desc + ' ' + trimmed : trimmed;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (!desc) {
|
|
97
|
+
fail('structure.description', "Missing required 'description' field in frontmatter");
|
|
98
|
+
} else if (desc.length > 1024) {
|
|
99
|
+
fail('structure.description', `Description too long: ${desc.length} chars. Maximum 1024 characters.`);
|
|
100
|
+
} else {
|
|
101
|
+
pass('structure.description', `${desc.length} chars, within limit`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Description phrasing warnings
|
|
105
|
+
if (desc && !/use when/i.test(desc)) {
|
|
106
|
+
warn('structure.use-when', "Description should contain 'Use when' phrasing for imperative intent");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (desc && !/do not use when|don't use when/i.test(desc)) {
|
|
110
|
+
warn('structure.negative-scope', "Description should contain 'Do NOT use when' phrasing for negative scope");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (desc) {
|
|
114
|
+
const compoundMarkers = /\b(and also\b|\badditionally\b|\bas well as\b)/i;
|
|
115
|
+
if (compoundMarkers.test(desc)) {
|
|
116
|
+
warn('structure.compound-intent', "Description may contain compound intent (multiple operations). Consider splitting into separate skills.");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// positive_triggers array (min 3 entries)
|
|
121
|
+
const ptMatch = frontmatter.match(/^positive_triggers:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
122
|
+
if (ptMatch) {
|
|
123
|
+
const triggers = ptMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
124
|
+
if (triggers.length < 3) {
|
|
125
|
+
warn('structure.positive-triggers', `positive_triggers has ${triggers.length} entries. Minimum 3 recommended for routing accuracy.`);
|
|
126
|
+
} else {
|
|
127
|
+
pass('structure.positive-triggers', `${triggers.length} entries`);
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
warn('structure.positive-triggers', "Missing 'positive_triggers' array in frontmatter. Recommended minimum 3 entries for semantic routing.");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// anti_triggers array (min 2 entries)
|
|
134
|
+
const atMatch = frontmatter.match(/^anti_triggers:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
135
|
+
if (atMatch) {
|
|
136
|
+
const triggers = atMatch[1].split('\n').filter(l => l.trim().startsWith('-'));
|
|
137
|
+
if (triggers.length < 2) {
|
|
138
|
+
warn('structure.anti-triggers', `anti_triggers has ${triggers.length} entries. Minimum 2 recommended for precision (+31.8%).`);
|
|
139
|
+
} else {
|
|
140
|
+
pass('structure.anti-triggers', `${triggers.length} entries`);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
warn('structure.anti-triggers', "Missing 'anti_triggers' array in frontmatter. Anti-triggers boost routing precision by 31.8%.");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Runtime contract when scripts exist
|
|
147
|
+
const scriptsDir = join(skillDir, 'scripts');
|
|
148
|
+
if (existsSync(scriptsDir)) {
|
|
149
|
+
const scriptFiles = readdirSync(scriptsDir).filter(f => f.endsWith('.mjs') || f.endsWith('.js') || f.endsWith('.py') || f.endsWith('.sh'));
|
|
150
|
+
if (scriptFiles.length > 0) {
|
|
151
|
+
if (!frontmatter.includes('runtime:') && !frontmatter.includes('timeout_seconds')) {
|
|
152
|
+
warn('structure.runtime-contract', 'Scripts exist but no runtime contract declared in frontmatter (runtime:, timeout_seconds:)');
|
|
153
|
+
} else {
|
|
154
|
+
pass('structure.runtime-contract', 'Runtime contract declared for bundled scripts');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Hardcoded absolute paths in script files
|
|
160
|
+
if (existsSync(scriptsDir)) {
|
|
161
|
+
const scriptFiles = readdirSync(scriptsDir).filter(f => f.endsWith('.mjs') || f.endsWith('.js') || f.endsWith('.py') || f.endsWith('.sh'));
|
|
162
|
+
for (const file of scriptFiles) {
|
|
163
|
+
const content = readFileSync(join(scriptsDir, file), 'utf-8');
|
|
164
|
+
const absPathPatterns = [
|
|
165
|
+
/['"]\/home\//,
|
|
166
|
+
/['"]\/root\//,
|
|
167
|
+
/['"]\/usr\/(?!local\/bin)(?!share)/,
|
|
168
|
+
/['"]C:\\\\/
|
|
169
|
+
];
|
|
170
|
+
for (const pattern of absPathPatterns) {
|
|
171
|
+
if (pattern.test(content)) {
|
|
172
|
+
fail(`structure.portability.${file}`, `Hardcoded absolute path detected in scripts/${file}. Use relative paths resolved via import.meta.url.`);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (/\.pi\/skills\/|\.agents\/skills\//.test(content)) {
|
|
177
|
+
fail(`structure.portability.${file}`, `Harness-specific directory reference detected in scripts/${file}. Scripts must be portable across harnesses.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (!checks.some(c => c.id.startsWith('structure.portability.') && c.status === 'FAIL') && scriptFiles.length > 0) {
|
|
181
|
+
pass('structure.portability', 'No hardcoded/harness-specific paths in scripts');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Directory structure conventions
|
|
186
|
+
for (const dir of ['scripts', 'references', 'assets']) {
|
|
187
|
+
if (!existsSync(join(skillDir, dir))) {
|
|
188
|
+
warn(`structure.dir-${dir}`, `Missing ${dir}/ directory`);
|
|
189
|
+
} else {
|
|
190
|
+
pass(`structure.dir-${dir}`, `${dir}/ present`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// File references in SKILL.md resolve
|
|
195
|
+
const lines = skillMd.split('\n');
|
|
196
|
+
for (const line of lines) {
|
|
197
|
+
const refMatch = line.match(/\]\(([^)]+)\)/);
|
|
198
|
+
if (refMatch) {
|
|
199
|
+
const ref = refMatch[1];
|
|
200
|
+
if (ref && !ref.startsWith('http') && !ref.startsWith('#') && !ref.startsWith('mailto:')) {
|
|
201
|
+
if (!existsSync(join(skillDir, ref))) {
|
|
202
|
+
warn(`structure.reference`, `File reference not found: ${ref}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Unified envelope
|
|
209
|
+
const fails = checks.filter(c => c.status === 'FAIL').length;
|
|
210
|
+
console.log(JSON.stringify({
|
|
211
|
+
target: skillDir,
|
|
212
|
+
pass: fails === 0,
|
|
213
|
+
name,
|
|
214
|
+
description_length: desc.length,
|
|
215
|
+
checks,
|
|
216
|
+
summary: {
|
|
217
|
+
total: checks.length,
|
|
218
|
+
pass: checks.filter(c => c.status === 'PASS').length,
|
|
219
|
+
fail: fails,
|
|
220
|
+
warn: checks.filter(c => c.status === 'WARN').length,
|
|
221
|
+
skip: checks.filter(c => c.status === 'SKIP').length
|
|
222
|
+
}
|
|
223
|
+
}));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-craft
|
|
3
|
+
description: >
|
|
4
|
+
Craft intentional interface design on any stack: set a direction, then build, review,
|
|
5
|
+
polish, or animate. Use when starting UI work, when a design looks templated or bland,
|
|
6
|
+
when reviewing a page or component for UX, accessibility, motion, or performance
|
|
7
|
+
quality, or when asked to make something feel more premium or calmer. Do NOT use when
|
|
8
|
+
the task is backend, API, database, or infrastructure work; when writing pure
|
|
9
|
+
documentation; or when choosing between rendering technologies, which is a stack
|
|
10
|
+
decision owned by the project.
|
|
11
|
+
allowed-tools: Bash(*)
|
|
12
|
+
license: MIT
|
|
13
|
+
compatibility: No runtime or network requirement; references ship with the skill.
|
|
14
|
+
metadata:
|
|
15
|
+
author: agentic
|
|
16
|
+
version: "1.0.0"
|
|
17
|
+
positive_triggers:
|
|
18
|
+
- "design or redesign a page, screen, or component"
|
|
19
|
+
- "make this interface look less templated"
|
|
20
|
+
- "review my UI for UX, accessibility, or motion quality"
|
|
21
|
+
- "polish, clarify, or animate this interface"
|
|
22
|
+
anti_triggers:
|
|
23
|
+
- "backend, API, database, or infrastructure work"
|
|
24
|
+
- "choose between rendering technologies"
|
|
25
|
+
- "write documentation, a README, or non-visual copy"
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Design Craft
|
|
29
|
+
|
|
30
|
+
A syllabus for interface work on any stack. Read the outline, then open only the reference a module points to.
|
|
31
|
+
|
|
32
|
+
## 1. Activation Boundary
|
|
33
|
+
|
|
34
|
+
**Triggers on:** designing or redesigning a page, screen, component, or flow; a design that reads as templated, bland, or loud; reviewing an interface for UX, accessibility, motion, or performance; polishing, clarifying, or animating an existing surface.
|
|
35
|
+
|
|
36
|
+
**Does not trigger on:** backend, API, database, or infrastructure work; pure documentation; choosing a renderer or framework (delegate to the project's own rules); non-visual performance work.
|
|
37
|
+
|
|
38
|
+
## 2. Design Read — the gate
|
|
39
|
+
|
|
40
|
+
Before the first UI edit, emit one line:
|
|
41
|
+
|
|
42
|
+
`Design Read: <mode> · <subject> · <audience> · <job> · dials v/m/d = x/y/z`
|
|
43
|
+
|
|
44
|
+
- **Build** — new surface or a new visual direction; **Review** — findings only; **Polish** — same identity, better craft; **Motion** — decide and build motion.
|
|
45
|
+
- The Design Read is a commitment: if later work contradicts it, say so and revise it explicitly rather than drifting.
|
|
46
|
+
|
|
47
|
+
## 3. The Brief Wins
|
|
48
|
+
|
|
49
|
+
- An explicit brief — aesthetic, era, material, palette, typeface, brand, or a supplied reference — outranks generic taste and every anti-slop rule below. Follow it exactly.
|
|
50
|
+
- **Refine preserves, redesign replaces.** Refinement keeps identity, behaviour, copy, and everything outside scope. Redesign keeps product truth and function, treats the old look as anti-reference, and replaces it wholesale — never polish a discarded direction.
|
|
51
|
+
- When the brief is silent on an axis, decide and state why. Never spend that freedom on a default.
|
|
52
|
+
- Ask exactly one question when product, audience, or job is unknown. Otherwise, decide.
|
|
53
|
+
|
|
54
|
+
## 4. Three Dials
|
|
55
|
+
|
|
56
|
+
Set three 1–10 values in the Design Read; they bound every later choice:
|
|
57
|
+
|
|
58
|
+
| Dial | 1–3 | 4–7 | 8–10 |
|
|
59
|
+
| --- | --- | --- | --- |
|
|
60
|
+
| variance | centred, minimal | balanced, modern | asymmetric, expressive |
|
|
61
|
+
| motion | micro-feedback only | standard reveals | choreographed sequences |
|
|
62
|
+
| density | airy (24–96px rhythm) | standard (16–64px) | dense (8–32px) |
|
|
63
|
+
|
|
64
|
+
Presets: operate/dashboard 2·1·8 · marketing/persuade 7·5·3 · read/docs 3·2·5 · experience/portfolio 9·8·2. Presets are starting points, not verdicts.
|
|
65
|
+
|
|
66
|
+
## 5. Non-negotiables — every mode
|
|
67
|
+
|
|
68
|
+
- Visible keyboard focus; never remove an outline without an equal replacement.
|
|
69
|
+
- Contrast: body text meets AA; hero and primary CTA target AAA.
|
|
70
|
+
- Semantic structure first (landmarks, ordered headings, real labels); ARIA only to fill a real gap.
|
|
71
|
+
- `prefers-reduced-motion` is honoured by rendering final states, not by merely shortening.
|
|
72
|
+
- Motion stays on compositor-friendly properties; layout-affecting animation is a defect.
|
|
73
|
+
- Theme parity: whatever modes the project ships, hierarchy and brand read the same in each.
|
|
74
|
+
- Asset honesty: references give traits — never reuse, trace, or closely reproduce assets, identity, or copy. No invented proof, logos, or testimonials.
|
|
75
|
+
- Honest claims: "premium", "cinematic", or "award-caliber" describe a bar, never a recognition.
|
|
76
|
+
- Offline-complete: no network call is required for the work to finish.
|
|
77
|
+
|
|
78
|
+
## 6. Anti-slop — contrast and anti-examples
|
|
79
|
+
|
|
80
|
+
| Old habit (default) | Better habit (choice) |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| viewport-height hero | content-height hero with the CTA inside the first viewport |
|
|
83
|
+
| every section fades in | one orchestrated entrance; the rest hold still |
|
|
84
|
+
| one card shape repeated | composition varies with content weight |
|
|
85
|
+
| animation everywhere | motion only where it explains a change |
|
|
86
|
+
| accent colour on one headline word | one deliberate type treatment for the whole page |
|
|
87
|
+
|
|
88
|
+
Anti-examples:
|
|
89
|
+
|
|
90
|
+
- `Do NOT: enter from scale(0)` → `Do: fade and translate from the element's real origin`
|
|
91
|
+
- `Do NOT: strip focus outlines for looks` → `Do: a visible focus indicator on every interactive element`
|
|
92
|
+
- `Do NOT: invent a logo wall or testimonials` → `Do: omit proof that cannot be sourced`
|
|
93
|
+
- `Do NOT: a generic "Something went wrong"` → `Do: name what failed and the next step`
|
|
94
|
+
- `Do NOT: an uppercase tracked eyebrow above every heading` → `Do: at most one small label per three sections, or none`
|
|
95
|
+
|
|
96
|
+
The full named list with fixes is in `references/anti-slop-patterns.md`.
|
|
97
|
+
|
|
98
|
+
## 7. Review contract
|
|
99
|
+
|
|
100
|
+
Report findings only, ordered by severity, one line each:
|
|
101
|
+
|
|
102
|
+
`<severity> · <file>:<line> · <rule violated> · <fix>`
|
|
103
|
+
|
|
104
|
+
- **blocker** — broken, inaccessible, or dishonest; **major** — hierarchy, contrast, motion, or performance; **minor** — polish.
|
|
105
|
+
- No prose essays, no restating the page, no praise padding: evidence, then fix.
|
|
106
|
+
- Do not modify the target unless the user asks for fixes.
|
|
107
|
+
|
|
108
|
+
## 8. Verification — bounded
|
|
109
|
+
|
|
110
|
+
Build fully → inspect once in one batch (desktop and mobile together on web; the shipped device classes elsewhere) → fix that batch in one pass → confirm with at most one more round → stop.
|
|
111
|
+
|
|
112
|
+
- Never open a third round; never polish without a finding.
|
|
113
|
+
- Check keyboard, focus, reduced motion, and every shipped theme before declaring done.
|
|
114
|
+
- Report the Design Read, the dials, the findings fixed, and anything left open.
|
|
115
|
+
|
|
116
|
+
## References
|
|
117
|
+
|
|
118
|
+
Open only what the current module needs:
|
|
119
|
+
|
|
120
|
+
- `references/art-direction.md` — brief reading, subject-matter grounding, surface intent, hero composition, asset honesty.
|
|
121
|
+
- `references/design-engineering.md` — type, colour, layout, materiality, states, forms and data, density, theme protocol.
|
|
122
|
+
- `references/motion-craft.md` — whether to animate, easing, duration, springs, transform and clip-path technique, gestures, performance, debugging.
|
|
123
|
+
- `references/anti-slop-patterns.md` — the named patterns and their fixes.
|
|
124
|
+
- `references/review-checklist.md` — review method plus the vendored Web Interface Guidelines digest (dated; live refresh optional).
|
|
125
|
+
- `references/process.md` — detection first, design-system decision, token persistence, bounded verification, handoff.
|
|
126
|
+
|
|
127
|
+
**Optional neighbours:** renderer and stack choice belong to the project — if `AGENTS.md` exists, its stack matrix wins. For secrets, HTML sinks, and auth surfaces, load `guardrails` when installed.
|
|
128
|
+
|
|
129
|
+
## Gotchas
|
|
130
|
+
|
|
131
|
+
- The Design Read precedes code. A redesign that keeps the old look has failed.
|
|
132
|
+
- Detect before import: check the manifest, then propose a dependency explicitly — never assume it exists.
|
|
133
|
+
- Read references on demand; loading all six wastes the budget they exist to protect.
|
|
134
|
+
- Dials are commitments: changing one mid-build means re-deciding, not nudging.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill": "design-craft",
|
|
3
|
+
"generated": {
|
|
4
|
+
"by": "process:structural-stage/1.0",
|
|
5
|
+
"at": "2026-09-21T00:00:00Z"
|
|
6
|
+
},
|
|
7
|
+
"stage": "behavioral",
|
|
8
|
+
"structural": {
|
|
9
|
+
"validate_structure": {
|
|
10
|
+
"pass": true,
|
|
11
|
+
"note": "recorded at apply gate (task 5.1)"
|
|
12
|
+
},
|
|
13
|
+
"validate_routing": {
|
|
14
|
+
"checks_total": 6,
|
|
15
|
+
"positive_triggers": 4,
|
|
16
|
+
"anti_triggers": 3,
|
|
17
|
+
"single_responsibility": true
|
|
18
|
+
},
|
|
19
|
+
"evals": {
|
|
20
|
+
"count": 7,
|
|
21
|
+
"assertions": 20,
|
|
22
|
+
"anti_trigger_coverage": true,
|
|
23
|
+
"agnosticism_coverage": true
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"behavioral_dxm": "1×0.35",
|
|
27
|
+
"ship_gate": {
|
|
28
|
+
"criterion": "d = +1 and m >= 0.2",
|
|
29
|
+
"applies_to": "behavioral stage"
|
|
30
|
+
},
|
|
31
|
+
"behavioral": {
|
|
32
|
+
"at": "2026-09-21T06:37:39.843Z",
|
|
33
|
+
"evals": 7,
|
|
34
|
+
"assertions": 20,
|
|
35
|
+
"baseline": 0.5,
|
|
36
|
+
"with_skill": 0.85,
|
|
37
|
+
"d": 1,
|
|
38
|
+
"m": 0.35,
|
|
39
|
+
"ship": "pass"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "design-craft",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "Build me a landing page for our new scheduling tool. It looks generic right now and I want it to feel designed.",
|
|
7
|
+
"expected_output": "Agent activates design-craft, emits a Design Read before the first UI edit (mode Build, subject, audience, job, dials), grounds the direction in the subject rather than the category, and records the dial values it will build against.",
|
|
8
|
+
"files": [],
|
|
9
|
+
"assertions": [
|
|
10
|
+
"Skill activates on a build request",
|
|
11
|
+
"Design Read emitted before any UI edit",
|
|
12
|
+
"Dials recorded with explicit 1-10 values",
|
|
13
|
+
"Direction grounded in subject matter, not category default"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": 2,
|
|
18
|
+
"prompt": "Review this dashboard component for UX, accessibility, and motion quality. Don't change anything yet, just tell me what's wrong.",
|
|
19
|
+
"expected_output": "Agent activates design-craft in Review mode, reports severity-ranked findings in file:line form with the rule violated and the fix, and makes no edits to the target.",
|
|
20
|
+
"files": [],
|
|
21
|
+
"assertions": [
|
|
22
|
+
"Skill activates on a review request",
|
|
23
|
+
"Each finding names severity, file:line, rule, and fix",
|
|
24
|
+
"No target files modified during review"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": 3,
|
|
29
|
+
"prompt": "Refactor our billing service to use a repository pattern and add a Postgres index for the invoice lookup query.",
|
|
30
|
+
"expected_output": "Agent does NOT activate design-craft. Backend, API, database, and infrastructure work is out of scope for this skill.",
|
|
31
|
+
"files": [],
|
|
32
|
+
"assertions": [
|
|
33
|
+
"Anti-trigger fires: no design-craft activation for backend work",
|
|
34
|
+
"No Design Read emitted for a non-visual task"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": 4,
|
|
39
|
+
"prompt": "Polish the pricing page. Keep the brand.",
|
|
40
|
+
"expected_output": "Agent activates design-craft in Polish mode and, when a named anti-slop pattern applies to the existing surface, refuses it explicitly (for example a viewport-height hero, an entrance from zero scale, or an eyebrow above every heading) and states the replacement.",
|
|
41
|
+
"files": [],
|
|
42
|
+
"assertions": [
|
|
43
|
+
"Named anti-slop pattern identified and refused by name",
|
|
44
|
+
"Replacement choice stated rather than silent removal",
|
|
45
|
+
"Design Read names Polish mode and preserves the incumbent identity"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": 5,
|
|
50
|
+
"prompt": "The hero animation feels off. Make it smoother, and keep iterating until it's perfect.",
|
|
51
|
+
"expected_output": "Agent activates design-craft in Motion mode, answers the animate-or-not question and the easing/duration choice, verifies in bounded passes (build, one batched inspection, one fix pass, at most one confirm round), then stops and reports rather than iterating indefinitely.",
|
|
52
|
+
"files": [],
|
|
53
|
+
"assertions": [
|
|
54
|
+
"Motion decision recorded (whether to animate, easing, duration)",
|
|
55
|
+
"Verification stops after at most one confirm round",
|
|
56
|
+
"No unbounded polish loop; open items reported instead"
|
|
57
|
+
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": 6,
|
|
61
|
+
"prompt": "Which framework should I use for my new site, and can you set up the project scaffold?",
|
|
62
|
+
"expected_output": "Agent does NOT decide the stack as a design question. Renderer and framework selection is deferred to the project's own rules; the skill either stays inactive or activates only for the visual work after the stack is chosen.",
|
|
63
|
+
"files": [],
|
|
64
|
+
"assertions": [
|
|
65
|
+
"Anti-trigger fires: no design-craft authority claimed over renderer choice",
|
|
66
|
+
"Stack decision deferred to project rules"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": 7,
|
|
71
|
+
"prompt": "Design a settings page for our admin console.",
|
|
72
|
+
"expected_output": "Agent produces design guidance that is framework- and library-neutral: it detects the stack from the project, names no framework, styling system, motion library, or component library in its instructions, and derives idiom from the project's conventions or the version-accurate documentation lookup flow.",
|
|
73
|
+
"files": [],
|
|
74
|
+
"assertions": [
|
|
75
|
+
"Body and guidance name no framework or library (agnosticism)",
|
|
76
|
+
"Stack detected from the project rather than assumed",
|
|
77
|
+
"Framework idiom deferred to project conventions or a documentation lookup"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Reference
|
|
3
|
+
title: Anti-Slop Patterns
|
|
4
|
+
description: The named default patterns that make interfaces read as generated, and the choice that replaces each.
|
|
5
|
+
generated: { by: agentic/1.0, at: 2026-09-21T00:00:00Z }
|
|
6
|
+
sources:
|
|
7
|
+
- { id: taste-skill, resource: references/design-skills/taste-skill.md, license: absent-terms (ideas distilled and rewritten, attributed) }
|
|
8
|
+
- { id: frontend-design, resource: references/design-skills/frontend-design.md, license: absent-terms }
|
|
9
|
+
- { id: awwwards, resource: references/design-skills/build-awwwards-quality-sites.md, license: absent-terms }
|
|
10
|
+
- { id: emil-design-eng, resource: references/design-skills/emil-design-eng.md, license: absent-terms }
|
|
11
|
+
status: stable
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Anti-Slop Patterns
|
|
15
|
+
|
|
16
|
+
Defaults to recognise, name, and replace with a decision. Every one of these is legitimate when the brief asks for it; none is legitimate as a reflex.
|
|
17
|
+
|
|
18
|
+
| # | Pattern | Symptom | Fix |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| 1 | Viewport-height hero | the action sits below the fold; the layout jumps when mobile chrome appears | size the hero to its content, keep the action in the first viewport, use dynamic viewport units |
|
|
21
|
+
| 2 | Emoji as icons | emoji in navigation, buttons, or metadata | one consistent icon set; if none exists, ship fewer icons |
|
|
22
|
+
| 3 | Zero-scale entrance | elements grow from nothing | fade and translate from the element's own origin |
|
|
23
|
+
| 4 | Fade-in everything | every section animates in on scroll | one orchestrated entrance; leave the rest still |
|
|
24
|
+
| 5 | Uniform card kit | identical radius, padding, and shadow regardless of hierarchy | vary weight by importance; borders before shadows |
|
|
25
|
+
| 6 | Eyebrow overuse | an uppercase tracked label above every heading | at most one per three sections, including the hero |
|
|
26
|
+
| 7 | Split-header default | big headline beside a small floating paragraph, every section | stack headline over body; reserve the split for real content |
|
|
27
|
+
| 8 | Layout repetition | one image-plus-text split repeated down the page | one use per family per page; break with a full-width or grid section |
|
|
28
|
+
| 9 | Zigzag marathon | alternating image-left / image-right more than twice | cap at two, then change composition |
|
|
29
|
+
| 10 | Empty bento cells | filler or blank tiles to complete a grid | cells equal content; reshape rather than pad |
|
|
30
|
+
| 11 | Flat background monotony | identical white panels with only text inside | vary at least a third of cells: image, texture, or tint |
|
|
31
|
+
| 12 | Default type, accented word | stock family, default scale, one word in colour or italic in the headline | choose type for the subject; treat type as the design |
|
|
32
|
+
| 13 | Numbered markers | 01 / 02 / 03 on content that is not a sequence | numbers only for genuine sequences |
|
|
33
|
+
| 14 | Motion with no job | movement on every hover and every load | animate only to explain a change; see `motion-craft.md` |
|
|
34
|
+
| 15 | Continuous offscreen animation | looping background motion nobody is watching | pause offscreen; stop entirely under reduced motion |
|
|
35
|
+
| 16 | Blur and glow everywhere | glassy panels, outer glow on every accent | one deliberate use, with a solid fallback |
|
|
36
|
+
| 17 | Gradient blob decoration | abstract gradient shapes standing in for art direction | supply real imagery or nothing |
|
|
37
|
+
| 18 | Invented proof | logo walls, testimonials, metrics, or avatars that cannot be sourced | omit; honest absence beats fabrication |
|
|
38
|
+
| 19 | Clever copy | system vocabulary, jokes where guidance belongs | plain nouns, active voice, specific labels |
|
|
39
|
+
| 20 | Vague failure, apologetic emptiness | "Something went wrong"; blank panel with no direction | name the failure and the fix; make empty states an invitation |
|
|
40
|
+
| 21 | Transition everything | animating all properties by default | list the properties; keep motion off the layout |
|
|
41
|
+
| 22 | Focus removed | outlines suppressed for aesthetics | visible focus indicator on every interactive element |
|
|
42
|
+
| 23 | Hand-rolled glyphs | ad-hoc vector paths for standard symbols | use the project's icon set; compose rather than draw |
|
|
43
|
+
| 24 | Centred everything | every block centred because centring is safe | left-align by default; centre deliberately |
|
|
44
|
+
|
|
45
|
+
## Using the list
|
|
46
|
+
|
|
47
|
+
- **Name the pattern in reviews** — "eyebrow overuse", not "looks generic" — so the fix is obvious and arguable.
|
|
48
|
+
- **Check the brief first.** A deliberate brutalism brief wants flat panels and raw type; an editorial brief wants serif measure and numbered sections. Pattern 13 is correct when the content is a sequence.
|
|
49
|
+
- **Budget the tells.** One or two can be choices; four together is a template.
|