td-ai-tools 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +49 -0
  2. package/agents/README.md +10 -0
  3. package/agents/horizon-component-library/AGENTS.md +184 -0
  4. package/agents/horizon-component-library/README.md +10 -0
  5. package/agents/horizon-component-library/scripts/td-guard.sh +49 -0
  6. package/bin/cli.js +209 -0
  7. package/package.json +23 -0
  8. package/scripts/smoke-install.sh +56 -0
  9. package/skills/README.md +15 -0
  10. package/skills/cache-reset/SKILL.md +18 -0
  11. package/skills/cache-reset/agents/openai.yaml +4 -0
  12. package/skills/car-ticket-generator/SKILL.md +130 -0
  13. package/skills/car-ticket-generator/agents/openai.yaml +4 -0
  14. package/skills/everhour-basecamp-estimates/.env.example +2 -0
  15. package/skills/everhour-basecamp-estimates/SKILL.md +73 -0
  16. package/skills/everhour-basecamp-estimates/agents/openai.yaml +4 -0
  17. package/skills/everhour-basecamp-estimates/scripts/update_estimates.py +518 -0
  18. package/skills/everhour-basecamp-estimates/tests/test_update_estimates.py +93 -0
  19. package/skills/horizon-component-migration/SKILL.md +59 -0
  20. package/skills/horizon-component-migration/agents/openai.yaml +4 -0
  21. package/skills/pr-solver/SKILL.md +50 -0
  22. package/skills/pr-solver/agents/openai.yaml +4 -0
  23. package/skills/pr-solver/references/github-pr-reviewthreads-graphql.md +63 -0
  24. package/skills/pr-solver/scripts/list_unresolved_threads.py +307 -0
  25. package/skills/pull-request/SKILL.md +216 -0
  26. package/skills/pull-request/agents/openai.yaml +4 -0
  27. package/skills/record-changes/SKILL.md +75 -0
  28. package/skills/record-changes/agents/openai.yaml +4 -0
  29. package/skills/record-changes/scripts/branch_diff_context.py +190 -0
  30. package/skills/stylesheet-migration/SKILL.md +36 -0
  31. package/skills/stylesheet-migration/agents/openai.yaml +6 -0
  32. package/skills/stylesheet-migration/scripts/__pycache__/liquid_stylesheet_migrator.cpython-312.pyc +0 -0
  33. package/skills/stylesheet-migration/scripts/__pycache__/test_liquid_stylesheet_migrator.cpython-312.pyc +0 -0
  34. package/skills/stylesheet-migration/scripts/liquid_stylesheet_migrator.py +204 -0
  35. package/skills/stylesheet-migration/scripts/migrate_stylesheet_tags.py +172 -0
  36. package/skills/stylesheet-migration/scripts/test_liquid_stylesheet_migrator.py +254 -0
  37. package/skills/td-js-vanilla-rules/SKILL.md +70 -0
  38. package/skills/td-js-vanilla-rules/agents/openai.yaml +3 -0
  39. package/skills/td-review/SKILL.md +122 -0
  40. package/skills/td-review/agents/openai.yaml +4 -0
  41. package/skills/td-review/agents/td-theme-reviewer.md +221 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: pr-solver
3
+ description: Resolve GitHub pull request feedback by querying unresolved review conversations with the GitHub GraphQL API and implementing code changes for each unresolved thread. Use when a task includes a GitHub PR URL, asks to address open review comments, or asks to clear unresolved conversations; request a PR link if it is missing.
4
+ ---
5
+
6
+ # GH PR Unresolved Conversations
7
+
8
+ Follow this workflow to turn unresolved PR review feedback into code fixes.
9
+
10
+ ## Collect Input
11
+
12
+ 1. Parse a GitHub PR link from the user request.
13
+ 2. Ask for the PR link if none is provided. Do not continue until a link is available.
14
+ 3. Accept `https://github.com/<owner>/<repo>/pull/<number>`.
15
+
16
+ ## Fetch Unresolved Conversations with GraphQL
17
+
18
+ 1. Use the GitHub GraphQL API, not the REST API, because unresolved status is only available on `reviewThreads.isResolved`.
19
+ 2. Ensure one of these environment variables is set before querying: `GH_TOKEN` or `GITHUB_TOKEN`.
20
+ 3. Run the bundled script:
21
+
22
+ ```bash
23
+ python3 .agents/skills/pr-solver/scripts/list_unresolved_threads.py "<pr-url>" --format json
24
+ ```
25
+
26
+ 4. Treat each returned thread where `is_resolved` is `false` as work to address.
27
+ 5. If scripting is unavailable, run the GraphQL query in `references/github-pr-reviewthreads-graphql.md` directly.
28
+
29
+ ## Implement Fixes Thread by Thread
30
+
31
+ 1. Process unresolved threads one at a time.
32
+ 2. Read the entire conversation in each thread before editing.
33
+ 3. Convert the feedback into code changes in the referenced file and nearby context.
34
+ 4. Prioritize the newest comment while preserving requirements from earlier comments.
35
+ 5. Run targeted validation after each fix: tests, lint, or type checks relevant to touched files.
36
+ 6. Mark a thread as blocked only when required context is missing or the feedback cannot be satisfied safely.
37
+
38
+ ## Report Results
39
+
40
+ 1. Report every unresolved thread with one status: `fixed`, `partially fixed`, or `blocked`.
41
+ 2. Include file references for each implemented fix.
42
+ 3. Include validation commands executed and whether they passed.
43
+ 4. Include thread URLs for any partial or blocked outcomes.
44
+ 5. Note that GitHub conversation resolution should happen after verification in the PR UI.
45
+
46
+ ## Resources
47
+
48
+ - `.agents/skills/pr-solver/scripts/list_unresolved_threads.py`: Query GitHub GraphQL `reviewThreads`, paginate, and output unresolved conversations.
49
+ - `.agents/skills/pr-solver/references/github-pr-reviewthreads-graphql.md`: Direct GraphQL fallback query and field guidance.
50
+ - The installer also mirrors this skill under `.claude/skills/pr-solver/`.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "PR Solver"
3
+ short_description: "Fetch and solve PR conversations"
4
+ default_prompt: "Use $pr-solver to fix unresolved PR conversations with GitHub GraphQL."
@@ -0,0 +1,63 @@
1
+ # GitHub PR Review Threads GraphQL Reference
2
+
3
+ Use this query when you cannot run `scripts/list_unresolved_threads.py`.
4
+
5
+ ## Why GraphQL
6
+
7
+ Use GraphQL because unresolved status is exposed on `PullRequest.reviewThreads.nodes[].isResolved`.
8
+ REST pull request review endpoints do not provide equivalent conversation resolution state.
9
+
10
+ ## Endpoint and Auth
11
+
12
+ - Endpoint: `https://api.github.com/graphql`
13
+ - Header: `Authorization: Bearer <GH_TOKEN or GITHUB_TOKEN>`
14
+ - Header: `Content-Type: application/json`
15
+
16
+ ## Query
17
+
18
+ ```graphql
19
+ query($owner: String!, $repo: String!, $number: Int!, $after: String) {
20
+ repository(owner: $owner, name: $repo) {
21
+ pullRequest(number: $number) {
22
+ number
23
+ title
24
+ url
25
+ reviewThreads(first: 50, after: $after) {
26
+ nodes {
27
+ id
28
+ isResolved
29
+ isOutdated
30
+ path
31
+ line
32
+ startLine
33
+ originalLine
34
+ originalStartLine
35
+ diffSide
36
+ comments(first: 100) {
37
+ nodes {
38
+ id
39
+ body
40
+ createdAt
41
+ url
42
+ author {
43
+ login
44
+ }
45
+ }
46
+ }
47
+ }
48
+ pageInfo {
49
+ hasNextPage
50
+ endCursor
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## Pagination
59
+
60
+ 1. Start with `after: null`.
61
+ 2. Repeat while `reviewThreads.pageInfo.hasNextPage` is `true`.
62
+ 3. Pass `endCursor` into the next request as `after`.
63
+ 4. Treat threads with `isResolved == false` as unresolved work items.
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env python3
2
+ """List GitHub PR review threads and unresolved conversations via GraphQL."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import re
10
+ import sys
11
+ import urllib.error
12
+ import urllib.request
13
+ from typing import Dict, List, Optional, Tuple
14
+
15
+ GITHUB_GRAPHQL_URL = "https://api.github.com/graphql"
16
+
17
+ PR_URL_PATTERN = re.compile(
18
+ r"^https?://github\.com/(?P<owner>[^/\s]+)/(?P<repo>[^/\s]+)/pull/(?P<number>\d+)(?:[/?#].*)?$"
19
+ )
20
+ PR_REF_PATTERN = re.compile(
21
+ r"^(?P<owner>[A-Za-z0-9_.-]+)/(?P<repo>[A-Za-z0-9_.-]+)#(?P<number>\d+)$"
22
+ )
23
+
24
+ QUERY = """
25
+ query($owner: String!, $repo: String!, $number: Int!, $after: String) {
26
+ repository(owner: $owner, name: $repo) {
27
+ pullRequest(number: $number) {
28
+ number
29
+ title
30
+ url
31
+ reviewThreads(first: 50, after: $after) {
32
+ nodes {
33
+ id
34
+ isResolved
35
+ isOutdated
36
+ path
37
+ line
38
+ startLine
39
+ originalLine
40
+ originalStartLine
41
+ diffSide
42
+ comments(first: 100) {
43
+ nodes {
44
+ id
45
+ body
46
+ createdAt
47
+ url
48
+ author {
49
+ login
50
+ }
51
+ }
52
+ }
53
+ }
54
+ pageInfo {
55
+ hasNextPage
56
+ endCursor
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
62
+ """
63
+
64
+
65
+ def parse_pr(value: str) -> Tuple[str, str, int]:
66
+ match = PR_URL_PATTERN.match(value.strip())
67
+ if not match:
68
+ match = PR_REF_PATTERN.match(value.strip())
69
+ if not match:
70
+ raise ValueError(
71
+ "PR must be https://github.com/<owner>/<repo>/pull/<number> or <owner>/<repo>#<number>."
72
+ )
73
+
74
+ owner = match.group("owner")
75
+ repo = match.group("repo")
76
+ number = int(match.group("number"))
77
+ return owner, repo, number
78
+
79
+
80
+ def resolve_token(token_env: str) -> str:
81
+ for key in [item.strip() for item in token_env.split(",") if item.strip()]:
82
+ token = os.getenv(key)
83
+ if token:
84
+ return token
85
+ raise RuntimeError(
86
+ f"Missing GitHub token. Set one of: {token_env}. "
87
+ "The token needs repo read permissions for private repositories."
88
+ )
89
+
90
+
91
+ def graphql_request(query: str, variables: Dict[str, object], token: str) -> Dict[str, object]:
92
+ body = json.dumps({"query": query, "variables": variables}).encode("utf-8")
93
+ req = urllib.request.Request(
94
+ GITHUB_GRAPHQL_URL,
95
+ data=body,
96
+ method="POST",
97
+ headers={
98
+ "Authorization": f"Bearer {token}",
99
+ "Content-Type": "application/json",
100
+ "Accept": "application/vnd.github+json",
101
+ },
102
+ )
103
+
104
+ try:
105
+ with urllib.request.urlopen(req) as response:
106
+ payload = json.loads(response.read().decode("utf-8"))
107
+ except urllib.error.HTTPError as exc:
108
+ detail = exc.read().decode("utf-8", errors="replace")
109
+ raise RuntimeError(f"GraphQL request failed: HTTP {exc.code}: {detail}") from exc
110
+ except urllib.error.URLError as exc:
111
+ raise RuntimeError(f"Network error while calling GitHub GraphQL: {exc}") from exc
112
+
113
+ if payload.get("errors"):
114
+ raise RuntimeError(f"GraphQL errors: {json.dumps(payload['errors'], indent=2)}")
115
+
116
+ data = payload.get("data")
117
+ if not isinstance(data, dict):
118
+ raise RuntimeError("GraphQL response did not include a valid data object.")
119
+ return data
120
+
121
+
122
+ def fetch_review_threads(owner: str, repo: str, number: int, token: str) -> Tuple[Dict[str, object], List[Dict[str, object]]]:
123
+ all_threads: List[Dict[str, object]] = []
124
+ cursor: Optional[str] = None
125
+ pull_request: Optional[Dict[str, object]] = None
126
+
127
+ while True:
128
+ variables: Dict[str, object] = {
129
+ "owner": owner,
130
+ "repo": repo,
131
+ "number": number,
132
+ "after": cursor,
133
+ }
134
+ data = graphql_request(QUERY, variables, token)
135
+ repository = data.get("repository")
136
+ if not isinstance(repository, dict):
137
+ raise RuntimeError("Repository not found or not accessible with this token.")
138
+
139
+ pull_request = repository.get("pullRequest")
140
+ if not isinstance(pull_request, dict):
141
+ raise RuntimeError(f"Pull request #{number} was not found in {owner}/{repo}.")
142
+
143
+ review_threads = pull_request.get("reviewThreads") or {}
144
+ nodes = review_threads.get("nodes") or []
145
+ all_threads.extend([node for node in nodes if isinstance(node, dict)])
146
+
147
+ page_info = review_threads.get("pageInfo") or {}
148
+ has_next = bool(page_info.get("hasNextPage"))
149
+ if not has_next:
150
+ return pull_request, all_threads
151
+
152
+ next_cursor = page_info.get("endCursor")
153
+ if not isinstance(next_cursor, str) or not next_cursor:
154
+ return pull_request, all_threads
155
+ cursor = next_cursor
156
+
157
+
158
+ def normalize_thread(thread: Dict[str, object]) -> Dict[str, object]:
159
+ comments_container = thread.get("comments") or {}
160
+ comment_nodes = comments_container.get("nodes") or []
161
+ comments: List[Dict[str, object]] = []
162
+ for comment in comment_nodes:
163
+ if not isinstance(comment, dict):
164
+ continue
165
+ author = comment.get("author") or {}
166
+ comments.append(
167
+ {
168
+ "id": comment.get("id"),
169
+ "author": (author.get("login") if isinstance(author, dict) else None),
170
+ "body": comment.get("body"),
171
+ "created_at": comment.get("createdAt"),
172
+ "url": comment.get("url"),
173
+ }
174
+ )
175
+
176
+ latest = comments[-1] if comments else {}
177
+ return {
178
+ "id": thread.get("id"),
179
+ "is_resolved": bool(thread.get("isResolved")),
180
+ "is_outdated": bool(thread.get("isOutdated")),
181
+ "path": thread.get("path"),
182
+ "line": thread.get("line"),
183
+ "start_line": thread.get("startLine"),
184
+ "original_line": thread.get("originalLine"),
185
+ "original_start_line": thread.get("originalStartLine"),
186
+ "diff_side": thread.get("diffSide"),
187
+ "latest_comment": latest,
188
+ "comments": comments,
189
+ }
190
+
191
+
192
+ def shorten(text: object, max_chars: int) -> str:
193
+ if not isinstance(text, str):
194
+ return ""
195
+ compact = " ".join(text.split())
196
+ if len(compact) <= max_chars:
197
+ return compact
198
+ if max_chars <= 3:
199
+ return compact[:max_chars]
200
+ return f"{compact[:max_chars - 3]}..."
201
+
202
+
203
+ def render_markdown(result: Dict[str, object], max_body_chars: int, include_resolved: bool) -> None:
204
+ pull_request = result["pull_request"]
205
+ threads: List[Dict[str, object]] = result["threads"]
206
+ unresolved_count = result["unresolved_count"]
207
+
208
+ print(f"# PR #{pull_request['number']}: {pull_request['title']}")
209
+ print(pull_request["url"])
210
+ print()
211
+ print(f"- Total review threads: {len(threads)}")
212
+ print(f"- Unresolved review threads: {unresolved_count}")
213
+ print()
214
+
215
+ if not threads:
216
+ print("No review threads were found.")
217
+ return
218
+
219
+ visible_threads = threads if include_resolved else [t for t in threads if not t["is_resolved"]]
220
+ for index, thread in enumerate(visible_threads, start=1):
221
+ location = thread["path"] or "(no file path)"
222
+ line = thread["line"] or thread["start_line"] or thread["original_line"] or "?"
223
+ latest = thread["latest_comment"] or {}
224
+ latest_author = latest.get("author") or "unknown"
225
+ latest_body = shorten(latest.get("body"), max_body_chars)
226
+ latest_url = latest.get("url") or ""
227
+
228
+ print(f"## Thread {index}")
229
+ print(f"- ID: {thread['id']}")
230
+ print(f"- Resolved: {thread['is_resolved']}")
231
+ print(f"- Outdated: {thread['is_outdated']}")
232
+ print(f"- Location: {location}:{line}")
233
+ print(f"- Latest comment author: {latest_author}")
234
+ if latest_url:
235
+ print(f"- Latest comment URL: {latest_url}")
236
+ if latest_body:
237
+ print(f"- Latest comment body: {latest_body}")
238
+ print(f"- Comment count: {len(thread['comments'])}")
239
+ print()
240
+
241
+
242
+ def main() -> int:
243
+ parser = argparse.ArgumentParser(
244
+ description="Fetch PR review threads via GitHub GraphQL and list unresolved conversations."
245
+ )
246
+ parser.add_argument(
247
+ "pr",
248
+ help="PR URL (https://github.com/<owner>/<repo>/pull/<number>) or <owner>/<repo>#<number>",
249
+ )
250
+ parser.add_argument(
251
+ "--token-env",
252
+ default="GH_TOKEN,GITHUB_TOKEN",
253
+ help="Comma-separated env vars to check for a GitHub token (default: GH_TOKEN,GITHUB_TOKEN).",
254
+ )
255
+ parser.add_argument(
256
+ "--format",
257
+ choices=("markdown", "json"),
258
+ default="markdown",
259
+ help="Output format (default: markdown).",
260
+ )
261
+ parser.add_argument(
262
+ "--include-resolved",
263
+ action="store_true",
264
+ help="Include resolved threads in output.",
265
+ )
266
+ parser.add_argument(
267
+ "--max-body-chars",
268
+ type=int,
269
+ default=800,
270
+ help="Maximum latest-comment body length in markdown output.",
271
+ )
272
+ args = parser.parse_args()
273
+
274
+ try:
275
+ owner, repo, number = parse_pr(args.pr)
276
+ token = resolve_token(args.token_env)
277
+ pull_request, raw_threads = fetch_review_threads(owner, repo, number, token)
278
+ threads = [normalize_thread(thread) for thread in raw_threads]
279
+ unresolved_count = sum(1 for thread in threads if not thread["is_resolved"])
280
+
281
+ result = {
282
+ "pull_request": {
283
+ "owner": owner,
284
+ "repo": repo,
285
+ "number": pull_request.get("number"),
286
+ "title": pull_request.get("title"),
287
+ "url": pull_request.get("url"),
288
+ },
289
+ "unresolved_count": unresolved_count,
290
+ "threads": threads,
291
+ }
292
+
293
+ if args.format == "json":
294
+ if not args.include_resolved:
295
+ result["threads"] = [thread for thread in threads if not thread["is_resolved"]]
296
+ print(json.dumps(result, indent=2))
297
+ return 0
298
+
299
+ render_markdown(result, args.max_body_chars, args.include_resolved)
300
+ return 0
301
+ except Exception as exc: # pylint: disable=broad-except
302
+ print(f"ERROR: {exc}", file=sys.stderr)
303
+ return 1
304
+
305
+
306
+ if __name__ == "__main__":
307
+ sys.exit(main())
@@ -0,0 +1,216 @@
1
+ ---
2
+ name: pull request
3
+ description: Generates well-structured GitHub pull request descriptions for Shopify theme development teams by analyzing git diffs and gathering context. Use this skill whenever a developer asks to create a PR, open a pull request, write a PR description, or submit code for review — even if they just say "make a PR" or "create a pull request". Also trigger when someone mentions needing a PR title, PR summary, or PR testing steps.
4
+ ---
5
+
6
+ # PR Description Generator
7
+
8
+ Helps Shopify theme developers create well-structured pull request descriptions by analyzing code changes and collecting context. Follow these steps in order.
9
+
10
+ ---
11
+
12
+ ## Step 1: Get Branch Info
13
+
14
+ Ask the developer for:
15
+ 1. **Source branch** — the feature/fix branch with their changes
16
+ 2. **Target branch** — the branch to merge into (e.g. `main`, `develop`, `release/x.x`)
17
+
18
+ ---
19
+
20
+ ## Step 2: Analyze the Diff
21
+
22
+ Run this command to fetch and diff the branches:
23
+
24
+ ```bash
25
+ git fetch origin && git diff origin/{target_branch}...origin/{source_branch}
26
+ ```
27
+
28
+ Analyze the diff for:
29
+ - Files changed, added, or deleted
30
+ - Nature of changes (feature, bug fix, refactor, styling, etc.)
31
+ - Key implementation details and patterns
32
+ - Potential impacts or side effects
33
+ - Testable and user-facing changes
34
+
35
+ ### Shopify-Specific Analysis Guide
36
+
37
+ | File Type | What to Look For | Testing Implications |
38
+ |-----------|-----------------|----------------------|
39
+ | `.liquid` sections/blocks | Schema changes, new settings, render tags, conditionals | Test each new setting; test conditional states |
40
+ | `.liquid` snippets | Parameters, usage contexts | Test in all contexts where snippet is used |
41
+ | `.js` files | Event listeners, DOM manipulation, API calls | Test interactions; test error states |
42
+ | `.css/.scss` files | Media queries, custom properties, animations | Test across breakpoints; test reduced motion |
43
+ | `locales/*.json` | New translation keys | Verify translations appear correctly |
44
+ | `config/*.json` | Theme settings | Test global settings impact |
45
+
46
+ ---
47
+
48
+ ## Step 3: Generate PR Content
49
+
50
+ Based on the diff, generate:
51
+
52
+ ### PR Title
53
+ Concise and descriptive. Use conventional commit style where appropriate (`feat:`, `fix:`, `refactor:`, `chore:`).
54
+
55
+ ### PR Summary
56
+ 1–2 sentences in plain, non-technical language describing what the changes accomplish from a user/business perspective.
57
+
58
+ ### Approach
59
+ Technical description covering:
60
+ - Key files modified and why
61
+ - Implementation strategy
62
+ - Notable patterns or techniques
63
+ - Trade-offs or decisions made
64
+
65
+ ### Testing Steps
66
+ Generate comprehensive, grouped testing steps as checkboxes. Cover all relevant categories:
67
+
68
+ **Functional Testing**
69
+ - New features or modified behavior
70
+ - Form inputs, buttons, interactive elements
71
+ - Conditional logic and different states
72
+
73
+ **Visual/UI Testing**
74
+ - Layout across breakpoints (mobile, tablet, desktop)
75
+ - New or modified CSS
76
+ - Section/block appearance in theme editor
77
+ - Hover states, transitions, animations
78
+
79
+ **Theme Editor Testing**
80
+ - New settings in section/block schemas
81
+ - Setting validations and default values
82
+ - Live preview updates
83
+ - Block add/remove/reorder
84
+
85
+ **Edge Cases**
86
+ - Empty states (no content, no images)
87
+ - Maximum content (long text, many items)
88
+ - Missing or broken assets
89
+
90
+ **Accessibility**
91
+ - Keyboard navigation
92
+ - Screen reader compatibility
93
+ - Focus states and tab order
94
+ - Color contrast
95
+
96
+ **Browser/Device**
97
+ - Cross-browser compatibility
98
+ - Mobile touch interactions
99
+
100
+ Format example:
101
+ ```markdown
102
+ **Feature Functionality**
103
+ - [ ] Verify the "Show price" toggle appears in section settings
104
+ - [ ] Confirm price displays when toggle is enabled
105
+ - [ ] Confirm price is hidden when toggle is disabled
106
+
107
+ **Responsive Behavior**
108
+ - [ ] Test on mobile (< 768px) — items should stack vertically
109
+ - [ ] Test on desktop (≥ 768px) — items should display in a grid
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Step 4: Collect Additional Info
115
+
116
+ Ask the developer for the following (cannot be inferred from code):
117
+
118
+ 1. **Basecamp Links** — Links to relevant Basecamp Cards or Todos
119
+ 2. **Other Considerations** — Edge cases, known limitations, notes for reviewers
120
+ 3. **Demo Links** — Shopify store preview URL and theme editor/customizer URL
121
+ 4. **Additional Information** — Any other relevant context
122
+
123
+ Then present the generated testing steps and ask:
124
+ - Any steps to remove (not applicable)?
125
+ - Any additional scenarios to add?
126
+ - Any specific test data or configuration needed?
127
+
128
+ ---
129
+
130
+ ## Step 5: Review & Confirm
131
+
132
+ Present the complete PR description to the developer for final review. Ask them to confirm it's accurate or request changes.
133
+
134
+ ---
135
+
136
+ ## Step 6: Confirm PR Type
137
+
138
+ Ask the developer to confirm the project type, as this determines the reviewer(s):
139
+
140
+ | PR Type | Reviewer(s) |
141
+ |---------|------------|
142
+ | **Retainer** | `@matrjohnson` |
143
+ | **Statamic** | `@matrjohnson` (use admin + preview links) |
144
+ | **Project** | `@andisadiazl` |
145
+ | **Component Library** | `@matrjohnson`, `@andisadiazl` |
146
+
147
+ ---
148
+
149
+ ## Step 7: Create the Pull Request
150
+
151
+ Once confirmed, create the PR with GitHub CLI:
152
+
153
+ ```bash
154
+ gh pr create \
155
+ --base "{target_branch}" \
156
+ --head "{source_branch}" \
157
+ --title "{auto_generated_title}" \
158
+ --body "{generated_pr_body}" \
159
+ --reviewer "{reviewers}"
160
+ ```
161
+
162
+ - **On success**: Share the PR URL with the developer.
163
+ - **On failure**: Display the error and troubleshoot:
164
+ - Not authenticated → `gh auth status` / `gh auth login`
165
+ - Branch not pushed → `git push -u origin {source_branch}`
166
+ - No commits between branches → inform developer
167
+ - PR already exists → provide link to existing PR
168
+
169
+ ---
170
+
171
+ ## PR Body Template
172
+
173
+ Use this exact format for the `--body` parameter:
174
+
175
+ ```markdown
176
+ ### PR Summary:
177
+ {auto_generated_summary}
178
+
179
+ ### Tasks Included?
180
+ {basecamp_links}
181
+
182
+ ### What approach did you take?
183
+ {auto_generated_approach}
184
+
185
+ ### Other considerations
186
+ {considerations}
187
+
188
+ ### Testing steps/scenarios
189
+ {auto_generated_and_reviewed_testing_steps_as_checkboxes}
190
+
191
+ ### Demo links
192
+ - [Store]({store_url})
193
+ - [Editor]({editor_url})
194
+
195
+ ### Checklist
196
+ - [ ] Followed [theme code principles](https://github.com/Shopify/dawn/blob/main/.github/CONTRIBUTING.md#theme-code-principles)
197
+ - [ ] Linted with [Theme Check](https://github.com/Shopify/theme-check)
198
+ - [ ] Tested on [mobile](https://shopify.dev/themes/store/requirements#mobile-browser-requirements)
199
+ - [ ] Tested on [multiple browsers](https://shopify.dev/themes/store/requirements#desktop-browser-requirements)
200
+ - [ ] Tested for [accessibility](https://shopify.dev/themes/best-practices/accessibility)
201
+
202
+ ### Additional Information
203
+ {additional_info}
204
+ ```
205
+
206
+ ---
207
+
208
+ ## Workflow Summary
209
+
210
+ 1. Ask for source and target branches
211
+ 2. Fetch and analyze the diff
212
+ 3. Generate title, summary, approach, and testing steps
213
+ 4. Collect Basecamp links, demo URLs, and other info; review testing steps
214
+ 5. Present complete PR for final confirmation
215
+ 6. Confirm PR type and assign reviewer(s)
216
+ 7. Create the PR with `gh pr create` and share the URL
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Pull Request"
3
+ short_description: "Create a pull request"
4
+ default_prompt: "Use $pull-request to create a pull request."