td-ai-tools 1.2.0 → 1.2.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/package.json +1 -1
- package/skills/README.md +3 -0
- package/skills/client-overview/SKILL.md +93 -0
- package/skills/client-overview/agents/openai.yaml +4 -0
- package/skills/client-overview/scripts/branch_client_overview_context.py +317 -0
- package/skills/debugging-ios-webkit/SKILL.md +60 -0
- package/skills/debugging-ios-webkit/references/device.md +62 -0
- package/skills/debugging-ios-webkit/references/playwright.md +60 -0
- package/skills/debugging-ios-webkit/references/simulator.md +76 -0
- package/skills/debugging-ios-webkit/scripts/device_eval.py +62 -0
- package/skills/debugging-ios-webkit/scripts/device_experiment.py +99 -0
- package/skills/debugging-ios-webkit/scripts/device_snapshot.py +62 -0
- package/skills/record-changes/SKILL.md +3 -3
- package/skills/shopify-lint/SKILL.md +50 -0
- package/skills/shopify-lint/agents/openai.yaml +4 -0
- package/skills/shopify-lint/scripts/setup.sh +60 -0
- package/skills/shopify-lint/scripts/shopify_lint.py +259 -0
- package/skills/shopify-lint/tests/test_shopify_lint.py +137 -0
- package/skills/shopify-lint/theme-check-theory/.theme-check.example.yml +14 -0
- package/skills/shopify-lint/theme-check-theory/README.md +123 -0
- package/skills/shopify-lint/theme-check-theory/configs/recommended.yml +8 -0
- package/skills/shopify-lint/theme-check-theory/package-lock.json +1947 -0
- package/skills/shopify-lint/theme-check-theory/package.json +44 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.test.ts +198 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unguarded-text-setting.ts +143 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.test.ts +137 -0
- package/skills/shopify-lint/theme-check-theory/src/checks/unused-section-settings.ts +64 -0
- package/skills/shopify-lint/theme-check-theory/src/index.test.ts +20 -0
- package/skills/shopify-lint/theme-check-theory/src/index.ts +11 -0
- package/skills/shopify-lint/theme-check-theory/src/test-utils.ts +31 -0
- package/skills/shopify-lint/theme-check-theory/src/utils/ast.ts +126 -0
- package/skills/shopify-lint/theme-check-theory/tsconfig.build.json +10 -0
- package/skills/shopify-lint/theme-check-theory/tsconfig.json +15 -0
- package/skills/shopify-lint/theme-check-theory/vitest.config.ts +11 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
|
|
2
|
+
# Debugging in the iOS Simulator
|
|
3
|
+
|
|
4
|
+
Real iOS WebKit + real Safari chrome, scriptable from the terminal. The one
|
|
5
|
+
hard limitation: **you cannot synthesize taps** (`simctl` has no touch API,
|
|
6
|
+
and macOS blocks synthetic clicks without Accessibility permission).
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
xcrun simctl list devices available # find a device + UDID
|
|
12
|
+
open -a Simulator --args -CurrentDeviceUDID <UDID>
|
|
13
|
+
xcrun simctl bootstatus <UDID> -b # wait for boot
|
|
14
|
+
xcrun simctl openurl booted "https://site.example/page"
|
|
15
|
+
xcrun simctl io booted screenshot /tmp/shot.png
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Driving state without touch
|
|
19
|
+
|
|
20
|
+
- **URL parameters the page already supports** — e.g. many themes open the
|
|
21
|
+
cart drawer on load with `?cart=open`; check the theme's JS for what it reads.
|
|
22
|
+
- **Temporary debug hooks** — add a query-param-gated script to the page that
|
|
23
|
+
auto-runs the interaction sequence, sync it to a dev theme, load with the
|
|
24
|
+
param, screenshot on a timer, then remove the hook:
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
if (new URLSearchParams(location.search).has('debug_cycles')) {
|
|
28
|
+
window.addEventListener('load', async () => {
|
|
29
|
+
const t = (ms) => new Promise(r => setTimeout(r, ms));
|
|
30
|
+
// example: cycle whatever component is under test
|
|
31
|
+
const d = document.querySelector('cart-drawer');
|
|
32
|
+
await t(2000); d.open(); await t(2500); d.close(); await t(1500); d.open();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- **App-switch nudge** (forces visibility/viewport recalc):
|
|
38
|
+
`xcrun simctl launch booted com.apple.Preferences; sleep 2; xcrun simctl launch booted com.apple.mobilesafari`
|
|
39
|
+
|
|
40
|
+
## Measuring screenshots
|
|
41
|
+
|
|
42
|
+
Screenshots are 3x scale on Pro-class devices — divide pixels by 3 for CSS px.
|
|
43
|
+
Pixel-scan with PIL rather than eyeballing:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from PIL import Image
|
|
47
|
+
im = Image.open('/tmp/shot.png').convert('RGB')
|
|
48
|
+
# find rows containing the element's known background colour, measure the band
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Comparing the measured painted box against the element's expected border-box
|
|
52
|
+
identifies which box was painted: a background at exactly content-box size
|
|
53
|
+
means padding wasn't painted (stale layer or native form-control painter).
|
|
54
|
+
|
|
55
|
+
## Calibrating viewport units
|
|
56
|
+
|
|
57
|
+
The simulator reaches your Mac's `localhost` directly. Serve a test page
|
|
58
|
+
(`python3 -m http.server 8899`) with fixed-position divs of `100svh`,
|
|
59
|
+
`100dvh`, `100lvh`, `100%` height and a `bottom: 0` marker, screenshot, and
|
|
60
|
+
measure where each ends. This settles "which viewport is this unit tracking"
|
|
61
|
+
arguments with data.
|
|
62
|
+
|
|
63
|
+
## Shopify-specific gotchas
|
|
64
|
+
|
|
65
|
+
- Preview/development themes show the **Draft bar**, which overlays the
|
|
66
|
+
bottom ~46pt of the viewport and covers bottom-anchored CTAs — it looks
|
|
67
|
+
exactly like a layout bug. Append `&pb=0` to hide it before judging.
|
|
68
|
+
- Add items to the cart with
|
|
69
|
+
`openurl booted ".../cart/add?id=<variant>&quantity=1&return_to=/cart"`
|
|
70
|
+
(a `/cart/<variant>:1` permalink jumps straight to checkout — not useful).
|
|
71
|
+
|
|
72
|
+
## When this level is not enough
|
|
73
|
+
|
|
74
|
+
If the bug needs real touch gestures, device GPU behavior, or you have the
|
|
75
|
+
broken state live on someone's phone, use references/device.md. For fast CSS iteration first, use
|
|
76
|
+
references/playwright.md.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Evaluate JavaScript in a live Safari tab on a USB-connected iOS device.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python device_eval.py <url-substring> ['<js-expression>']
|
|
6
|
+
|
|
7
|
+
The expression runs in the page and its return value is printed. Wrap DOM
|
|
8
|
+
reads in an IIFE returning JSON.stringify(...) for structured output.
|
|
9
|
+
Defaults to a generic element diagnostic if no expression is given.
|
|
10
|
+
|
|
11
|
+
Requires: pip install pymobiledevice3 (Python >= 3.10)
|
|
12
|
+
Phone: Settings -> Safari -> Advanced -> Web Inspector ON, tab foreground,
|
|
13
|
+
screen unlocked, and no other inspector attached (close Mac Safari Develop).
|
|
14
|
+
"""
|
|
15
|
+
import asyncio
|
|
16
|
+
import sys
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
20
|
+
from pymobiledevice3.services.webinspector import WebinspectorService
|
|
21
|
+
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
22
|
+
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
23
|
+
|
|
24
|
+
DEFAULT_EXPR = r"""(() => {
|
|
25
|
+
return JSON.stringify({
|
|
26
|
+
href: location.href.slice(0, 120),
|
|
27
|
+
theme: (window.Shopify && Shopify.theme) ? Shopify.theme : undefined,
|
|
28
|
+
vv: { h: Math.round(visualViewport.height), ih: innerHeight },
|
|
29
|
+
});
|
|
30
|
+
})()"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def attach(inspector, match):
|
|
34
|
+
pages = await inspector.get_open_application_pages(timeout=2)
|
|
35
|
+
ap = next((p for p in pages if match in (getattr(p.page, 'web_url', '') or '')), None)
|
|
36
|
+
if ap is None:
|
|
37
|
+
urls = [getattr(p.page, 'web_url', None) for p in pages]
|
|
38
|
+
raise SystemExit(f'no tab matching {match!r}; open tabs: {urls}')
|
|
39
|
+
session_id = str(uuid.uuid4()).upper()
|
|
40
|
+
# wait_target=True is required on iOS 17+ (Target-domain wrapped protocol)
|
|
41
|
+
session = await asyncio.wait_for(
|
|
42
|
+
InspectorSession.create(
|
|
43
|
+
SessionProtocol(inspector, session_id, ap.application, ap.page, method_prefix=""),
|
|
44
|
+
wait_target=True),
|
|
45
|
+
timeout=10)
|
|
46
|
+
await session.runtime_enable()
|
|
47
|
+
return session
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
match = sys.argv[1] if len(sys.argv) > 1 else 'http'
|
|
52
|
+
expr = sys.argv[2] if len(sys.argv) > 2 else DEFAULT_EXPR
|
|
53
|
+
lockdown = await create_using_usbmux()
|
|
54
|
+
inspector = WebinspectorService(lockdown=lockdown)
|
|
55
|
+
async with inspector:
|
|
56
|
+
session = await attach(inspector, match)
|
|
57
|
+
result = await asyncio.wait_for(session.runtime_evaluate(expr, return_by_value=True), timeout=15)
|
|
58
|
+
print(result)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Run stepped repair experiments on a live broken page while a human watches
|
|
3
|
+
the device. Each step is announced with a colored banner injected into the
|
|
4
|
+
page; the watcher reports which step number visually fixed the element.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python device_experiment.py <url-substring> [target-css-selector]
|
|
8
|
+
|
|
9
|
+
Edit STEPS for the element/bug at hand. The winning step becomes the
|
|
10
|
+
permanent fix, wired into the code path that precedes the broken state.
|
|
11
|
+
"""
|
|
12
|
+
import asyncio
|
|
13
|
+
import sys
|
|
14
|
+
import uuid
|
|
15
|
+
|
|
16
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
17
|
+
from pymobiledevice3.services.webinspector import WebinspectorService
|
|
18
|
+
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
19
|
+
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
20
|
+
|
|
21
|
+
TARGET = sys.argv[2] if len(sys.argv) > 2 else 'body'
|
|
22
|
+
|
|
23
|
+
STEPS = [
|
|
24
|
+
("STEP 1: banner only (sanity)", "void 0"),
|
|
25
|
+
("STEP 2: opacity nudge", f"""
|
|
26
|
+
(() => {{ const el = document.querySelector('{TARGET}'); if (!el) return;
|
|
27
|
+
el.style.opacity = '0.99';
|
|
28
|
+
requestAnimationFrame(() => {{ el.style.opacity = ''; }}); }})()"""),
|
|
29
|
+
("STEP 3: own compositing layer", f"""
|
|
30
|
+
(() => {{ const el = document.querySelector('{TARGET}'); if (!el) return;
|
|
31
|
+
el.style.transform = 'translateZ(0)'; }})()"""),
|
|
32
|
+
("STEP 4: display rebuild", f"""
|
|
33
|
+
(() => {{ const el = document.querySelector('{TARGET}'); if (!el) return;
|
|
34
|
+
el.style.display = 'none'; void el.offsetHeight; el.style.display = ''; }})()"""),
|
|
35
|
+
("STEP 5: reflow via class re-toggle", f"""
|
|
36
|
+
(() => {{ const el = document.querySelector('{TARGET}'); if (!el) return;
|
|
37
|
+
const p = el.parentElement; p.classList.add('td-exp-retoggle');
|
|
38
|
+
void p.offsetHeight; p.classList.remove('td-exp-retoggle'); }})()"""),
|
|
39
|
+
("STEP 6: scroll nudge", """
|
|
40
|
+
(() => { const s = document.scrollingElement; s.scrollTop += 1; s.scrollTop -= 1; })()"""),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
BANNER = """
|
|
44
|
+
(() => {
|
|
45
|
+
let el = document.getElementById('td-exp-banner');
|
|
46
|
+
if (!el) {
|
|
47
|
+
el = document.createElement('div');
|
|
48
|
+
el.id = 'td-exp-banner';
|
|
49
|
+
el.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:2147483647;padding:18px;font:bold 24px -apple-system;text-align:center;color:#fff;';
|
|
50
|
+
document.body.appendChild(el);
|
|
51
|
+
}
|
|
52
|
+
el.style.background = COLOR;
|
|
53
|
+
el.textContent = LABEL;
|
|
54
|
+
})()
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
COLORS = ['#c0392b', '#d35400', '#f39c12', '#27ae60', '#2980b9', '#8e44ad']
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def main():
|
|
61
|
+
match = sys.argv[1] if len(sys.argv) > 1 else 'http'
|
|
62
|
+
lockdown = await create_using_usbmux()
|
|
63
|
+
inspector = WebinspectorService(lockdown=lockdown)
|
|
64
|
+
async with inspector:
|
|
65
|
+
pages = await inspector.get_open_application_pages(timeout=2)
|
|
66
|
+
ap = next((p for p in pages if match in (getattr(p.page, 'web_url', '') or '')), None)
|
|
67
|
+
if ap is None:
|
|
68
|
+
raise SystemExit(f'no tab matching {match!r}')
|
|
69
|
+
session_id = str(uuid.uuid4()).upper()
|
|
70
|
+
session = await asyncio.wait_for(
|
|
71
|
+
InspectorSession.create(
|
|
72
|
+
SessionProtocol(inspector, session_id, ap.application, ap.page, method_prefix=""),
|
|
73
|
+
wait_target=True),
|
|
74
|
+
timeout=10)
|
|
75
|
+
await session.runtime_enable()
|
|
76
|
+
|
|
77
|
+
async def banner(color, label):
|
|
78
|
+
await session.runtime_evaluate(BANNER.replace('COLOR', repr(color)).replace('LABEL', repr(label)))
|
|
79
|
+
|
|
80
|
+
# Lead-in so the watcher can reproduce the broken state first
|
|
81
|
+
for remaining in range(20, 0, -5):
|
|
82
|
+
await banner('#111', f'BREAK IT NOW — reproduce the bug. Steps start in {remaining}s')
|
|
83
|
+
print(f'lead-in {remaining}s', flush=True)
|
|
84
|
+
await asyncio.sleep(5)
|
|
85
|
+
|
|
86
|
+
for i, (label, code) in enumerate(STEPS):
|
|
87
|
+
await banner(COLORS[i % len(COLORS)], label + ' — fixed?')
|
|
88
|
+
await session.runtime_evaluate(code)
|
|
89
|
+
print('ran', label, flush=True)
|
|
90
|
+
await asyncio.sleep(5)
|
|
91
|
+
|
|
92
|
+
await banner('#111', 'DONE — report which step fixed it')
|
|
93
|
+
await asyncio.sleep(6)
|
|
94
|
+
await session.runtime_evaluate("document.getElementById('td-exp-banner')?.remove()")
|
|
95
|
+
print('done', flush=True)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Capture a Page.snapshotRect PNG from a live Safari tab on a USB device.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python device_snapshot.py <url-substring> [out.png] [x y w h]
|
|
6
|
+
|
|
7
|
+
IMPORTANT: the snapshot repaints from the render tree. If the snapshot looks
|
|
8
|
+
CORRECT while the device screen looks BROKEN, the bug is a stale compositor
|
|
9
|
+
texture (paint bug), not a layout bug — fix with a paint invalidation (e.g.
|
|
10
|
+
opacity nudge), not with CSS.
|
|
11
|
+
"""
|
|
12
|
+
import asyncio
|
|
13
|
+
import base64
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
from pymobiledevice3.lockdown import create_using_usbmux
|
|
19
|
+
from pymobiledevice3.services.webinspector import WebinspectorService
|
|
20
|
+
from pymobiledevice3.services.web_protocol.inspector_session import InspectorSession
|
|
21
|
+
from pymobiledevice3.services.web_protocol.session_protocol import SessionProtocol
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
match = sys.argv[1] if len(sys.argv) > 1 else 'http'
|
|
26
|
+
out = sys.argv[2] if len(sys.argv) > 2 else 'snapshot.png'
|
|
27
|
+
x, y, w, h = (int(a) for a in sys.argv[3:7]) if len(sys.argv) > 6 else (0, 0, 430, 932)
|
|
28
|
+
|
|
29
|
+
lockdown = await create_using_usbmux()
|
|
30
|
+
inspector = WebinspectorService(lockdown=lockdown)
|
|
31
|
+
async with inspector:
|
|
32
|
+
pages = await inspector.get_open_application_pages(timeout=2)
|
|
33
|
+
ap = next((p for p in pages if match in (getattr(p.page, 'web_url', '') or '')), None)
|
|
34
|
+
if ap is None:
|
|
35
|
+
raise SystemExit(f'no tab matching {match!r}')
|
|
36
|
+
session_id = str(uuid.uuid4()).upper()
|
|
37
|
+
session = await asyncio.wait_for(
|
|
38
|
+
InspectorSession.create(
|
|
39
|
+
SessionProtocol(inspector, session_id, ap.application, ap.page, method_prefix=""),
|
|
40
|
+
wait_target=True),
|
|
41
|
+
timeout=10)
|
|
42
|
+
|
|
43
|
+
mid = await session.send_message_to_target({'method': 'Page.enable', 'params': {}})
|
|
44
|
+
await asyncio.wait_for(session.receive_response_by_id(mid), timeout=10)
|
|
45
|
+
|
|
46
|
+
mid = await session.send_message_to_target(
|
|
47
|
+
{'method': 'Page.snapshotRect',
|
|
48
|
+
'params': {'x': x, 'y': y, 'width': w, 'height': h, 'coordinateSystem': 'Viewport'}})
|
|
49
|
+
res = await asyncio.wait_for(session.receive_response_by_id(mid), timeout=15)
|
|
50
|
+
# iOS 17+ wraps responses in the Target domain — unwrap
|
|
51
|
+
if res.get('method') == 'Target.dispatchMessageFromTarget':
|
|
52
|
+
res = json.loads(res['params']['message'])
|
|
53
|
+
data = res.get('result', res).get('dataURL', '')
|
|
54
|
+
if not data.startswith('data:image/png;base64,'):
|
|
55
|
+
raise SystemExit(f'snapshot failed: {json.dumps(res)[:200]}')
|
|
56
|
+
with open(out, 'wb') as f:
|
|
57
|
+
f.write(base64.b64decode(data.split(',', 1)[1]))
|
|
58
|
+
print('saved', out)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
asyncio.run(main())
|
|
@@ -33,8 +33,8 @@ Use the script output to identify changed files, then inspect the relevant diffs
|
|
|
33
33
|
Prioritize:
|
|
34
34
|
|
|
35
35
|
- User-facing behavior changes
|
|
36
|
-
-
|
|
37
|
-
- CSS or markup changes that affect
|
|
36
|
+
- CMS setting/schema changes
|
|
37
|
+
- CSS or markup changes that affect rendering
|
|
38
38
|
- Renamed files and vendor-theme hotspots
|
|
39
39
|
|
|
40
40
|
Do not summarize the `docs/changes.md` edit itself as part of the branch work. If the branch contains unrelated skill or tooling files, either omit them from the changelog entry or separate them clearly when they are relevant to the project's maintenance history.
|
|
@@ -57,7 +57,7 @@ Guidelines:
|
|
|
57
57
|
- Make the title describe the feature or fix, not the branch name.
|
|
58
58
|
- Write `Purpose` in plain language with outcome-focused bullets.
|
|
59
59
|
- Use the `Files changed` table to explain why each file matters.
|
|
60
|
-
- Call out non-`td-` theme or vendor files in a separate subsection when
|
|
60
|
+
- Call out non-`td-` theme or vendor files in a separate subsection when in a Shopify project.
|
|
61
61
|
- Mention renamed files explicitly.
|
|
62
62
|
- Keep `Upgrade impact` brief and concrete.
|
|
63
63
|
- Use `Notes` for implementation details, edge cases, or assumptions.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shopify-lint
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
description: Run Shopify CLI Theme Check with Theory Digital's bundled custom checks while reporting and failing only on offenses in files modified on the current Git branch. Use when Codex needs to lint a Shopify theme, validate branch-scoped Liquid or theme changes, enforce Theory theme rules, or avoid surfacing pre-existing Theme Check offenses from untouched files.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Shopify Lint
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
The bundled `theme-check-theory` custom-check package ships as TypeScript source only; its `node_modules` and compiled `dist/` are not committed. Run setup once before the first run (and after upgrading the skill):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bash .agents/skills/shopify-lint/scripts/setup.sh
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Installing with `td-ai-tools install --setup shopify-lint` runs this automatically. The script:
|
|
18
|
+
|
|
19
|
+
1. Runs `npm install && npm run build` inside `theme-check-theory/`, producing `dist/index.js` — the CommonJS entry point the root `.theme-check.yml` requires.
|
|
20
|
+
2. Writes a `.theme-check.yml` at the project root wiring in the bundled checks. If one already exists it is left untouched; ensure its `require:` list includes `./.agents/skills/shopify-lint/theme-check-theory`.
|
|
21
|
+
|
|
22
|
+
Keep these dependencies inside `theme-check-theory/node_modules`; do not install Node dependencies at the theme root.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
Run the bundled Python script from the Shopify theme root:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python3 .agents/skills/shopify-lint/scripts/shopify_lint.py --path .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The script runs `shopify theme check --output json`, identifies files changed since the current branch's merge-base, adds staged, unstaged, and untracked files, and emits only reports whose paths are in that set. The root `.theme-check.yml` directly requires the bundled `theme-check-theory` package from this skill directory.
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
1. Confirm `shopify` and `python3` are available.
|
|
37
|
+
2. Run the bundled script instead of calling `shopify theme check` directly.
|
|
38
|
+
3. Treat exit code `0` as no failing offenses in modified files, `1` as filtered offenses at or above the fail level, and `2` as a Git, CLI, or JSON-processing error.
|
|
39
|
+
4. Fix reported issues and rerun until the command passes. Do not fix offenses in untouched files unless the user expands the scope.
|
|
40
|
+
5. Keep custom-check dependencies inside `theme-check-theory/node_modules`. Do not install Node dependencies at the theme root.
|
|
41
|
+
|
|
42
|
+
The default base is `origin/HEAD`, then `origin/main`, `main`, `origin/master`, or `master`. Override it when needed:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python3 .agents/skills/shopify-lint/scripts/shopify_lint.py --path . --base-ref origin/develop
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Set `SHOPIFY_LINT_BASE_REF` for the same override in automation. Use `--format json` for machine-readable filtered output and `--fail-level warning` or `--fail-level info` for stricter runs. When passing `--config <path>`, preserve the bundled package's `require` entry or the Theory checks will not load.
|
|
49
|
+
|
|
50
|
+
Do not use Theme Check auto-correction through this workflow because it can modify untouched files before filtering.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# shopify-lint setup
|
|
5
|
+
#
|
|
6
|
+
# Run automatically by the installer (`--setup`) from the installed skill
|
|
7
|
+
# directory, or manually with `bash scripts/setup.sh`. It:
|
|
8
|
+
# 1. Builds the bundled `theme-check-theory` custom-check package
|
|
9
|
+
# (its node_modules and dist/ are intentionally not committed).
|
|
10
|
+
# 2. Writes a `.theme-check.yml` at the project root that wires the bundled
|
|
11
|
+
# checks into Shopify CLI Theme Check.
|
|
12
|
+
|
|
13
|
+
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
14
|
+
PKG_DIR="$SKILL_DIR/theme-check-theory"
|
|
15
|
+
|
|
16
|
+
# 1. Build the bundled custom-check package.
|
|
17
|
+
echo "shopify-lint setup: building theme-check-theory in $PKG_DIR"
|
|
18
|
+
cd "$PKG_DIR"
|
|
19
|
+
npm install
|
|
20
|
+
npm run build
|
|
21
|
+
|
|
22
|
+
# 2. Generate the project-root .theme-check.yml.
|
|
23
|
+
#
|
|
24
|
+
# The installed layout is <project>/<.agents|.claude>/skills/shopify-lint, so
|
|
25
|
+
# the project root is three levels above the skill directory. Guard against
|
|
26
|
+
# running outside that layout (e.g. from the catalog repo) to avoid writing the
|
|
27
|
+
# config into an unexpected directory.
|
|
28
|
+
TARGET_DIR="$(cd "$SKILL_DIR/../.." && pwd)"
|
|
29
|
+
TARGET_BASE="$(basename "$TARGET_DIR")"
|
|
30
|
+
if [[ "$TARGET_BASE" != ".agents" && "$TARGET_BASE" != ".claude" ]]; then
|
|
31
|
+
echo "shopify-lint setup: unrecognized install layout ($TARGET_DIR); skipping .theme-check.yml generation." >&2
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
PROJECT_ROOT="$(dirname "$TARGET_DIR")"
|
|
36
|
+
THEME_CHECK_FILE="$PROJECT_ROOT/.theme-check.yml"
|
|
37
|
+
|
|
38
|
+
if [[ -f "$THEME_CHECK_FILE" ]]; then
|
|
39
|
+
echo "shopify-lint setup: $THEME_CHECK_FILE already exists; leaving it unchanged."
|
|
40
|
+
echo " Ensure its 'require:' list includes ./.agents/skills/shopify-lint/theme-check-theory"
|
|
41
|
+
exit 0
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
cat > "$THEME_CHECK_FILE" <<'YAML'
|
|
45
|
+
extends:
|
|
46
|
+
- theme-check:recommended
|
|
47
|
+
|
|
48
|
+
require:
|
|
49
|
+
- ./.agents/skills/shopify-lint/theme-check-theory
|
|
50
|
+
|
|
51
|
+
UnusedSectionSettings:
|
|
52
|
+
enabled: true
|
|
53
|
+
severity: warning
|
|
54
|
+
|
|
55
|
+
UnguardedTextSetting:
|
|
56
|
+
enabled: true
|
|
57
|
+
severity: warning
|
|
58
|
+
YAML
|
|
59
|
+
|
|
60
|
+
echo "shopify-lint setup: wrote $THEME_CHECK_FILE"
|