视觉画布设计
canvas-design
基于设计原则创作静态视觉作品,用于海报与品牌图形。
prd-to-issues【文档与办公】Converts a product requirements document into a reviewed set of GitHub issues: end-to-end increments sized for one pull request, each with checkable acceptance criteria, traced back to the PRD's requirements and linked as sub-issues with blocked-by relationships. Use when the user says "break this PRD into issues", "create tickets from the PRD", "tur…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
A PRD describes what a feature must do; it is not something a developer or a coding agent can pick up and ship. This skill turns one into a backlog: a handful of GitHub issues that each put working behaviour in front of a user, fit in one pull request, and carry acceptance criteria precise enough to check. Every requirement is traced to an issue, the order of work is stored as real GitHub relationships (sub-issue of the PRD, blocked by its prerequisites), and nothing is created until the user has approved the breakdown.
Ask only for what cannot be looked up: where the PRD is (issue number, file, or pasted text) and which repository receives the issues. Then read the rest from the project:
gh --version # 2.94.0+ has --parent / --blocked-by / --type
gh repo view --json nameWithOwner,hasIssuesEnabled
gh issue view 142 --json number,title,body,comments,labels,milestone,subIssues
gh label list --limit 200 --json name,description
gh api "repos/{owner}/{repo}/milestones" --jq '.[].title'
gh fills {owner} and {repo} from the current directory. Read the comments as well as the body: decisions made after the PRD was written usually live there. On gh older than 2.94.0 drop subIssues from the field list. If subIssues.totalCount is above zero, the PRD was already broken down once: list what exists and ask whether to extend it instead of creating a second set.
Number every requirement the PRD states (R1, R2, …), keeping the PRD's own numbering if it has one. Separately list the stated non-goals and every question the PRD leaves open. A requirement that cannot be verified as written ("search should be fast") gets a proposed measurable form, shown to the user as a question, not silently decided.
Find the route a request for this feature takes through the system: storage, domain logic, API, interface, tests. Note what already exists and can be reused, the test command, and any conventions an issue should point at. This is what makes an issue startable without a second investigation.
Each issue is a vertical slice: one user-visible behaviour carried through every layer it needs, tests included. "Add the tables", "build the API", "build the UI" are layers, not increments; nobody can try a layer.
Give every issue a readiness state. Ready means it can be built from the issue text and the PRD alone, including by an unattended agent. Needs decision means a named person must choose something first; say who and what.
Record only true prerequisites, so the dependency graph stays shallow and work can run in parallel. No cycles. For each issue, write what it is blocked by; for the whole set, say which issues can start today.
Show the breakdown as a table (title, requirements covered, readiness, blocked by) followed by a coverage check: every R appears in at least one issue, every non-goal appears in none, every open question has a decision issue or an answer. Then ask what would change the plan: is any item too large to review in one pull request, is any dependency wrong, should anything be cut or deferred, which label and milestone should the set carry. Revise until the user approves. Creating issues notifies watchers and is tedious to undo, so do not create a draft set "to look at".
Write each body to a file in a temporary directory and create the issues in dependency order, so blockers have numbers before the issues that need them. gh issue create prints the new issue's URL on stdout; the number is its last path segment.
tmp=$(mktemp -d)
gh label create "prd:saved-searches" --description "Work items for PRD #142" --color 1D76DB --force
url=$(gh issue create --title "Save the current filter and reopen it from the sidebar" \
--body-file "$tmp/01-save-and-reopen.md" --label "prd:saved-searches" --parent 142)
first=${url##*/}
gh issue create --title "Rename and delete a saved search" \
--body-file "$tmp/02-rename-delete.md" --label "prd:saved-searches" \
--parent 142 --blocked-by "$first"
--force makes label creation safe to repeat. --blocked-by takes one number or a comma-separated list. Add --milestone "Q4 search" or --type Feature only if that milestone or issue type exists in the repository. Use the title the user approved, written as an outcome, not as a task.
Body shape (plain Markdown, same headings in every issue):
Part of #142 · covers R1, R2.gh issue view 142 --json subIssues --jq '.subIssues.nodes[] | "#\(.number) \(.title) [\(.state)]"'
gh issue comment 142 --body-file "$tmp/summary.md"
The summary comment lists the created issues in suggested order, which can start now, and which wait on a decision. Leave the PRD's own body and state untouched. Finish by giving the user the same list with URLs.
Repository fernhill/tracker-web, PRD in issue #142 "Saved searches with email alerts", gh 2.102.0. The ledger has seven requirements: R1 save the current ticket filter under a name; R2 saved searches appear in the sidebar and re-run on click; R3 rename and delete; R4 opt in to a daily email digest per search; R5 the digest lists tickets that newly match, and is not sent when nothing is new; R6 one-click unsubscribe in every digest; R7 at most 25 saved searches per user. Non-goals: sharing searches with teammates, instant alerts. Open question: when the digest is sent.
Breakdown shown to the user:
| # | Issue | Covers | Readiness | Blocked by |
|---|---|---|---|---|
| 1 | Save the current filter and reopen it from the sidebar | R1, R2 | Ready | — |
| 2 | Rename and delete a saved search | R3 | Ready | 1 |
| 3 | Refuse a 26th saved search with a clear message | R7 | Ready | 1 |
| 4 | Decide digest send time and time-zone handling | R4 (open question) | Needs decision: Priya Nair | — |
| 5 | Daily digest email for a saved search | R4, R5 | Ready once 4 is answered | 1, 4 |
| 6 | One-click unsubscribe from a digest | R6 | Ready | 5 |
Coverage: R1–R7 all assigned; no issue touches sharing or instant alerts. Issues 1 and 4 can start today; 2 and 3 run in parallel after 1.
After approval the issues are created as #151–#156. Body of #151:
## Outcome
A signed-in user can save the filter currently applied to the ticket list under a
name, sees it in the sidebar, and clicking it applies the same filter again.
Part of #142 · covers R1, R2
## Scope
- Store a saved search (name + serialized filter) per user
- `POST /api/saved-searches` and `GET /api/saved-searches`
- "Save search" button on the ticket list, "Saved searches" group in the sidebar
- Not here: rename and delete (#152), the 25-search limit (#153), digests (#155)
## Acceptance criteria
- [ ] With status=open and assignee=me applied, saving as "My open tickets" adds a
sidebar entry without a page reload
- [ ] In a new session, clicking the entry shows the same tickets as setting the
filter by hand
- [ ] A request made with another user's session returns none of my saved searches
- [ ] An empty name or one longer than 80 characters is rejected with a field error
- [ ] API tests cover both endpoints; a component test covers the sidebar group
## Notes for the implementer
`useTicketFilters` already serializes filters to the URL query string. Store that
string instead of introducing a second format.
## Depends on
Nothing — can start now.
The digest issue is created with both of its prerequisites:
gh issue create --title "Daily digest email for a saved search" \
--body-file "$tmp/05-daily-digest.md" --label "prd:saved-searches" \
--parent 142 --blocked-by 151,154
Repository marlowe-freight/dispatch, PRD at docs/prd/csv-import.md, gh 2.63.0. There is no parent issue, so the user is asked whether to open a tracking issue that links to the file; they agree, and it becomes #210. The approved breakdown has four issues: upload a CSV and import valid rows (R1, R2), report rejected rows with line numbers (R3), map custom column names (R4), import files over 10,000 rows in the background (R5, blocked by the first).
This gh version has no --parent or --blocked-by, so the issues (#211–#214) are created with title, body file and label only, and linked through the REST API afterwards. Both endpoints want the issue's database id, not its number:
for n in 211 212 213 214; do
id=$(gh api "repos/{owner}/{repo}/issues/$n" --jq .id)
gh api "repos/{owner}/{repo}/issues/210/sub_issues" -F sub_issue_id="$id" --silent
done
blocker=$(gh api "repos/{owner}/{repo}/issues/211" --jq .id)
gh api "repos/{owner}/{repo}/issues/214/dependencies/blocked_by" -F issue_id="$blocker" --silent
-F sends the value as a JSON integer, which these endpoints require, and adding a field switches the request to POST. Report to the user: tracking issue #210 with four sub-issues; #211, #212 and #213 can start now; #214 waits for #211.
gh issue list --label "prd:saved-searches" --state all --json number,title) and skip titles that already exist.gh issue create fails on an unknown label or milestone. Create the label first; never create milestones or types without asking.gh already has (or GH_TOKEN from the environment). If gh auth status reports no login, stop and ask; do not look for tokens on disk.