td-ai-tools 1.1.8 → 1.1.10
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/package.json +1 -1
- package/skills/README.md +3 -0
- package/skills/a11y-audit/SKILL.md +60 -0
- package/skills/a11y-audit/agents/openai.yaml +4 -0
- package/skills/a11y-audit/package.json +17 -0
- package/skills/a11y-audit/scripts/audit.js +624 -0
- package/skills/a11y-audit/scripts/setup.sh +6 -0
- package/skills/browser-validation/SKILL.md +15 -0
- package/skills/browser-validation/agents/openai.yaml +4 -0
- package/skills/browser-validation/config.toml +1 -0
- package/skills/scry/SKILL.md +69 -0
- package/skills/scry/scripts/scry.py +1 -0
- package/skills/shopify-cli/SKILL.md +58 -0
- package/skills/shopify-cli/agents/openai.yaml +4 -0
- package/skills/shopify-cli/config.toml +4 -0
- package/skills/shopify-cli/references/other-theme-commands.md +72 -0
- package/skills/shopify-cli/references/theme-management.md +95 -0
package/package.json
CHANGED
package/skills/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# skills
|
|
2
2
|
|
|
3
3
|
## Available Skills
|
|
4
|
+
- `a11y-audit`: Run script-driven accessibility audits for user-provided URLs with Axe Core and Playwright, targeting…
|
|
4
5
|
- `barrage`: Convert a group of Basecamp todos or cards into a td-barrage queue.json task file.
|
|
5
6
|
- `basecamp`: Interact with Basecamp via the Basecamp CLI.
|
|
7
|
+
- `browser-validation`: Before completing a task validate frontend or template changes in a real browser with the Playwright-CLI…
|
|
6
8
|
- `cache-reset`: Clear and warm Laravel and Statamic caches (including Statamic Glide image caches) after content or template…
|
|
7
9
|
- `car-ticket-generator`: Generate a ticket for the codex-auto-runner queue
|
|
8
10
|
- `everhour-basecamp-estimates`: Bulk update Everhour task estimates from a Basecamp todo or todolist URL, then append bracketed hours to the…
|
|
@@ -14,6 +16,7 @@
|
|
|
14
16
|
- `pull-request-statamic`: Generates GitHub pull request descriptions for Statamic and Laravel development by analyzing git diffs and…
|
|
15
17
|
- `record-changes`: Update `docs/changes.md` by summarizing the current branch against the primary development branch.
|
|
16
18
|
- `scry`: Single-site visual regression workflow for comparing a live URL against a preview/staging URL in Playwright…
|
|
19
|
+
- `shopify-cli`: Shopify CLI workflows for theme development. Use when the user needs to run or explain Shopify theme commands,…
|
|
17
20
|
- `stylesheet-migration`: Migrate Shopify Liquid `{% stylesheet %}` blocks into theme CSS assets using bundled Python scripts.
|
|
18
21
|
- `td-js-vanilla-rules`: Theory Digital vanilla JavaScript standards for Shopify theme work.
|
|
19
22
|
- `td-review`: Run parallel code review agents on a PR (including TD theme compliance) and produce a synthesized findings…
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: a11y-audit
|
|
3
|
+
description: Run script-driven accessibility audits for user-provided URLs with Axe Core and Playwright, targeting baseline or WCAG thresholds such as WCAG 2.1 AA or WCAG 2.2 AA. Use when Codex is asked to audit a page/site for accessibility, produce a prioritized list of required accessibility changes, identify WCAG/Axe violations, or perform manual accessibility review with playwright-cli.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# A11y Audit
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
1. Run the Axe audit script first. Keep dependencies local to this skill directory.
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
cd .agents/skills/a11y-audit
|
|
14
|
+
npm run setup
|
|
15
|
+
node scripts/audit.js "https://example.com" --threshold wcag22aa
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If the user does not name a threshold, use `baseline`. Accepted thresholds: `baseline`, `wcag2a`, `wcag2aa`, `wcag21aa`, `wcag22aa`. The script also accepts multiple URLs and `--tags` for custom Axe tag filters.
|
|
19
|
+
|
|
20
|
+
2. Read the generated Markdown report path printed by the script. Use it as the primary source of automated findings and required changes.
|
|
21
|
+
|
|
22
|
+
3. If the report includes `Manual review required`, review those items with `$playwright-cli`. Use browser snapshots, keyboard navigation, screenshots, and targeted page evaluation as needed. Do not guess outcomes for manual-only criteria.
|
|
23
|
+
|
|
24
|
+
4. Return the final audit as a concise remediation list:
|
|
25
|
+
- Automated failures from Axe that must be fixed.
|
|
26
|
+
- Manual-review failures confirmed with `playwright-cli`.
|
|
27
|
+
- Manual checks that could not be completed, with the blocker and exact next action.
|
|
28
|
+
- Threshold tested, URL(s), viewport(s), and report artifact paths.
|
|
29
|
+
|
|
30
|
+
## Script
|
|
31
|
+
|
|
32
|
+
Use `scripts/audit.js` for deterministic collection. It:
|
|
33
|
+
- launches Chromium through Playwright;
|
|
34
|
+
- injects Axe Core from the skill-local dependency;
|
|
35
|
+
- audits desktop and mobile viewports by default;
|
|
36
|
+
- writes `report.md`, `results.json`, and `manual-review.json`;
|
|
37
|
+
- groups issues by impact, rule, WCAG tags, affected nodes, and suggested remediation;
|
|
38
|
+
- lists Axe `incomplete` results and threshold-specific human checks for manual review.
|
|
39
|
+
|
|
40
|
+
Exit code `2` means the audit completed and found violations or manual-review items. Treat it as a findings signal, not a runner failure.
|
|
41
|
+
|
|
42
|
+
Useful options:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
node scripts/audit.js URL [URL ...]
|
|
46
|
+
node scripts/audit.js URL --threshold wcag22aa --out ./a11y-report
|
|
47
|
+
node scripts/audit.js URL --viewport desktop --viewport 390x844
|
|
48
|
+
node scripts/audit.js URL --tags wcag2a,wcag2aa,wcag21aa,wcag22aa
|
|
49
|
+
node scripts/audit.js URL --wait-until networkidle --timeout 45000
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Manual Review With playwright-cli
|
|
53
|
+
|
|
54
|
+
Use `playwright-cli` only after the script flags manual review or the user asks for deeper investigation. Typical checks:
|
|
55
|
+
- Keyboard path: `Tab`, `Shift+Tab`, `Enter`, `Space`, `Escape`; confirm logical order, visible focus, no traps, and operable controls.
|
|
56
|
+
- Screen-reader semantics proxy: inspect snapshots for names, roles, headings, landmarks, labels, and state changes.
|
|
57
|
+
- Visual checks: screenshots for focus indication, responsive reflow, text spacing, content on hover/focus, contrast edge cases, and motion/animation behavior.
|
|
58
|
+
- Forms and dynamic UI: submit invalid states, open menus/dialogs, expand accordions, trigger errors, and verify announcements or visible status messages.
|
|
59
|
+
|
|
60
|
+
Close browser sessions when finished.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "a11y-audit-skill",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Local dependencies for the a11y-audit Codex skill.",
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"audit": "node scripts/audit.js",
|
|
10
|
+
"setup": "bash scripts/setup.sh"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"axe-core": "^4.10.3",
|
|
14
|
+
"playwright": "^1.52.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {}
|
|
17
|
+
}
|
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const SKILL_ROOT = path.resolve(__dirname, '..');
|
|
8
|
+
if (!process.env.PLAYWRIGHT_BROWSERS_PATH) {
|
|
9
|
+
process.env.PLAYWRIGHT_BROWSERS_PATH = path.join(SKILL_ROOT, '.ms-playwright');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HELP = `
|
|
13
|
+
Usage:
|
|
14
|
+
node scripts/audit.js URL [URL ...] [options]
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--threshold <name> baseline | wcag2a | wcag2aa | wcag21aa | wcag22aa
|
|
18
|
+
Default: baseline
|
|
19
|
+
--tags <csv> Custom axe tags, overriding --threshold.
|
|
20
|
+
--viewport <preset|WxH> desktop, mobile, tablet, or e.g. 390x844.
|
|
21
|
+
May be repeated. Default: desktop,mobile
|
|
22
|
+
--out <dir> Output directory. Default: ./a11y-audit-<timestamp>
|
|
23
|
+
--wait-until <state> load | domcontentloaded | networkidle. Default: networkidle
|
|
24
|
+
--timeout <ms> Navigation timeout. Default: 30000
|
|
25
|
+
--include-passes Include passed/inapplicable results in results.json.
|
|
26
|
+
--help Show this help.
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const THRESHOLDS = {
|
|
30
|
+
baseline: null,
|
|
31
|
+
wcag2a: ['wcag2a'],
|
|
32
|
+
wcag2aa: ['wcag2a', 'wcag2aa'],
|
|
33
|
+
wcag21aa: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'],
|
|
34
|
+
wcag22aa: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22a', 'wcag22aa']
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const VIEWPORTS = {
|
|
38
|
+
desktop: { name: 'desktop', width: 1440, height: 1000 },
|
|
39
|
+
mobile: { name: 'mobile', width: 390, height: 844, isMobile: true },
|
|
40
|
+
tablet: { name: 'tablet', width: 768, height: 1024, isMobile: true }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const MANUAL_CHECKS = {
|
|
44
|
+
baseline: [
|
|
45
|
+
{
|
|
46
|
+
id: 'keyboard-operable',
|
|
47
|
+
title: 'Keyboard operation and focus path',
|
|
48
|
+
wcag: ['2.1.1', '2.1.2', '2.4.3', '2.4.7'],
|
|
49
|
+
review: 'Use Tab, Shift+Tab, Enter, Space, and Escape to confirm all interactive elements are reachable, usable, visibly focused, and do not trap focus.'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'meaningful-structure',
|
|
53
|
+
title: 'Meaningful reading and heading order',
|
|
54
|
+
wcag: ['1.3.1', '1.3.2', '2.4.6'],
|
|
55
|
+
review: 'Inspect headings, landmarks, labels, and DOM order against the visual layout and page purpose.'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'dynamic-state-announcements',
|
|
59
|
+
title: 'Dynamic content and status changes',
|
|
60
|
+
wcag: ['4.1.3'],
|
|
61
|
+
review: 'Trigger menus, filters, validation, async loading, dialogs, and notifications; confirm state changes are exposed visibly and semantically.'
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
wcag2a: [
|
|
65
|
+
{
|
|
66
|
+
id: 'non-text-alternatives-quality',
|
|
67
|
+
title: 'Quality of text alternatives',
|
|
68
|
+
wcag: ['1.1.1'],
|
|
69
|
+
review: 'Confirm image, icon, chart, media, and control alternatives communicate equivalent purpose, not just that an alt attribute exists.'
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'media-alternatives',
|
|
73
|
+
title: 'Captions, transcripts, and audio descriptions',
|
|
74
|
+
wcag: ['1.2.1', '1.2.2', '1.2.3'],
|
|
75
|
+
review: 'Inspect prerecorded audio/video for required captions, transcripts, or audio description equivalents.'
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'sensory-and-color',
|
|
79
|
+
title: 'Instructions do not rely only on sensory traits or color',
|
|
80
|
+
wcag: ['1.3.3', '1.4.1'],
|
|
81
|
+
review: 'Check instructions, errors, charts, and required states for text or programmatic cues beyond color, shape, size, sound, or position.'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'keyboard-operable',
|
|
85
|
+
title: 'Keyboard operation and focus path',
|
|
86
|
+
wcag: ['2.1.1', '2.1.2', '2.4.3', '2.4.7'],
|
|
87
|
+
review: 'Use Tab, Shift+Tab, Enter, Space, and Escape to confirm all interactive elements are reachable, usable, visibly focused, and do not trap focus.'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'timing-pausing-flashing',
|
|
91
|
+
title: 'Timing, moving content, and flashing',
|
|
92
|
+
wcag: ['2.2.1', '2.2.2', '2.3.1'],
|
|
93
|
+
review: 'Check time limits, auto-updating regions, moving content, carousels, and flashes for required pause/stop/extend controls and flash safety.'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'meaningful-navigation',
|
|
97
|
+
title: 'Bypass blocks, page title, link purpose, headings, and labels',
|
|
98
|
+
wcag: ['2.4.1', '2.4.2', '2.4.4', '2.4.6'],
|
|
99
|
+
review: 'Confirm repeated navigation can be bypassed and that titles, links, headings, and labels describe purpose in context.'
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'forms-errors-names',
|
|
103
|
+
title: 'Forms, errors, names, roles, and values',
|
|
104
|
+
wcag: ['3.3.1', '3.3.2', '4.1.2'],
|
|
105
|
+
review: 'Submit forms and inspect widgets for clear labels, visible error identification, instructions, names, roles, values, and state changes.'
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
wcag2aa: [
|
|
109
|
+
{
|
|
110
|
+
id: 'live-media-and-audio-description',
|
|
111
|
+
title: 'Live captions and prerecorded audio description',
|
|
112
|
+
wcag: ['1.2.4', '1.2.5'],
|
|
113
|
+
review: 'Inspect live media and prerecorded video for captions and audio description where applicable.'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'orientation-reflow-resize',
|
|
117
|
+
title: 'Orientation, resize, and reflow',
|
|
118
|
+
wcag: ['1.3.4', '1.4.4', '1.4.10'],
|
|
119
|
+
review: 'Resize to 320 CSS px width, zoom to 200%, and rotate viewport; confirm content and functionality remain available without two-dimensional scrolling except where allowed.'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'visual-contrast-and-spacing',
|
|
123
|
+
title: 'Contrast, non-text contrast, and text spacing',
|
|
124
|
+
wcag: ['1.4.3', '1.4.11', '1.4.12'],
|
|
125
|
+
review: 'Check text, focus indicators, icons, component boundaries, charts, and custom text spacing. Verify edge cases Axe cannot compute from CSS or images.'
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: 'hover-focus-content',
|
|
129
|
+
title: 'Content on hover or focus',
|
|
130
|
+
wcag: ['1.4.13'],
|
|
131
|
+
review: 'Open tooltips, popovers, menus, and hover cards; confirm content is dismissible, hoverable, and persistent while needed.'
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'multiple-ways-consistent-navigation',
|
|
135
|
+
title: 'Multiple ways and consistency',
|
|
136
|
+
wcag: ['2.4.5', '3.2.3', '3.2.4'],
|
|
137
|
+
review: 'Confirm pages can be found through more than one method where applicable and repeated navigation/components behave consistently.'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
id: 'error-suggestion-prevention',
|
|
141
|
+
title: 'Error suggestion and prevention',
|
|
142
|
+
wcag: ['3.3.3', '3.3.4'],
|
|
143
|
+
review: 'Test validation and critical submissions for actionable suggestions, review, confirmation, or reversal where required.'
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
wcag22aa: [
|
|
147
|
+
{
|
|
148
|
+
id: 'focus-not-obscured',
|
|
149
|
+
title: 'Focus not obscured',
|
|
150
|
+
wcag: ['2.4.11', '2.4.12'],
|
|
151
|
+
review: 'Tab through sticky headers, cookie banners, dialogs, and overlays; confirm focused controls are not hidden behind other content.'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: 'dragging-target-size',
|
|
155
|
+
title: 'Dragging alternatives and target size',
|
|
156
|
+
wcag: ['2.5.7', '2.5.8'],
|
|
157
|
+
review: 'Check drag/drop and small pointer targets for single-pointer alternatives and minimum target spacing/size exceptions.'
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'consistent-help-redundant-entry-accessible-auth',
|
|
161
|
+
title: 'Help, redundant entry, and accessible authentication',
|
|
162
|
+
wcag: ['3.2.6', '3.3.7', '3.3.8'],
|
|
163
|
+
review: 'Inspect forms, login, checkout, and support paths for consistent help placement, no unnecessary repeated entry, and authentication without cognitive-function tests unless alternatives exist.'
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
function parseArgs(argv) {
|
|
169
|
+
const opts = {
|
|
170
|
+
threshold: 'baseline',
|
|
171
|
+
viewports: [],
|
|
172
|
+
waitUntil: 'networkidle',
|
|
173
|
+
timeout: 30000,
|
|
174
|
+
includePasses: false,
|
|
175
|
+
urls: []
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
179
|
+
const arg = argv[i];
|
|
180
|
+
if (arg === '--help' || arg === '-h') {
|
|
181
|
+
opts.help = true;
|
|
182
|
+
} else if (arg === '--threshold') {
|
|
183
|
+
opts.threshold = (argv[++i] || '').toLowerCase();
|
|
184
|
+
} else if (arg === '--tags') {
|
|
185
|
+
opts.tags = (argv[++i] || '').split(',').map((tag) => tag.trim()).filter(Boolean);
|
|
186
|
+
} else if (arg === '--viewport') {
|
|
187
|
+
opts.viewports.push(parseViewport(argv[++i]));
|
|
188
|
+
} else if (arg === '--out') {
|
|
189
|
+
opts.out = argv[++i];
|
|
190
|
+
} else if (arg === '--wait-until') {
|
|
191
|
+
opts.waitUntil = argv[++i];
|
|
192
|
+
} else if (arg === '--timeout') {
|
|
193
|
+
opts.timeout = Number(argv[++i]);
|
|
194
|
+
} else if (arg === '--include-passes') {
|
|
195
|
+
opts.includePasses = true;
|
|
196
|
+
} else if (arg.startsWith('--')) {
|
|
197
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
198
|
+
} else {
|
|
199
|
+
opts.urls.push(arg);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (!opts.viewports.length) {
|
|
204
|
+
opts.viewports = [VIEWPORTS.desktop, VIEWPORTS.mobile];
|
|
205
|
+
}
|
|
206
|
+
if (!opts.out) {
|
|
207
|
+
opts.out = path.resolve(process.cwd(), `a11y-audit-${timestamp()}`);
|
|
208
|
+
} else {
|
|
209
|
+
opts.out = path.resolve(process.cwd(), opts.out);
|
|
210
|
+
}
|
|
211
|
+
if (!Number.isFinite(opts.timeout) || opts.timeout <= 0) {
|
|
212
|
+
throw new Error('--timeout must be a positive number');
|
|
213
|
+
}
|
|
214
|
+
if (!THRESHOLDS.hasOwnProperty(opts.threshold)) {
|
|
215
|
+
throw new Error(`Unsupported threshold "${opts.threshold}". Use one of: ${Object.keys(THRESHOLDS).join(', ')}`);
|
|
216
|
+
}
|
|
217
|
+
if (!['load', 'domcontentloaded', 'networkidle'].includes(opts.waitUntil)) {
|
|
218
|
+
throw new Error('--wait-until must be load, domcontentloaded, or networkidle');
|
|
219
|
+
}
|
|
220
|
+
return opts;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function parseViewport(value) {
|
|
224
|
+
if (!value) throw new Error('--viewport requires a value');
|
|
225
|
+
if (VIEWPORTS[value]) return VIEWPORTS[value];
|
|
226
|
+
const match = /^(\d{2,5})x(\d{2,5})$/i.exec(value);
|
|
227
|
+
if (!match) throw new Error(`Unsupported viewport "${value}". Use desktop, mobile, tablet, or WIDTHxHEIGHT.`);
|
|
228
|
+
return { name: `${match[1]}x${match[2]}`, width: Number(match[1]), height: Number(match[2]) };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function main() {
|
|
232
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
233
|
+
if (opts.help) {
|
|
234
|
+
process.stdout.write(HELP);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (!opts.urls.length) {
|
|
238
|
+
throw new Error('Provide at least one URL.');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const { chromium } = loadDependency('playwright');
|
|
242
|
+
const axe = loadDependency('axe-core');
|
|
243
|
+
fs.mkdirSync(opts.out, { recursive: true });
|
|
244
|
+
|
|
245
|
+
const browser = await launchChromium(chromium);
|
|
246
|
+
const results = [];
|
|
247
|
+
try {
|
|
248
|
+
for (const url of opts.urls) {
|
|
249
|
+
for (const viewport of opts.viewports) {
|
|
250
|
+
results.push(await auditPage({ browser, axe, url, viewport, opts }));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} finally {
|
|
254
|
+
await browser.close();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const report = buildReport(results, opts);
|
|
258
|
+
const json = {
|
|
259
|
+
metadata: metadata(opts),
|
|
260
|
+
summary: summarize(results),
|
|
261
|
+
results: opts.includePasses ? results : results.map(stripLargeResultFields),
|
|
262
|
+
manualReview: buildManualReview(results, opts)
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
fs.writeFileSync(path.join(opts.out, 'results.json'), `${JSON.stringify(json, null, 2)}\n`);
|
|
266
|
+
fs.writeFileSync(path.join(opts.out, 'manual-review.json'), `${JSON.stringify(json.manualReview, null, 2)}\n`);
|
|
267
|
+
fs.writeFileSync(path.join(opts.out, 'report.md'), report);
|
|
268
|
+
|
|
269
|
+
const summary = summarize(results);
|
|
270
|
+
console.log(`Accessibility audit complete: ${opts.out}`);
|
|
271
|
+
console.log(`Violations: ${summary.violations}; manual review items: ${json.manualReview.items.length}; URLs: ${opts.urls.length}; viewports: ${opts.viewports.length}`);
|
|
272
|
+
console.log(`Report: ${path.join(opts.out, 'report.md')}`);
|
|
273
|
+
|
|
274
|
+
if (summary.violations > 0 || json.manualReview.items.length > 0) {
|
|
275
|
+
process.exitCode = 2;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function loadDependency(name) {
|
|
280
|
+
try {
|
|
281
|
+
return require(name);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
if (error && error.code === 'MODULE_NOT_FOUND') {
|
|
284
|
+
throw new Error(`Missing dependency "${name}". Run "cd .agents/skills/a11y-audit && npm install" first.`);
|
|
285
|
+
}
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function launchChromium(chromium) {
|
|
291
|
+
try {
|
|
292
|
+
return await chromium.launch({
|
|
293
|
+
channel: 'chromium',
|
|
294
|
+
chromiumSandbox: false,
|
|
295
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
|
296
|
+
});
|
|
297
|
+
} catch (error) {
|
|
298
|
+
const message = String(error && error.message ? error.message : error);
|
|
299
|
+
if (message.includes("Executable doesn't exist") || message.includes('Please run the following command')) {
|
|
300
|
+
throw new Error(`Missing Playwright Chromium browser in ${process.env.PLAYWRIGHT_BROWSERS_PATH}. Run "cd .agents/skills/a11y-audit && npm run setup" first.`);
|
|
301
|
+
}
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function auditPage({ browser, axe, url, viewport, opts }) {
|
|
307
|
+
const context = await browser.newContext({
|
|
308
|
+
viewport: { width: viewport.width, height: viewport.height },
|
|
309
|
+
isMobile: Boolean(viewport.isMobile)
|
|
310
|
+
});
|
|
311
|
+
const page = await context.newPage();
|
|
312
|
+
const startedAt = new Date().toISOString();
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
await page.goto(url, { waitUntil: opts.waitUntil, timeout: opts.timeout });
|
|
316
|
+
await page.addScriptTag({ content: axe.source });
|
|
317
|
+
|
|
318
|
+
const axeOptions = {
|
|
319
|
+
resultTypes: opts.includePasses
|
|
320
|
+
? ['violations', 'incomplete', 'passes', 'inapplicable']
|
|
321
|
+
: ['violations', 'incomplete']
|
|
322
|
+
};
|
|
323
|
+
const tags = opts.tags || THRESHOLDS[opts.threshold];
|
|
324
|
+
if (tags) {
|
|
325
|
+
axeOptions.runOnly = { type: 'tag', values: tags };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const axeResults = await page.evaluate(async (options) => {
|
|
329
|
+
return window.axe.run(document, options);
|
|
330
|
+
}, axeOptions);
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
url,
|
|
334
|
+
finalUrl: page.url(),
|
|
335
|
+
title: await page.title(),
|
|
336
|
+
viewport: viewport.name,
|
|
337
|
+
viewportSize: { width: viewport.width, height: viewport.height },
|
|
338
|
+
startedAt,
|
|
339
|
+
completedAt: new Date().toISOString(),
|
|
340
|
+
threshold: opts.threshold,
|
|
341
|
+
tags: tags || ['axe-default'],
|
|
342
|
+
error: null,
|
|
343
|
+
axe: axeResults
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return {
|
|
347
|
+
url,
|
|
348
|
+
finalUrl: page.url(),
|
|
349
|
+
title: await page.title().catch(() => ''),
|
|
350
|
+
viewport: viewport.name,
|
|
351
|
+
viewportSize: { width: viewport.width, height: viewport.height },
|
|
352
|
+
startedAt,
|
|
353
|
+
completedAt: new Date().toISOString(),
|
|
354
|
+
threshold: opts.threshold,
|
|
355
|
+
tags: opts.tags || THRESHOLDS[opts.threshold] || ['axe-default'],
|
|
356
|
+
error: String(error && error.stack ? error.stack : error),
|
|
357
|
+
axe: { violations: [], incomplete: [], passes: [], inapplicable: [] }
|
|
358
|
+
};
|
|
359
|
+
} finally {
|
|
360
|
+
await context.close();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function metadata(opts) {
|
|
365
|
+
return {
|
|
366
|
+
generatedAt: new Date().toISOString(),
|
|
367
|
+
threshold: opts.threshold,
|
|
368
|
+
tags: opts.tags || THRESHOLDS[opts.threshold] || ['axe-default'],
|
|
369
|
+
urls: opts.urls,
|
|
370
|
+
viewports: opts.viewports.map((viewport) => ({
|
|
371
|
+
name: viewport.name,
|
|
372
|
+
width: viewport.width,
|
|
373
|
+
height: viewport.height
|
|
374
|
+
}))
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function summarize(results) {
|
|
379
|
+
const summary = {
|
|
380
|
+
violations: 0,
|
|
381
|
+
incomplete: 0,
|
|
382
|
+
pagesWithErrors: 0,
|
|
383
|
+
impacts: {}
|
|
384
|
+
};
|
|
385
|
+
for (const result of results) {
|
|
386
|
+
if (result.error) summary.pagesWithErrors += 1;
|
|
387
|
+
for (const violation of result.axe.violations || []) {
|
|
388
|
+
summary.violations += violation.nodes.length || 1;
|
|
389
|
+
const impact = violation.impact || 'unknown';
|
|
390
|
+
summary.impacts[impact] = (summary.impacts[impact] || 0) + (violation.nodes.length || 1);
|
|
391
|
+
}
|
|
392
|
+
for (const item of result.axe.incomplete || []) {
|
|
393
|
+
summary.incomplete += item.nodes.length || 1;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return summary;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function stripLargeResultFields(result) {
|
|
400
|
+
const clone = JSON.parse(JSON.stringify(result));
|
|
401
|
+
delete clone.axe.passes;
|
|
402
|
+
delete clone.axe.inapplicable;
|
|
403
|
+
return clone;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function buildManualReview(results, opts) {
|
|
407
|
+
const items = [];
|
|
408
|
+
|
|
409
|
+
for (const result of results) {
|
|
410
|
+
if (result.error) {
|
|
411
|
+
items.push({
|
|
412
|
+
type: 'page-error',
|
|
413
|
+
url: result.url,
|
|
414
|
+
viewport: result.viewport,
|
|
415
|
+
title: 'Audit could not complete',
|
|
416
|
+
detail: result.error
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
for (const incomplete of result.axe.incomplete || []) {
|
|
421
|
+
for (const node of incomplete.nodes || [{}]) {
|
|
422
|
+
items.push({
|
|
423
|
+
type: 'axe-incomplete',
|
|
424
|
+
url: result.finalUrl || result.url,
|
|
425
|
+
viewport: result.viewport,
|
|
426
|
+
ruleId: incomplete.id,
|
|
427
|
+
impact: incomplete.impact || 'unknown',
|
|
428
|
+
title: incomplete.help,
|
|
429
|
+
wcag: wcagTags(incomplete.tags),
|
|
430
|
+
target: node.target || [],
|
|
431
|
+
detail: node.failureSummary || incomplete.description,
|
|
432
|
+
helpUrl: incomplete.helpUrl
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const manualChecks = checksForThreshold(opts.threshold);
|
|
439
|
+
for (const check of manualChecks) {
|
|
440
|
+
items.push({
|
|
441
|
+
type: 'threshold-manual-check',
|
|
442
|
+
threshold: opts.threshold,
|
|
443
|
+
id: check.id,
|
|
444
|
+
title: check.title,
|
|
445
|
+
wcag: check.wcag,
|
|
446
|
+
detail: check.review
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return { items };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function checksForThreshold(threshold) {
|
|
454
|
+
const ordered = ['baseline'];
|
|
455
|
+
if (['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'].includes(threshold)) ordered.push('wcag2a');
|
|
456
|
+
if (['wcag2aa', 'wcag21aa', 'wcag22aa'].includes(threshold)) ordered.push('wcag2aa');
|
|
457
|
+
if (['wcag22aa'].includes(threshold)) ordered.push('wcag22aa');
|
|
458
|
+
|
|
459
|
+
const seen = new Set();
|
|
460
|
+
const checks = [];
|
|
461
|
+
for (const group of ordered) {
|
|
462
|
+
for (const check of MANUAL_CHECKS[group]) {
|
|
463
|
+
const key = `${check.id}:${check.wcag.join(',')}`;
|
|
464
|
+
if (!seen.has(key)) {
|
|
465
|
+
checks.push(check);
|
|
466
|
+
seen.add(key);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return checks;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function buildReport(results, opts) {
|
|
474
|
+
const lines = [];
|
|
475
|
+
const summary = summarize(results);
|
|
476
|
+
const manual = buildManualReview(results, opts);
|
|
477
|
+
const meta = metadata(opts);
|
|
478
|
+
|
|
479
|
+
lines.push('# A11y Audit Report', '');
|
|
480
|
+
lines.push(`Generated: ${meta.generatedAt}`);
|
|
481
|
+
lines.push(`Threshold: ${opts.threshold}`);
|
|
482
|
+
lines.push(`Axe tags: ${meta.tags.join(', ')}`);
|
|
483
|
+
lines.push(`URLs: ${opts.urls.join(', ')}`);
|
|
484
|
+
lines.push(`Viewports: ${meta.viewports.map((v) => `${v.name} (${v.width}x${v.height})`).join(', ')}`, '');
|
|
485
|
+
|
|
486
|
+
lines.push('## Summary', '');
|
|
487
|
+
lines.push(`- Violating node instances: ${summary.violations}`);
|
|
488
|
+
lines.push(`- Axe incomplete/manual node instances: ${summary.incomplete}`);
|
|
489
|
+
lines.push(`- Page audit errors: ${summary.pagesWithErrors}`);
|
|
490
|
+
lines.push(`- Manual review required: ${manual.items.length > 0 ? 'yes' : 'no'}`);
|
|
491
|
+
lines.push('');
|
|
492
|
+
|
|
493
|
+
if (Object.keys(summary.impacts).length) {
|
|
494
|
+
lines.push('Impact counts:');
|
|
495
|
+
for (const [impact, count] of Object.entries(sortImpacts(summary.impacts))) {
|
|
496
|
+
lines.push(`- ${impact}: ${count}`);
|
|
497
|
+
}
|
|
498
|
+
lines.push('');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
lines.push('## Required Changes From Axe', '');
|
|
502
|
+
const violations = collectViolations(results);
|
|
503
|
+
if (!violations.length) {
|
|
504
|
+
lines.push('No automated Axe violations were found for the selected threshold and viewport set.', '');
|
|
505
|
+
} else {
|
|
506
|
+
for (const group of violations) {
|
|
507
|
+
lines.push(`### ${group.impactLabel} ${group.id}: ${group.help}`);
|
|
508
|
+
lines.push('');
|
|
509
|
+
lines.push(`- WCAG/tags: ${group.tags.length ? group.tags.join(', ') : 'n/a'}`);
|
|
510
|
+
lines.push(`- Help: ${group.helpUrl || 'n/a'}`);
|
|
511
|
+
lines.push(`- Required change: ${group.description}`);
|
|
512
|
+
lines.push(`- Affected instances: ${group.nodes.length}`);
|
|
513
|
+
lines.push('');
|
|
514
|
+
for (const node of group.nodes) {
|
|
515
|
+
lines.push(` - ${node.url} [${node.viewport}] target: \`${formatTarget(node.target)}\``);
|
|
516
|
+
if (node.failureSummary) lines.push(` ${oneLine(node.failureSummary)}`);
|
|
517
|
+
}
|
|
518
|
+
lines.push('');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
lines.push('## Manual Review Required', '');
|
|
523
|
+
if (!manual.items.length) {
|
|
524
|
+
lines.push('No manual-review items were generated.', '');
|
|
525
|
+
} else {
|
|
526
|
+
const byType = groupBy(manual.items, (item) => item.type);
|
|
527
|
+
for (const [type, items] of Object.entries(byType)) {
|
|
528
|
+
lines.push(`### ${titleCase(type)}`, '');
|
|
529
|
+
for (const item of items) {
|
|
530
|
+
lines.push(`- ${item.title || item.id}`);
|
|
531
|
+
if (item.wcag && item.wcag.length) lines.push(` WCAG: ${item.wcag.join(', ')}`);
|
|
532
|
+
if (item.url) lines.push(` URL: ${item.url} [${item.viewport || 'n/a'}]`);
|
|
533
|
+
if (item.target && item.target.length) lines.push(` Target: \`${formatTarget(item.target)}\``);
|
|
534
|
+
if (item.helpUrl) lines.push(` Help: ${item.helpUrl}`);
|
|
535
|
+
lines.push(` Review: ${oneLine(item.detail || '')}`);
|
|
536
|
+
}
|
|
537
|
+
lines.push('');
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
lines.push('## Raw Artifacts', '');
|
|
542
|
+
lines.push('- `results.json`: Axe results and metadata.');
|
|
543
|
+
lines.push('- `manual-review.json`: Items that require `playwright-cli` review.');
|
|
544
|
+
lines.push('');
|
|
545
|
+
|
|
546
|
+
return `${lines.join('\n')}\n`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function collectViolations(results) {
|
|
550
|
+
const groups = new Map();
|
|
551
|
+
|
|
552
|
+
for (const result of results) {
|
|
553
|
+
for (const violation of result.axe.violations || []) {
|
|
554
|
+
const key = [violation.id, violation.impact || 'unknown'].join('::');
|
|
555
|
+
if (!groups.has(key)) {
|
|
556
|
+
groups.set(key, {
|
|
557
|
+
id: violation.id,
|
|
558
|
+
impact: violation.impact || 'unknown',
|
|
559
|
+
impactLabel: `[${(violation.impact || 'unknown').toUpperCase()}]`,
|
|
560
|
+
help: violation.help,
|
|
561
|
+
helpUrl: violation.helpUrl,
|
|
562
|
+
description: violation.description,
|
|
563
|
+
tags: wcagTags(violation.tags),
|
|
564
|
+
nodes: []
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
const group = groups.get(key);
|
|
568
|
+
for (const node of violation.nodes || []) {
|
|
569
|
+
group.nodes.push({
|
|
570
|
+
url: result.finalUrl || result.url,
|
|
571
|
+
viewport: result.viewport,
|
|
572
|
+
target: node.target || [],
|
|
573
|
+
failureSummary: node.failureSummary || ''
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return Array.from(groups.values()).sort((a, b) => {
|
|
580
|
+
return impactRank(a.impact) - impactRank(b.impact) || a.id.localeCompare(b.id);
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function wcagTags(tags = []) {
|
|
585
|
+
return tags.filter((tag) => /^(wcag|section508|best-practice)/.test(tag));
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function impactRank(impact) {
|
|
589
|
+
return { critical: 0, serious: 1, moderate: 2, minor: 3, unknown: 4 }[impact] ?? 4;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function sortImpacts(impacts) {
|
|
593
|
+
return Object.fromEntries(Object.entries(impacts).sort(([a], [b]) => impactRank(a) - impactRank(b)));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function groupBy(items, keyFn) {
|
|
597
|
+
return items.reduce((groups, item) => {
|
|
598
|
+
const key = keyFn(item);
|
|
599
|
+
groups[key] = groups[key] || [];
|
|
600
|
+
groups[key].push(item);
|
|
601
|
+
return groups;
|
|
602
|
+
}, {});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function formatTarget(target) {
|
|
606
|
+
return Array.isArray(target) ? target.join(', ') : String(target || 'n/a');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function oneLine(value) {
|
|
610
|
+
return String(value || '').replace(/\s+/g, ' ').trim();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function titleCase(value) {
|
|
614
|
+
return String(value).replace(/-/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase());
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function timestamp() {
|
|
618
|
+
return new Date().toISOString().replace(/[:.]/g, '-');
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
main().catch((error) => {
|
|
622
|
+
console.error(error.message || error);
|
|
623
|
+
process.exit(1);
|
|
624
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-validation
|
|
3
|
+
description: Before completing a task validate frontend or template changes in a real browser with the Playwright-CLI skill. Use when an agent has changed anything that will change appearance or functionality.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Browser Validation
|
|
8
|
+
|
|
9
|
+
## Required Dependency
|
|
10
|
+
|
|
11
|
+
If no Playwright-CLI skill is available, stop and ask the user to install the required Playwright-CLI skill before browser validation can run.
|
|
12
|
+
|
|
13
|
+
## Configuration
|
|
14
|
+
|
|
15
|
+
Read `config.toml` in this skill folder and use `dev_server_url` as the target application URL.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dev_server_url = "http://localhost:8000"
|
package/skills/scry/SKILL.md
CHANGED
|
@@ -68,6 +68,75 @@ Useful options:
|
|
|
68
68
|
- `--path=/about --path=/products/example` overrides sitemap paths for the current run and writes them to `paths.json`.
|
|
69
69
|
- `--wait-until=networkidle` changes navigation waiting; default is `domcontentloaded`.
|
|
70
70
|
|
|
71
|
+
## Temporary Playwright Setup
|
|
72
|
+
|
|
73
|
+
Some theme repositories do not have a local `package.json`, `node_modules/`, or `@playwright/test` install. In that case, keep Playwright out of the repository and install it temporarily under `/tmp`.
|
|
74
|
+
|
|
75
|
+
Create the temporary install directory:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mkdir -p /tmp/scry-playwright
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Install Playwright Test there:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install --prefix /tmp/scry-playwright @playwright/test
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Install the browser needed for the run:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
/tmp/scry-playwright/node_modules/.bin/playwright install chromium
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Generate the scry config and paths from the repository root:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python3 .agents/skills/scry/scripts/scry.py "$LIVE_URL" "$PREVIEW_URL" --refresh --regenerate-paths --no-ui --browser=chromium
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
If `paths.json` is large, inspect it and trim duplicate template paths before the full comparison.
|
|
100
|
+
|
|
101
|
+
Run the generated suite with the temporary Playwright install:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
env NODE_PATH=/tmp/scry-playwright/node_modules \
|
|
105
|
+
SCRY_CONFIG="$PWD/.agents/skills/scry/config.json" \
|
|
106
|
+
SCRY_PATHS="$PWD/.agents/skills/scry/paths.json" \
|
|
107
|
+
SCRY_BASELINE_DIR="$PWD/.agents/skills/scry/baselines" \
|
|
108
|
+
SCRY_REFRESH=1 \
|
|
109
|
+
/tmp/scry-playwright/node_modules/.bin/playwright test \
|
|
110
|
+
--config "$PWD/.agents/skills/scry/runtime/playwright.config.cjs"
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Cleanup
|
|
114
|
+
|
|
115
|
+
After a temporary Playwright run, remove transient report output from the repository root:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
rm -rf playwright-report test-results
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Remove the temporary dependency install:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
rm -rf /tmp/scry-playwright
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Only remove downloaded Playwright browsers when intentionally resetting local tooling:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
rm -rf ~/.cache/ms-playwright
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Keep these scry artifacts unless intentionally resetting the comparison state:
|
|
134
|
+
|
|
135
|
+
- `.agents/skills/scry/config.json`
|
|
136
|
+
- `.agents/skills/scry/paths.json`
|
|
137
|
+
- `.agents/skills/scry/runtime/`
|
|
138
|
+
- `.agents/skills/scry/baselines/`
|
|
139
|
+
|
|
71
140
|
## Playwright CLI
|
|
72
141
|
|
|
73
142
|
If the comparison fails or needs manual investigation, use `$playwright-cli` after the UI run to inspect pages directly.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shopify-cli
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Shopify CLI workflows for theme development. Use when the user needs to run or explain Shopify theme commands, especially starting the theme development server, previewing local Liquid/theme changes, listing themes, pushing or pulling theme files, duplicating themes, renaming themes, or checking other Shopify CLI theme command syntax.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Shopify CLI
|
|
8
|
+
|
|
9
|
+
Official docs: https://shopify.dev/docs/api/shopify-cli/theme
|
|
10
|
+
|
|
11
|
+
## First Step: Store Config
|
|
12
|
+
|
|
13
|
+
Read `config.toml` in this skill folder before running any Shopify theme command that talks to a store.
|
|
14
|
+
|
|
15
|
+
- Use the configured `store` value as the `--store` argument every time.
|
|
16
|
+
- If `store` is empty or missing, stop and ask the user for the correct Shopify store value before running commands.
|
|
17
|
+
- Accept either the store prefix or the full `.myshopify.com` host. Do not rely on Shopify CLI's cached last-used store, because it can point at a different repo's store.
|
|
18
|
+
|
|
19
|
+
## Development Server
|
|
20
|
+
|
|
21
|
+
Primary workflow:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
shopify theme dev --store <configured-store>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use from the Shopify theme root, where the standard theme directories such as `layout/`, `templates/`, `sections/`, `snippets/`, `assets/`, `config/`, and `locales/` are available.
|
|
28
|
+
|
|
29
|
+
Common options:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
shopify theme dev --store <configured-store> --port 9292
|
|
33
|
+
shopify theme dev --store <configured-store> --host 0.0.0.0 --port 9292
|
|
34
|
+
shopify theme dev --store <configured-store> --theme-editor-sync
|
|
35
|
+
shopify theme dev --store <configured-store> --only "sections/*.liquid"
|
|
36
|
+
shopify theme dev --store <configured-store> --ignore "config/settings_data.json"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Safety rules:
|
|
40
|
+
|
|
41
|
+
- Do not use `--allow-live` for development server work unless the user explicitly requests live-theme development after acknowledging the risk.
|
|
42
|
+
- Prefer a development theme or unpublished theme for previewing.
|
|
43
|
+
- If the command prompts for authentication, let the user complete the browser login or provide the approved Theme Access token flow.
|
|
44
|
+
|
|
45
|
+
## Progressive Disclosure
|
|
46
|
+
|
|
47
|
+
Read only the reference file needed for the user's task:
|
|
48
|
+
|
|
49
|
+
- `references/theme-management.md`: list, push, pull, duplicate, and rename. Use this for the second-most common workflow group.
|
|
50
|
+
- `references/other-theme-commands.md`: check, console, delete, info, init, language-server, metafields pull, open, package, preview, profile, publish, and share.
|
|
51
|
+
|
|
52
|
+
## Global Safety
|
|
53
|
+
|
|
54
|
+
- Never push to the live theme. Do not provide live-push command syntax.
|
|
55
|
+
- Never publish a theme or rename/delete a live theme unless the user explicitly asks for that exact production action.
|
|
56
|
+
- Prefer `--json` for commands whose output will be parsed by automation.
|
|
57
|
+
- Prefer explicit `--theme <id-or-name>` when the user has identified the target theme; otherwise expect Shopify CLI to prompt.
|
|
58
|
+
- Before destructive or overwriting commands, identify the store and target theme in the user-facing update.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Other Shopify Theme Commands
|
|
2
|
+
|
|
3
|
+
Use this reference for Shopify CLI theme commands outside the primary dev-server and management workflows.
|
|
4
|
+
|
|
5
|
+
Official command index: https://shopify.dev/docs/api/shopify-cli/theme
|
|
6
|
+
|
|
7
|
+
Read `../config.toml` first for commands that talk to a store, and always pass `--store <configured-store>` when the command supports it.
|
|
8
|
+
|
|
9
|
+
## Local Analysis And Tooling
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
shopify theme check
|
|
13
|
+
shopify theme check --path <theme-path>
|
|
14
|
+
shopify theme console
|
|
15
|
+
shopify theme language-server
|
|
16
|
+
shopify theme package
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- `theme check`: run Theme Check for Liquid/theme best practices and errors.
|
|
20
|
+
- `theme console`: start the Liquid REPL.
|
|
21
|
+
- `theme language-server`: start the theme language server.
|
|
22
|
+
- `theme package`: create a ZIP package from local theme files.
|
|
23
|
+
|
|
24
|
+
## Store And Theme Information
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
shopify theme info --store <configured-store>
|
|
28
|
+
shopify theme open --store <configured-store>
|
|
29
|
+
shopify theme open --store <configured-store> --theme <theme-id-or-name>
|
|
30
|
+
shopify theme share --store <configured-store>
|
|
31
|
+
shopify theme profile --store <configured-store> --url <path-or-url>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `theme info`: show theme environment information, including current store.
|
|
35
|
+
- `theme open`: return preview/admin links for a theme.
|
|
36
|
+
- `theme share`: upload as a new unpublished theme and return a shareable preview.
|
|
37
|
+
- `theme profile`: profile Liquid rendering on a page.
|
|
38
|
+
|
|
39
|
+
## Creating Or Bootstrapping
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
shopify theme init <directory>
|
|
43
|
+
shopify theme init <directory> --clone-url <git-url>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use `theme init` to clone a starting theme into a local directory.
|
|
47
|
+
|
|
48
|
+
## Metafields
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
shopify theme metafields pull --store <configured-store>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use this to retrieve Shopify Admin metafields for theme development.
|
|
55
|
+
|
|
56
|
+
## Preview Overrides
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
shopify theme preview --store <configured-store> --theme <theme-id-or-name> --overrides <overrides-file>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Use this to apply JSON overrides to a theme preview.
|
|
63
|
+
|
|
64
|
+
## Destructive Or Production Commands
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
shopify theme delete --store <configured-store> --theme <theme-id-or-name>
|
|
68
|
+
shopify theme publish --store <configured-store> --theme <theme-id-or-name>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `theme delete`: destructive. Require explicit user confirmation for the exact store and theme.
|
|
72
|
+
- `theme publish`: production-impacting. Do not run unless the user explicitly asks to publish the exact theme.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Shopify Theme Management
|
|
2
|
+
|
|
3
|
+
Use this reference for listing, pushing, pulling, duplicating, and renaming themes.
|
|
4
|
+
|
|
5
|
+
Official docs:
|
|
6
|
+
|
|
7
|
+
- https://shopify.dev/docs/api/shopify-cli/theme/theme-list
|
|
8
|
+
- https://shopify.dev/docs/api/shopify-cli/theme/theme-push
|
|
9
|
+
- https://shopify.dev/docs/api/shopify-cli/theme/theme-pull
|
|
10
|
+
- https://shopify.dev/docs/api/shopify-cli/theme/theme-duplicate
|
|
11
|
+
- https://shopify.dev/docs/api/shopify-cli/theme/theme-rename
|
|
12
|
+
|
|
13
|
+
Always read `../config.toml` first and pass `--store <configured-store>`. If the store is not configured, ask the user before running anything. Never use Shopify CLI's cached last store.
|
|
14
|
+
|
|
15
|
+
## List Themes
|
|
16
|
+
|
|
17
|
+
Purpose: list themes in the configured store with IDs and roles.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
shopify theme list --store <configured-store>
|
|
21
|
+
shopify theme list --store <configured-store> --json
|
|
22
|
+
shopify theme list --store <configured-store> --role unpublished
|
|
23
|
+
shopify theme list --store <configured-store> --name "<partial-name>"
|
|
24
|
+
shopify theme list --store <configured-store> --id <theme-id>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Use `--json` when another command needs a theme ID.
|
|
28
|
+
|
|
29
|
+
## Push Theme Files
|
|
30
|
+
|
|
31
|
+
Purpose: upload local theme files. This can overwrite remote theme files, so name the target store and theme before running.
|
|
32
|
+
|
|
33
|
+
Allowed patterns:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
shopify theme push --store <configured-store> --unpublished --json
|
|
37
|
+
shopify theme push --store <configured-store> --theme <theme-id-or-name> --strict
|
|
38
|
+
shopify theme push --store <configured-store> --theme <theme-id-or-name> --nodelete
|
|
39
|
+
shopify theme push --store <configured-store> --theme <theme-id-or-name> --only "sections/*.liquid"
|
|
40
|
+
shopify theme push --store <configured-store> --theme <theme-id-or-name> --ignore "config/settings_data.json"
|
|
41
|
+
shopify theme push --store <configured-store> --development
|
|
42
|
+
shopify theme push --store <configured-store> --development-context <context-name>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Forbidden:
|
|
46
|
+
|
|
47
|
+
- Do not push to the live theme.
|
|
48
|
+
- Do not use `--live`, `--publish`, or `--allow-live`.
|
|
49
|
+
- Do not provide command syntax for live pushes.
|
|
50
|
+
|
|
51
|
+
Notes:
|
|
52
|
+
|
|
53
|
+
- If no theme is specified, Shopify CLI prompts the user to choose a target theme.
|
|
54
|
+
- `--unpublished` creates a new unpublished theme.
|
|
55
|
+
- `--strict` requires Theme Check to pass without errors before pushing.
|
|
56
|
+
- `--nodelete` avoids deleting remote files that are absent locally.
|
|
57
|
+
|
|
58
|
+
## Pull Theme Files
|
|
59
|
+
|
|
60
|
+
Purpose: retrieve theme files from Shopify. Pulling can overwrite local files.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
shopify theme pull --store <configured-store>
|
|
64
|
+
shopify theme pull --store <configured-store> --theme <theme-id-or-name>
|
|
65
|
+
shopify theme pull --store <configured-store> --development
|
|
66
|
+
shopify theme pull --store <configured-store> --nodelete
|
|
67
|
+
shopify theme pull --store <configured-store> --only "templates/*.json"
|
|
68
|
+
shopify theme pull --store <configured-store> --ignore "config/settings_data.json"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Avoid pulling the live theme unless the user explicitly asks for it and understands that local files may be overwritten.
|
|
72
|
+
|
|
73
|
+
## Duplicate Themes
|
|
74
|
+
|
|
75
|
+
Purpose: duplicate an existing remote theme. Shopify CLI prompts if no target theme is supplied.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
shopify theme duplicate --store <configured-store>
|
|
79
|
+
shopify theme duplicate --store <configured-store> --theme <theme-id-or-name> --name "<new-theme-name>"
|
|
80
|
+
shopify theme duplicate --store <configured-store> --theme <theme-id-or-name> --name "<new-theme-name>" --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Use `--force` only when the user has already confirmed the exact source theme, because it suppresses prompts and confirmations.
|
|
84
|
+
|
|
85
|
+
## Rename Themes
|
|
86
|
+
|
|
87
|
+
Purpose: rename a remote theme.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
shopify theme rename --store <configured-store>
|
|
91
|
+
shopify theme rename --store <configured-store> --theme <theme-id-or-name> --name "<new-theme-name>"
|
|
92
|
+
shopify theme rename --store <configured-store> --development --name "<new-theme-name>"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Do not rename the live theme unless the user explicitly asks for that production change.
|