swe-workflow-skills 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 +144 -0
- package/VERSION +1 -0
- package/bin/cli.mjs +44 -0
- package/catalog.json +225 -0
- package/commands/role.md +34 -0
- package/hooks/session-start.mjs +118 -0
- package/install.mjs +318 -0
- package/package.json +47 -0
- package/roles.json +194 -0
- package/scripts/resolve.mjs +296 -0
- package/skills/accessibility-design/SKILL.md +128 -0
- package/skills/accessibility-design/evals/evals.json +51 -0
- package/skills/accessibility-design/references/accessibility-patterns.md +321 -0
- package/skills/api-design/SKILL.md +144 -0
- package/skills/api-design/evals/evals.json +48 -0
- package/skills/api-design/references/rest-conventions.md +147 -0
- package/skills/api-design/templates/api-spec.md +156 -0
- package/skills/architecture-design/SKILL.md +86 -0
- package/skills/architecture-design/evals/evals.json +44 -0
- package/skills/architecture-design/references/clean-architecture.md +147 -0
- package/skills/architecture-design/references/component-principles.md +129 -0
- package/skills/architecture-design/references/principles.md +81 -0
- package/skills/architecture-design/references/solid-principles.md +106 -0
- package/skills/architecture-design/templates/adr.md +63 -0
- package/skills/architecture-documentation/SKILL.md +126 -0
- package/skills/architecture-documentation/evals/evals.json +44 -0
- package/skills/architecture-documentation/references/abstraction-levels.md +91 -0
- package/skills/architecture-documentation/references/diagram-tooling.md +141 -0
- package/skills/architecture-documentation/templates/architecture-doc.md +171 -0
- package/skills/bug-investigating/SKILL.md +133 -0
- package/skills/bug-investigating/evals/evals.json +56 -0
- package/skills/bug-investigating/references/common-bugs.md +62 -0
- package/skills/bug-investigating/references/debugging-patterns.md +94 -0
- package/skills/cicd-pipeline/SKILL.md +87 -0
- package/skills/cicd-pipeline/evals/evals.json +34 -0
- package/skills/cicd-pipeline/references/pipeline-patterns.md +206 -0
- package/skills/cicd-pipeline/templates/github-actions.md +206 -0
- package/skills/code-reviewing/SKILL.md +92 -0
- package/skills/code-reviewing/evals/evals.json +62 -0
- package/skills/code-reviewing/references/error-handling.md +108 -0
- package/skills/code-reviewing/references/review-checklist.md +144 -0
- package/skills/configuration-strategy/SKILL.md +109 -0
- package/skills/configuration-strategy/evals/evals.json +41 -0
- package/skills/configuration-strategy/references/config-patterns.md +161 -0
- package/skills/containerization/SKILL.md +90 -0
- package/skills/containerization/evals/evals.json +36 -0
- package/skills/containerization/references/dockerfile-patterns.md +168 -0
- package/skills/containerization/templates/dockerfile.md +154 -0
- package/skills/data-modeling/SKILL.md +83 -0
- package/skills/data-modeling/evals/evals.json +43 -0
- package/skills/data-modeling/references/conventions.md +57 -0
- package/skills/data-modeling/templates/schema.md +120 -0
- package/skills/dependency-impact-analysis/SKILL.md +113 -0
- package/skills/dependency-impact-analysis/evals/evals.json +41 -0
- package/skills/dependency-impact-analysis/references/impact-patterns.md +150 -0
- package/skills/dependency-management/SKILL.md +161 -0
- package/skills/dependency-management/evals/evals.json +48 -0
- package/skills/dependency-management/references/evaluation-checklist.md +127 -0
- package/skills/deployment-checklist/SKILL.md +132 -0
- package/skills/deployment-checklist/evals/evals.json +58 -0
- package/skills/deployment-checklist/references/pre-deploy-gates.md +91 -0
- package/skills/deployment-repo/SKILL.md +190 -0
- package/skills/deployment-repo/evals/evals.json +44 -0
- package/skills/deployment-repo/references/deployment-repo-patterns.md +258 -0
- package/skills/deployment-repo/references/version-compatibility.md +227 -0
- package/skills/deployment-repo/templates/deployment-repo-structure.md +226 -0
- package/skills/effort-estimation/SKILL.md +95 -0
- package/skills/effort-estimation/evals/evals.json +32 -0
- package/skills/effort-estimation/references/estimation-methods.md +154 -0
- package/skills/feature-planning/SKILL.md +84 -0
- package/skills/feature-planning/evals/evals.json +44 -0
- package/skills/feature-planning/templates/plan.md +55 -0
- package/skills/feature-planning/templates/task.md +26 -0
- package/skills/frontend-architecture/SKILL.md +154 -0
- package/skills/frontend-architecture/evals/evals.json +34 -0
- package/skills/frontend-architecture/references/component-patterns.md +264 -0
- package/skills/frontend-architecture/templates/folder-structure.md +203 -0
- package/skills/git-workflow/SKILL.md +127 -0
- package/skills/git-workflow/evals/evals.json +44 -0
- package/skills/git-workflow/references/conventions.md +142 -0
- package/skills/git-workflow/templates/pull-request.md +57 -0
- package/skills/gitops-delivery/SKILL.md +319 -0
- package/skills/gitops-delivery/evals/evals.json +44 -0
- package/skills/gitops-delivery/references/gitops-patterns.md +478 -0
- package/skills/gitops-delivery/templates/gitops-config.md +196 -0
- package/skills/incident-response/SKILL.md +130 -0
- package/skills/incident-response/evals/evals.json +55 -0
- package/skills/incident-response/references/communication-templates.md +134 -0
- package/skills/incident-response/references/severity-levels.md +77 -0
- package/skills/incident-response/templates/incident-timeline.md +57 -0
- package/skills/infrastructure-as-code/SKILL.md +86 -0
- package/skills/infrastructure-as-code/evals/evals.json +33 -0
- package/skills/infrastructure-as-code/references/iac-patterns.md +179 -0
- package/skills/infrastructure-as-code/templates/terraform-module.md +158 -0
- package/skills/metrics-and-okrs/SKILL.md +113 -0
- package/skills/metrics-and-okrs/evals/evals.json +33 -0
- package/skills/metrics-and-okrs/references/metric-catalog.md +90 -0
- package/skills/metrics-and-okrs/templates/okr-document.md +61 -0
- package/skills/ml-experiment-tracking/SKILL.md +96 -0
- package/skills/ml-experiment-tracking/evals/evals.json +32 -0
- package/skills/ml-experiment-tracking/references/tracking-tools.md +127 -0
- package/skills/ml-model-deployment/SKILL.md +106 -0
- package/skills/ml-model-deployment/evals/evals.json +34 -0
- package/skills/ml-model-deployment/references/serving-patterns.md +162 -0
- package/skills/ml-pipeline-design/SKILL.md +162 -0
- package/skills/ml-pipeline-design/evals/evals.json +34 -0
- package/skills/ml-pipeline-design/references/pipeline-components.md +174 -0
- package/skills/observability-design/SKILL.md +162 -0
- package/skills/observability-design/evals/evals.json +52 -0
- package/skills/observability-design/references/logging-patterns.md +229 -0
- package/skills/observability-design/references/slo-framework.md +151 -0
- package/skills/observability-design/templates/slo-document.md +80 -0
- package/skills/performance-optimization/SKILL.md +83 -0
- package/skills/performance-optimization/evals/evals.json +47 -0
- package/skills/performance-optimization/references/bottleneck-patterns.md +256 -0
- package/skills/performance-optimization/references/concurrency.md +101 -0
- package/skills/prd-writing/SKILL.md +109 -0
- package/skills/prd-writing/evals/evals.json +33 -0
- package/skills/prd-writing/references/prd-examples.md +132 -0
- package/skills/prd-writing/templates/prd.md +71 -0
- package/skills/prd-writing/templates/rfc.md +79 -0
- package/skills/project-documentation/SKILL.md +104 -0
- package/skills/project-documentation/evals/evals.json +48 -0
- package/skills/project-documentation/references/contributing-guide.md +100 -0
- package/skills/project-documentation/templates/changelog.md +59 -0
- package/skills/project-documentation/templates/readme.md +121 -0
- package/skills/project-proposal/SKILL.md +90 -0
- package/skills/project-proposal/evals/evals.json +31 -0
- package/skills/project-proposal/references/proposal-examples.md +140 -0
- package/skills/project-proposal/templates/proposal.md +76 -0
- package/skills/project-review/SKILL.md +111 -0
- package/skills/project-review/evals/evals.json +42 -0
- package/skills/refactoring/SKILL.md +90 -0
- package/skills/refactoring/evals/evals.json +44 -0
- package/skills/refactoring/references/transformations.md +132 -0
- package/skills/retrospective/SKILL.md +157 -0
- package/skills/retrospective/evals/evals.json +34 -0
- package/skills/retrospective/references/facilitation-guide.md +130 -0
- package/skills/retrospective/templates/post-mortem.md +121 -0
- package/skills/rollback-strategy/SKILL.md +116 -0
- package/skills/rollback-strategy/evals/evals.json +55 -0
- package/skills/rollback-strategy/references/rollback-patterns.md +125 -0
- package/skills/rollback-strategy/templates/rollback-plan.md +69 -0
- package/skills/security-audit/SKILL.md +146 -0
- package/skills/security-audit/evals/evals.json +66 -0
- package/skills/security-audit/references/owasp-top-10.md +167 -0
- package/skills/security-audit/templates/security-report.md +79 -0
- package/skills/skill-router/SKILL.md +171 -0
- package/skills/skill-router/evals/evals.json +39 -0
- package/skills/strategic-review/SKILL.md +106 -0
- package/skills/strategic-review/evals/evals.json +42 -0
- package/skills/strategic-review/templates/full-review-prompt.md +84 -0
- package/skills/tdd-workflow/SKILL.md +147 -0
- package/skills/tdd-workflow/evals/evals.json +58 -0
- package/skills/tdd-workflow/references/test-quality.md +71 -0
- package/skills/technical-debt-review/SKILL.md +113 -0
- package/skills/technical-debt-review/evals/evals.json +41 -0
- package/skills/technical-debt-review/references/debt-taxonomy.md +124 -0
- package/skills/technical-debt-review/templates/debt-audit.md +85 -0
- package/skills/test-data-strategy/SKILL.md +129 -0
- package/skills/test-data-strategy/evals/evals.json +49 -0
- package/skills/test-data-strategy/references/data-generation-patterns.md +341 -0
- package/skills/test-suite-design/SKILL.md +137 -0
- package/skills/test-suite-design/evals/evals.json +69 -0
- package/skills/test-suite-design/references/test-infrastructure.md +175 -0
- package/skills/test-suite-design/references/testing-pyramid.md +140 -0
- package/skills/ui-ux-design/SKILL.md +117 -0
- package/skills/ui-ux-design/evals/evals.json +35 -0
- package/skills/ui-ux-design/references/interaction-patterns.md +145 -0
- package/skills/ui-ux-design/templates/screen-spec.md +97 -0
- package/skills/verification-before-completion/SKILL.md +85 -0
- package/skills/verification-before-completion/evals/evals.json +53 -0
- package/skills/writing-skills/SKILL.md +87 -0
- package/skills/writing-skills/evals/evals.json +41 -0
- package/skills/writing-skills/references/pressure-testing.md +69 -0
- package/uninstall.mjs +182 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Resolve roles.json into skill lists and skillOverrides maps.
|
|
3
|
+
//
|
|
4
|
+
// Dependency-free helper shared by install.mjs / uninstall.mjs (import) and by the
|
|
5
|
+
// SessionStart hook + the /role command (CLI). Pure JSON + set logic — runs on
|
|
6
|
+
// Linux, macOS, and Windows with only the Node that Claude Code already requires.
|
|
7
|
+
//
|
|
8
|
+
// Usage (CLI):
|
|
9
|
+
// node resolve.mjs roles List role keys + labels (TSV).
|
|
10
|
+
// node resolve.mjs skills <role> Resolved skill set for a role (one per line).
|
|
11
|
+
// node resolve.mjs label <role> Human label for a role.
|
|
12
|
+
// node resolve.mjs overrides [role] Emit a skillOverrides JSON object marking
|
|
13
|
+
// every installed skill outside (pinned ∪ role)
|
|
14
|
+
// as "name-only". Installed skill names are read
|
|
15
|
+
// from stdin (newline-separated). No role => only
|
|
16
|
+
// the pinned set stays on (the baseline).
|
|
17
|
+
// node resolve.mjs apply <settings.local.json> <skills_dir> [role]
|
|
18
|
+
// Merge the name-only baseline (for [role], or
|
|
19
|
+
// pinned-only) into the settings file's
|
|
20
|
+
// skillOverrides, preserving all other keys and
|
|
21
|
+
// any overrides for non-installed skills.
|
|
22
|
+
// node resolve.mjs prune <settings.local.json> [skill ...]
|
|
23
|
+
// Remove the given skills from the settings
|
|
24
|
+
// file's skillOverrides (used by uninstall).
|
|
25
|
+
// Drops skillOverrides entirely when empty.
|
|
26
|
+
// node resolve.mjs validate <skills_dir> Integrity check: every referenced skill
|
|
27
|
+
// exists, and every non-meta skill is in >=1 role.
|
|
28
|
+
//
|
|
29
|
+
// roles.json is read from $ROLES_JSON if set, else ../roles.json relative to this file.
|
|
30
|
+
|
|
31
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, readdirSync } from "node:fs";
|
|
32
|
+
import { dirname, join, resolve as resolvePath } from "node:path";
|
|
33
|
+
import { fileURLToPath } from "node:url";
|
|
34
|
+
|
|
35
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
export const DEFAULT_ROLES_JSON =
|
|
37
|
+
process.env.ROLES_JSON || join(HERE, "..", "roles.json");
|
|
38
|
+
|
|
39
|
+
// ---- core resolution -------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
export function loadRoles(rolesPath = DEFAULT_ROLES_JSON) {
|
|
42
|
+
return JSON.parse(readFileSync(rolesPath, "utf-8"));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function roleOrDie(data, role) {
|
|
46
|
+
const roles = data.roles || {};
|
|
47
|
+
if (!(role in roles)) {
|
|
48
|
+
const known = Object.keys(roles).sort().join(", ");
|
|
49
|
+
die(`unknown role '${role}' (known: ${known})`);
|
|
50
|
+
}
|
|
51
|
+
return roles[role];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Role working set = its core set UNION its own skills, order-stable.
|
|
55
|
+
export function resolvedSkills(data, role) {
|
|
56
|
+
const r = roleOrDie(data, role);
|
|
57
|
+
const core = (data.core || {})[r.core || "universal"] || [];
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const skill of [...core, ...(r.skills || [])]) {
|
|
61
|
+
if (!seen.has(skill)) {
|
|
62
|
+
seen.add(skill);
|
|
63
|
+
out.push(skill);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Skills that stay `on` (full description, auto-trigger): the pinned set, plus the
|
|
70
|
+
// active role's working set when a role is given.
|
|
71
|
+
export function keepOnSet(data, role) {
|
|
72
|
+
const keep = new Set(data.pinned || []);
|
|
73
|
+
if (role) for (const s of resolvedSkills(data, role)) keep.add(s);
|
|
74
|
+
return keep;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function nameonlyMap(data, installed, role) {
|
|
78
|
+
const keepOn = keepOnSet(data, role);
|
|
79
|
+
const map = {};
|
|
80
|
+
for (const s of installed) if (!keepOn.has(s)) map[s] = "name-only";
|
|
81
|
+
return map;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Directory entries that are skills (subdirectories), sorted.
|
|
85
|
+
export function installedSkills(skillsDir) {
|
|
86
|
+
return readdirSync(skillsDir)
|
|
87
|
+
.filter((e) => statSync(join(skillsDir, e)).isDirectory())
|
|
88
|
+
.sort();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---- settings I/O ----------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
// Recursively sort object keys so output is deterministic (mirrors Python's
|
|
94
|
+
// json.dump(..., sort_keys=True)).
|
|
95
|
+
function sortKeysDeep(value) {
|
|
96
|
+
if (Array.isArray(value)) return value.map(sortKeysDeep);
|
|
97
|
+
if (value && typeof value === "object") {
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const k of Object.keys(value).sort()) out[k] = sortKeysDeep(value[k]);
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function loadSettings(settingsPath) {
|
|
106
|
+
if (existsSync(settingsPath) && statSync(settingsPath).isFile()) {
|
|
107
|
+
try {
|
|
108
|
+
return JSON.parse(readFileSync(settingsPath, "utf-8")) || {};
|
|
109
|
+
} catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function writeSettings(settingsPath, settings) {
|
|
117
|
+
mkdirSync(dirname(resolvePath(settingsPath)), { recursive: true });
|
|
118
|
+
writeFileSync(settingsPath, JSON.stringify(sortKeysDeep(settings), null, 2) + "\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- apply / prune ---------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
// Merge the name-only baseline into a settings file. Returns {nameOnly, on, role}.
|
|
124
|
+
export function applyBaseline(data, settingsPath, skillsDir, role) {
|
|
125
|
+
if (role === "all" || role === "none") role = null;
|
|
126
|
+
if (role) roleOrDie(data, role);
|
|
127
|
+
|
|
128
|
+
const installed = installedSkills(skillsDir);
|
|
129
|
+
const desired = nameonlyMap(data, installed, role); // {skill: "name-only"} tail
|
|
130
|
+
|
|
131
|
+
const settings = loadSettings(settingsPath);
|
|
132
|
+
let existing = settings.skillOverrides;
|
|
133
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) existing = {};
|
|
134
|
+
|
|
135
|
+
// Preserve overrides for skills we don't manage (not installed here); we own every
|
|
136
|
+
// installed skill's entry: name-only for the tail, absent (=> on) otherwise.
|
|
137
|
+
const installedSet = new Set(installed);
|
|
138
|
+
const merged = {};
|
|
139
|
+
for (const [k, v] of Object.entries(existing)) if (!installedSet.has(k)) merged[k] = v;
|
|
140
|
+
Object.assign(merged, desired);
|
|
141
|
+
settings.skillOverrides = merged;
|
|
142
|
+
|
|
143
|
+
writeSettings(settingsPath, settings);
|
|
144
|
+
const nameOnly = Object.keys(desired).length;
|
|
145
|
+
return { nameOnly, on: installed.length - nameOnly, role };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Remove the given skills from a settings file's skillOverrides. Returns removed count.
|
|
149
|
+
export function pruneSettings(settingsPath, skills) {
|
|
150
|
+
if (!(existsSync(settingsPath) && statSync(settingsPath).isFile())) return { removed: 0, missing: true };
|
|
151
|
+
const settings = loadSettings(settingsPath);
|
|
152
|
+
const overrides = settings.skillOverrides;
|
|
153
|
+
if (!overrides || typeof overrides !== "object" || Array.isArray(overrides)) {
|
|
154
|
+
return { removed: 0, noOverrides: true };
|
|
155
|
+
}
|
|
156
|
+
const want = new Set(skills);
|
|
157
|
+
let removed = 0;
|
|
158
|
+
for (const s of want) {
|
|
159
|
+
if (s in overrides) {
|
|
160
|
+
delete overrides[s];
|
|
161
|
+
removed++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (Object.keys(overrides).length > 0) settings.skillOverrides = overrides;
|
|
165
|
+
else delete settings.skillOverrides; // drop the empty key entirely
|
|
166
|
+
writeSettings(settingsPath, settings);
|
|
167
|
+
return { removed };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- validate --------------------------------------------------------------
|
|
171
|
+
|
|
172
|
+
export function validate(data, skillsDir) {
|
|
173
|
+
const errors = [];
|
|
174
|
+
|
|
175
|
+
// Every skill referenced anywhere in the SSOT.
|
|
176
|
+
const referenced = new Set([...(data.pinned || []), ...(data.meta_only || [])]);
|
|
177
|
+
for (const core of Object.values(data.core || {})) for (const s of core) referenced.add(s);
|
|
178
|
+
const inARole = new Set();
|
|
179
|
+
for (const role of Object.keys(data.roles || {})) {
|
|
180
|
+
for (const s of resolvedSkills(data, role)) {
|
|
181
|
+
referenced.add(s);
|
|
182
|
+
inARole.add(s);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Every referenced skill must exist on disk.
|
|
187
|
+
for (const skill of [...referenced].sort()) {
|
|
188
|
+
if (!isDir(join(skillsDir, skill))) errors.push(`referenced skill missing on disk: ${skill}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Every non-meta skill on disk must belong to >=1 role (no orphans).
|
|
192
|
+
const meta = new Set(data.meta_only || []);
|
|
193
|
+
const universal = new Set((data.core || {}).universal || []);
|
|
194
|
+
const technical = new Set((data.core || {}).technical || []);
|
|
195
|
+
const covered = new Set([...inARole, ...universal, ...technical]);
|
|
196
|
+
for (const entry of readdirSync(skillsDir).sort()) {
|
|
197
|
+
if (!isDir(join(skillsDir, entry))) continue;
|
|
198
|
+
if (meta.has(entry)) continue;
|
|
199
|
+
if (!covered.has(entry)) errors.push(`orphan skill (in no role/core): ${entry}`);
|
|
200
|
+
}
|
|
201
|
+
return errors;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isDir(p) {
|
|
205
|
+
return existsSync(p) && statSync(p).isDirectory();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ---- CLI -------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
export function die(msg, code = 1) {
|
|
211
|
+
process.stderr.write(`resolve.mjs: ${msg}\n`);
|
|
212
|
+
process.exit(code);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function readStdin() {
|
|
216
|
+
try {
|
|
217
|
+
return readFileSync(0, "utf-8");
|
|
218
|
+
} catch {
|
|
219
|
+
return "";
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const CLI = {
|
|
224
|
+
roles(data) {
|
|
225
|
+
for (const [key, r] of Object.entries(data.roles || {})) {
|
|
226
|
+
process.stdout.write(`${key}\t${r.label || key}\n`);
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
skills(data, args) {
|
|
230
|
+
if (!args[0]) die("skills requires a <role>");
|
|
231
|
+
for (const s of resolvedSkills(data, args[0])) process.stdout.write(s + "\n");
|
|
232
|
+
},
|
|
233
|
+
label(data, args) {
|
|
234
|
+
if (!args[0]) die("label requires a <role>");
|
|
235
|
+
process.stdout.write(roleOrDie(data, args[0]).label || args[0]);
|
|
236
|
+
process.stdout.write("\n");
|
|
237
|
+
},
|
|
238
|
+
overrides(data, args) {
|
|
239
|
+
const role = args[0] || null;
|
|
240
|
+
if (role) roleOrDie(data, role);
|
|
241
|
+
const installed = readStdin()
|
|
242
|
+
.split("\n")
|
|
243
|
+
.map((l) => l.trim())
|
|
244
|
+
.filter(Boolean);
|
|
245
|
+
process.stdout.write(JSON.stringify(sortKeysDeep(nameonlyMap(data, installed, role)), null, 2) + "\n");
|
|
246
|
+
},
|
|
247
|
+
apply(data, args) {
|
|
248
|
+
if (args.length < 2) die("apply requires <settings.local.json> <skills_dir> [role]");
|
|
249
|
+
const { nameOnly, on, role } = applyBaseline(data, args[0], args[1], args[2]);
|
|
250
|
+
process.stdout.write(
|
|
251
|
+
`applied: ${nameOnly} name-only, ${on} on (role=${role || "baseline"}) -> ${args[0]}\n`,
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
prune(_data, args) {
|
|
255
|
+
if (!args[0]) die("prune requires <settings.local.json> [skill ...]");
|
|
256
|
+
const settingsPath = args[0];
|
|
257
|
+
const res = pruneSettings(settingsPath, args.slice(1));
|
|
258
|
+
if (res.missing) process.stdout.write(`pruned: 0 (no settings file at ${settingsPath})\n`);
|
|
259
|
+
else if (res.noOverrides) process.stdout.write(`pruned: 0 (no skillOverrides) -> ${settingsPath}\n`);
|
|
260
|
+
else process.stdout.write(`pruned: ${res.removed} skillOverrides entries -> ${settingsPath}\n`);
|
|
261
|
+
},
|
|
262
|
+
validate(data, args) {
|
|
263
|
+
if (!args[0]) die("validate requires a <skills_dir>");
|
|
264
|
+
const errors = validate(data, args[0]);
|
|
265
|
+
if (errors.length) {
|
|
266
|
+
for (const e of errors) process.stderr.write(`FAIL: ${e}\n`);
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
process.stdout.write(`OK: roles.json integrity verified against ${args[0]}\n`);
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
// Commands that don't read roles.json — they work on a settings file alone, so they
|
|
274
|
+
// must run even when resolve.mjs is installed away from roles.json (e.g. under hooks/).
|
|
275
|
+
const NO_ROLES_DATA = new Set(["prune"]);
|
|
276
|
+
|
|
277
|
+
function main() {
|
|
278
|
+
const [cmd, ...args] = process.argv.slice(2);
|
|
279
|
+
if (!cmd || !(cmd in CLI)) {
|
|
280
|
+
die(`usage: resolve.mjs <${Object.keys(CLI).join("|")}> [args]`, 2);
|
|
281
|
+
}
|
|
282
|
+
const data = NO_ROLES_DATA.has(cmd) ? {} : loadRoles();
|
|
283
|
+
CLI[cmd](data, args);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Run as CLI only when invoked directly (not when imported). Compare case-insensitively
|
|
287
|
+
// on Windows, where the invoking path's drive-letter / casing may differ from the
|
|
288
|
+
// module URL's even when they point at the same file.
|
|
289
|
+
function samePath(a, b) {
|
|
290
|
+
const x = resolvePath(a);
|
|
291
|
+
const y = resolvePath(b);
|
|
292
|
+
return process.platform === "win32" ? x.toLowerCase() === y.toLowerCase() : x === y;
|
|
293
|
+
}
|
|
294
|
+
if (process.argv[1] && samePath(process.argv[1], fileURLToPath(import.meta.url))) {
|
|
295
|
+
main();
|
|
296
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: accessibility-design
|
|
3
|
+
description: "Design and implement accessible web/mobile apps to WCAG 2.1/2.2 — semantic HTML, ARIA patterns, keyboard navigation, focus management, color contrast, screen readers, accessible forms. Triggers: accessibility, a11y, WCAG, screen reader, keyboard navigation, ARIA, focus management, color contrast, alt text, semantic HTML, tab order, focus trap, skip link, announce. Build it in, don't bolt it on."
|
|
4
|
+
model: sonnet
|
|
5
|
+
allowed-tools: Read, Grep, Glob, Write, Edit
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Accessibility Design
|
|
9
|
+
|
|
10
|
+
Build accessibility into applications from the start. Accessibility is not a feature — it's a quality attribute, like performance or security. Retrofitting it is 10x harder than building it in.
|
|
11
|
+
|
|
12
|
+
## Core Principle
|
|
13
|
+
|
|
14
|
+
**Use semantic HTML first, ARIA second, custom JavaScript last.** A native `<button>` gives you keyboard support, focus management, and screen reader announcement for free. A `<div onClick>` gives you none of that and requires manual reimplementation of every behavior the browser provides natively.
|
|
15
|
+
|
|
16
|
+
## Workflow
|
|
17
|
+
|
|
18
|
+
### Step 1: Understand the Requirements
|
|
19
|
+
|
|
20
|
+
Determine the target compliance level:
|
|
21
|
+
|
|
22
|
+
- **WCAG 2.1 Level A**: Minimum baseline (avoid lawsuits, but still inaccessible)
|
|
23
|
+
- **WCAG 2.1 Level AA**: Industry standard (recommended minimum for all projects)
|
|
24
|
+
- **WCAG 2.2 Level AA**: Current standard, adds mobile and cognitive accessibility
|
|
25
|
+
- **WCAG Level AAA**: Aspirational — few sites achieve this fully
|
|
26
|
+
|
|
27
|
+
For most projects, **target WCAG 2.1 AA** as the minimum. Level AAA for specific features where feasible.
|
|
28
|
+
|
|
29
|
+
### Step 2: Apply Semantic HTML
|
|
30
|
+
|
|
31
|
+
Before reaching for ARIA, use the right HTML element:
|
|
32
|
+
|
|
33
|
+
| Instead of... | Use... | You get for free... |
|
|
34
|
+
|--------------|--------|---------------------|
|
|
35
|
+
| `<div onClick>` | `<button>` | Focus, Enter/Space activation, role announcement |
|
|
36
|
+
| `<div>` for navigation | `<nav>` | Landmark for screen reader navigation |
|
|
37
|
+
| `<span>` for heading | `<h1>`-`<h6>` | Document outline, heading navigation |
|
|
38
|
+
| `<div>` for list | `<ul>/<ol>` | List count announcement, list navigation |
|
|
39
|
+
| `<div>` for input | `<input>` + `<label>` | Label association, auto-focus, form submission |
|
|
40
|
+
| `<div>` for table | `<table>` | Row/column navigation, header association |
|
|
41
|
+
| Custom dropdown | `<select>` or `<details>` | Keyboard navigation, screen reader support |
|
|
42
|
+
|
|
43
|
+
See [references/accessibility-patterns.md](references/accessibility-patterns.md) for comprehensive patterns.
|
|
44
|
+
|
|
45
|
+
### Step 3: Design Keyboard Navigation
|
|
46
|
+
|
|
47
|
+
Every interactive element must be operable by keyboard alone:
|
|
48
|
+
|
|
49
|
+
**Tab order:** Interactive elements should follow a logical reading order. Don't use `tabindex` values > 0 (they override the natural order). Use `tabindex="0"` to add non-interactive elements to the tab order when needed, and `tabindex="-1"` to make elements focusable programmatically but not in the tab sequence.
|
|
50
|
+
|
|
51
|
+
**Focus visibility:** Focused elements must have a visible indicator. Never `outline: none` without providing an alternative. Use `:focus-visible` (shows focus ring only for keyboard users, not mouse clicks).
|
|
52
|
+
|
|
53
|
+
**Keyboard shortcuts for common patterns:**
|
|
54
|
+
|
|
55
|
+
| Pattern | Keys |
|
|
56
|
+
|---------|------|
|
|
57
|
+
| Buttons | Enter or Space to activate |
|
|
58
|
+
| Links | Enter to follow |
|
|
59
|
+
| Menus | Arrow keys to navigate, Enter to select, Escape to close |
|
|
60
|
+
| Tabs | Arrow keys between tabs, Tab to move focus into panel |
|
|
61
|
+
| Modals | Tab cycles within modal (focus trap), Escape to close |
|
|
62
|
+
| Autocomplete | Arrow keys to navigate suggestions, Enter to select |
|
|
63
|
+
|
|
64
|
+
### Step 4: Design Focus Management
|
|
65
|
+
|
|
66
|
+
Focus management is critical for dynamic content. When the page changes without a full reload, focus must move to the right place:
|
|
67
|
+
|
|
68
|
+
| Scenario | Focus should go to... |
|
|
69
|
+
|----------|----------------------|
|
|
70
|
+
| Modal opens | First focusable element inside the modal |
|
|
71
|
+
| Modal closes | The element that triggered the modal |
|
|
72
|
+
| Item deleted from list | Next item in list, or previous if last was deleted |
|
|
73
|
+
| Error after form submit | First field with an error |
|
|
74
|
+
| Page/route change | Main content area or page heading |
|
|
75
|
+
| Toast notification | Do NOT move focus (announce with aria-live instead) |
|
|
76
|
+
|
|
77
|
+
### Step 5: Handle Dynamic Content
|
|
78
|
+
|
|
79
|
+
When content changes without page reload, screen readers need to be notified:
|
|
80
|
+
|
|
81
|
+
**aria-live regions:**
|
|
82
|
+
- `aria-live="polite"` — Announce when the screen reader is idle (status messages, search results count)
|
|
83
|
+
- `aria-live="assertive"` — Interrupt immediately (errors, critical alerts)
|
|
84
|
+
|
|
85
|
+
**Common announcements:**
|
|
86
|
+
- Form errors: "3 errors found. First error: Email is required."
|
|
87
|
+
- Search results: "24 results found for 'react'."
|
|
88
|
+
- Toast: "Settings saved successfully."
|
|
89
|
+
- Loading: "Loading your dashboard..." → "Dashboard loaded."
|
|
90
|
+
|
|
91
|
+
### Step 6: Ensure Visual Accessibility
|
|
92
|
+
|
|
93
|
+
**Color contrast:**
|
|
94
|
+
- Normal text: 4.5:1 ratio minimum (AA)
|
|
95
|
+
- Large text (18px+ or 14px+ bold): 3:1 ratio minimum (AA)
|
|
96
|
+
- UI components and graphical objects: 3:1 ratio minimum
|
|
97
|
+
- Don't convey information by color alone — use icons, patterns, or text labels alongside color
|
|
98
|
+
|
|
99
|
+
**Typography and spacing:**
|
|
100
|
+
- Body text: minimum 16px (1rem)
|
|
101
|
+
- Line height: at least 1.5 for body text
|
|
102
|
+
- Paragraph spacing: at least 2x font size
|
|
103
|
+
- Users must be able to zoom to 200% without horizontal scrolling
|
|
104
|
+
|
|
105
|
+
### Step 7: Validate
|
|
106
|
+
|
|
107
|
+
Automated testing catches ~30% of accessibility issues. Manual testing catches the rest.
|
|
108
|
+
|
|
109
|
+
**Automated:**
|
|
110
|
+
- Run axe-core or Lighthouse accessibility audit
|
|
111
|
+
- Add eslint-plugin-jsx-a11y to catch common React issues at build time
|
|
112
|
+
- Include @axe-core/react in development mode for runtime warnings
|
|
113
|
+
|
|
114
|
+
**Manual:**
|
|
115
|
+
- [ ] Navigate the entire flow using only keyboard (no mouse)
|
|
116
|
+
- [ ] Test with a screen reader (VoiceOver on Mac, NVDA on Windows)
|
|
117
|
+
- [ ] Verify all images have meaningful alt text (or `alt=""` for decorative)
|
|
118
|
+
- [ ] Verify color contrast with a contrast checker tool
|
|
119
|
+
- [ ] Test at 200% zoom — no horizontal scroll, no overlapping text
|
|
120
|
+
- [ ] Verify all form fields have associated labels
|
|
121
|
+
- [ ] Verify error messages are announced to screen readers
|
|
122
|
+
|
|
123
|
+
## Principles Applied
|
|
124
|
+
|
|
125
|
+
- **KISS**: Use native HTML first. Every custom ARIA widget is code you have to maintain and test.
|
|
126
|
+
- **Progressive enhancement**: Build the accessible version first, then enhance for visual design. Not the other way around.
|
|
127
|
+
- **DRY**: Accessible patterns in shared components (Button, Input, Modal) mean every consumer gets accessibility for free.
|
|
128
|
+
- **Defense in depth**: Automated linting + automated testing + manual testing. No single method catches everything.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "accessibility-design",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "I'm building a modal dialog component in React for confirming destructive actions (like deleting a project). Make it accessible.",
|
|
7
|
+
"expected_output": "Should implement the full accessible modal pattern: role=dialog, aria-modal, focus trap, Escape to close, focus restoration, background inert, and proper labeling.",
|
|
8
|
+
"assertions": [
|
|
9
|
+
"Uses role='dialog' and aria-modal='true'",
|
|
10
|
+
"Labels the dialog with aria-labelledby pointing to the title",
|
|
11
|
+
"Implements focus trap (Tab cycles within modal)",
|
|
12
|
+
"Moves focus to the first focusable element on open",
|
|
13
|
+
"Returns focus to the trigger element on close",
|
|
14
|
+
"Closes on Escape key",
|
|
15
|
+
"Makes background content inert while open",
|
|
16
|
+
"Destructive button is NOT the default focus (Cancel is safer default)",
|
|
17
|
+
"Uses semantic HTML (button elements, not divs with onClick)"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": 2,
|
|
22
|
+
"prompt": "Audit this React component for accessibility issues:\n\n<div className=\"card\" onClick={() => navigate(`/project/${id}`)}>\n <div className=\"title\">{name}</div>\n <img src={thumbnail} />\n <div className=\"badge\" style={{color: status === 'active' ? 'green' : 'red'}}>\n {status}\n </div>\n <div className=\"actions\">\n <div className=\"btn\" onClick={(e) => { e.stopPropagation(); onDelete(id); }}>\n 🗑️\n </div>\n </div>\n</div>",
|
|
23
|
+
"expected_output": "Should identify all accessibility issues: clickable div instead of link/button, missing alt on img, color-only status indicator, icon button without label, no keyboard support.",
|
|
24
|
+
"assertions": [
|
|
25
|
+
"Identifies clickable div should be an <a> or <Link> (for navigation)",
|
|
26
|
+
"Identifies missing alt attribute on the image",
|
|
27
|
+
"Identifies color-only status indicator (green/red without text or icon)",
|
|
28
|
+
"Identifies delete button as a div — should be <button>",
|
|
29
|
+
"Identifies emoji icon without accessible label (aria-label needed)",
|
|
30
|
+
"Notes that div+onClick has no keyboard support (no Enter/Space handling)",
|
|
31
|
+
"Provides corrected accessible version of the component",
|
|
32
|
+
"Corrected version uses semantic HTML (a, button, img alt)"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": 3,
|
|
37
|
+
"prompt": "We're building a form with email, password, a terms checkbox, and a submit button. The designer wants inline validation that shows errors as you type. Make it accessible.",
|
|
38
|
+
"expected_output": "Should implement accessible form with labels, error announcements, aria-invalid, aria-describedby, and proper validation timing.",
|
|
39
|
+
"assertions": [
|
|
40
|
+
"Every input has an associated <label> with htmlFor",
|
|
41
|
+
"Error messages use aria-describedby to link to their field",
|
|
42
|
+
"Fields with errors have aria-invalid='true'",
|
|
43
|
+
"Errors are announced to screen readers (role='alert' or aria-live)",
|
|
44
|
+
"Validation is debounced (not on every keystroke)",
|
|
45
|
+
"Checkbox has an associated label (clickable text)",
|
|
46
|
+
"Submit button is a <button type='submit'>",
|
|
47
|
+
"On submit with errors, focus moves to the first field with an error"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|