GitHub Backlog Sync
Mirror the local docs/product/ backlog — the Epics, User Stories, Tasks, and Implementation
Plans that [[product-item]] scaffolds — out to GitHub Issues, and optionally onto a
GitHub Project board. The local Markdown files stay the source of truth; each Issue is a
mirror that links back. Sync is idempotent: the issue number is recorded into the artifact's
frontmatter (github_issue:), so re-running updates the existing Issue instead of duplicating it.
Voice — read this first
You are a capable agent, not a script-runner. Everything below is guidance to apply with
judgment, not a checklist to recite. In practice that means:
- Be concise and human. Scale the response to the ask — a one-line "is this worth it?" gets a
short, warm answer, not a wall of headings. Lead with the answer.
- Use initiative. Do the cheap, smart things a thoughtful colleague would: sanity-check the
repo exists before planning, notice when artifacts are empty stubs, ask what's really behind the
request. The bundled script handles the mechanics so you can spend attention here.
- Recommend, don't gatekeep. Give your honest read (including "I'd stay local"), then let the
user decide — and offer to do it their way if they disagree.
When to invoke
- The user asks to push/mirror/sync
docs/product/ artifacts to GitHub Issues or a Project.
- New or changed artifacts need their Issues created or brought up to date.
- The user asks whether the backlog is big enough to move onto GitHub (see the metric below).
If there's no docs/product/ backlog yet, there's nothing to sync — point them at
[[product-item]] to create artifacts first.
Is GitHub even worth it? (the size metric)
A local Markdown backlog is plenty for small efforts; pushing everything to GitHub adds noise and
a second place to look. So when the backlog is small, say so and recommend staying local —
but always offer to do it anyway if they want, and if they're just asking (not instructing),
ask what's prompting it, in case there's an unspoken need a board wouldn't solve.
Lead with the numbers (the script prints them), then recommend GitHub when any hold:
- more than ~15–20 open tracked artifacts, or
- more than 1 active Epic, or
- more than 1 person/agent on the backlog (shared, external visibility), or
- work spanning multiple sprints / more than ~2 weeks, or
- external stakeholders need read access.
A Project board specifically earns its keep once there's more than 1 Epic and you want a
cross-cutting status view.
Backend & pre-flight
- Prefer the
gh CLI (installed + authenticated). The bundled script drives it.
- Confirm the repo resolves before planning — a quick
gh repo view <owner/name> catches a
typo or an access problem cheaply, before you've promised anything. Default the repo to the
current git remote's origin when it's unambiguous (that's usually the one they mean). If the
check fails, surface it prominently — a clear heading, the command you ran, and its output —
so it's obvious and actionable, and stop until it's resolved.
- If
gh is absent but the GitHub MCP tools are connected, follow MCP fallback below.
- If neither is available, say what to set up. Don't guess.
Inputs
Ask only for what's missing: repo (owner/name, default the origin remote), product dir
(default docs/product), an optional project number, and any scope limit (a type, an Epic
and its children, a single id).
Procedure
Pre-flight the backend and repo (above).
Dry-run — always first. Run the bundled script in plan-only mode. It ships with this skill;
run it in place from assets/scripts/sync_github_items.py (substitute this skill's real
install path for <skill>). It takes the backlog dir as an argument and finds its template
relative to itself, so it needs no bootstrap:
uv run --with pyyaml python <skill>/assets/scripts/sync_github_items.py docs/product --repo <owner/name> [--project <N>]
It writes nothing and prints: the backlog counts; per artifact whether it's a create (no
github_issue: yet) or update; on a re-sync, whether each already-synced artifact has
changed since last sync (unchanged ones are skipped); and advisory flags for stubs and
status drift (see below).
Report and confirm — as "what I need before I sync". Give a tight summary: the size read
(recommend, don't gatekeep), what will be created vs. updated, and — because this reaches a
shared, external system — an explicit note that already-synced artifacts are updated, not
duplicated. Then list, as clear actions, anything you need from the user (repo confirmation,
project details, how to handle stubs/drift). Offer to walk through them interactively. Get an
explicit go-ahead before writing.
Apply on confirmation by re-running with --apply. The script creates/updates issues, sets
open/closed state from status, (with --project) puts them on the board with the right status,
and records github_issue: / github_project_item: / github_synced_digest: back into
frontmatter. Those frontmatter writes are the only local changes — leave committing to the user.
Report what was created, updated, and skipped, with issue URLs.
Before you push — three things to catch
The dry-run flags these; handle them with judgment rather than plowing ahead:
- Stubs. An artifact still full of
<...> template placeholders becomes a low-signal Issue.
When you see stubs, say so, and rather than pushing noise, offer to fill them out first —
hand that to [[product-item]], which owns artifact content (a short question-and-answer pass
per artifact). Also worth asking: does the real content live somewhere else (a doc, a
spreadsheet, someone's notes) that should be brought in first?
- Status drift. The local
status: is free text and can drift off the canonical set
(Proposed, Ready, In Progress, In Review, Done, Archived). The script flags any that
don't match; surface near-duplicates and reconcile them before mapping onto a Project field,
rather than silently creating odd columns.
- What changed since last sync. On a re-sync the script marks each synced artifact
changed/unchanged (by digesting its title + status + body) and skips the unchanged ones. Use
that to tell the user what actually moved, not just "re-synced everything".
Mapping rules
- Title —
`<ID>` <title> (e.g. EPIC-004 Payments platform), greppable by id.
- Body — rendered from
assets/templates/issue-body-template.md: a backlink, type/status/
parent, and the artifact's content. Regenerated on each update; discussion lives in Issue
comments, which the sync never touches.
- Label — the artifact type (
epic / story / task / plan).
- State —
Done / Archived close the Issue; every other status leaves it open.
- Project status (with
--project) — the artifact status: maps 1:1 to a Project
single-select field of the same name; create missing options once.
Safety
- The only local writes are the bookkeeping frontmatter fields above — never an artifact's other
content, its parent, or the roadmap.
- Plan-only by default; mutations need
--apply and the user's OK.
- One-way sync (local → GitHub). Changes made on GitHub aren't pulled back; the local artifact
wins on the next sync.
MCP fallback (no gh)
When only the GitHub MCP server is available, do what the script would, in the same order, using
the MCP Issue/Project tools: read the artifacts, compute create-vs-update from github_issue:,
apply the mapping rules, then record the returned issue number (and project item id) back into
frontmatter. Keep the same pre-flight, plan-then-confirm, stub/drift awareness.
Acceptance checklist
- The repo was confirmed to resolve before any plan was promised.
- A dry-run was shown and confirmed before any GitHub write.
- The size read was given as a recommendation (with an offer to proceed anyway), not a gate.
- Stubs and status drift were surfaced, not silently pushed.
- No duplicate issues: artifacts with a
github_issue: were updated (or skipped if unchanged).
- Every synced artifact carries its
github_issue: (and github_project_item: with a Project)
afterward; issue state matches status; only frontmatter was written locally.
Related: consumes artifacts from [[product-item]]; complements [[shared-task-tracking]].
1---2name: github-backlog-sync3description: Use when mirroring a local product backlog (docs/product/ Epics, User Stories, Tasks, Plans created by /product-item) out to GitHub Issues, or onto a GitHub Project board — creating or updating issues, keeping status in sync, and recording the issue number back on each artifact. Trigger whenever someone says "push these to GitHub Issues", "sync the backlog to GitHub", "create issues for these tasks/epics", "put this on the GitHub Project board", "mirror docs/product to GitHub", or asks whether the backlog is big enough to move onto GitHub. The local Markdown stays the source of truth; GitHub is the mirror.4---56# GitHub Backlog Sync78Mirror the local `docs/product/` backlog — the Epics, User Stories, Tasks, and Implementation9Plans that `[[product-item]]` scaffolds — out to **GitHub Issues**, and optionally onto a10**GitHub Project** board. The local Markdown files stay the **source of truth**; each Issue is a11mirror that links back. Sync is **idempotent**: the issue number is recorded into the artifact's12frontmatter (`github_issue:`), so re-running updates the existing Issue instead of duplicating it.1314## Voice — read this first1516You are a capable agent, not a script-runner. Everything below is guidance to apply with17judgment, not a checklist to recite. In practice that means:1819- **Be concise and human.** Scale the response to the ask — a one-line "is this worth it?" gets a20 short, warm answer, not a wall of headings. Lead with the answer.21- **Use initiative.** Do the cheap, smart things a thoughtful colleague would: sanity-check the22 repo exists before planning, notice when artifacts are empty stubs, ask what's really behind the23 request. The bundled script handles the mechanics so you can spend attention here.24- **Recommend, don't gatekeep.** Give your honest read (including "I'd stay local"), then let the25 user decide — and offer to do it their way if they disagree.2627## When to invoke2829- The user asks to push/mirror/sync `docs/product/` artifacts to GitHub Issues or a Project.30- New or changed artifacts need their Issues created or brought up to date.31- The user asks whether the backlog is big enough to move onto GitHub (see the metric below).3233If there's no `docs/product/` backlog yet, there's nothing to sync — point them at34`[[product-item]]` to create artifacts first.3536## Is GitHub even worth it? (the size metric)3738A local Markdown backlog is plenty for small efforts; pushing everything to GitHub adds noise and39a second place to look. So when the backlog is small, **say so and recommend staying local** —40but always **offer to do it anyway** if they want, and if they're just *asking* (not instructing),41ask what's prompting it, in case there's an unspoken need a board wouldn't solve.4243Lead with the numbers (the script prints them), then recommend GitHub when **any** hold:4445- more than ~**15–20 open** tracked artifacts, or46- more than **1 active Epic**, or47- more than **1 person/agent** on the backlog (shared, external visibility), or48- work **spanning multiple sprints** / more than ~2 weeks, or49- **external stakeholders** need read access.5051A **Project board** specifically earns its keep once there's **more than 1 Epic** and you want a52cross-cutting status view.5354## Backend & pre-flight5556- Prefer the **`gh` CLI** (installed + authenticated). The bundled script drives it.57- **Confirm the repo resolves before planning** — a quick `gh repo view <owner/name>` catches a58 typo or an access problem cheaply, before you've promised anything. Default the repo to the59 current git remote's `origin` when it's unambiguous (that's usually the one they mean). If the60 check fails, surface it **prominently** — a clear heading, the command you ran, and its output —61 so it's obvious and actionable, and stop until it's resolved.62- If `gh` is absent but the **GitHub MCP** tools are connected, follow **MCP fallback** below.63- If neither is available, say what to set up. Don't guess.6465## Inputs6667Ask only for what's missing: **repo** (`owner/name`, default the origin remote), **product dir**68(default `docs/product`), an optional **project** number, and any **scope** limit (a type, an Epic69and its children, a single id).7071## Procedure72731. **Pre-flight** the backend and repo (above).742. **Dry-run — always first.** Run the bundled script in plan-only mode. It ships with this skill;75 run it **in place** from `assets/scripts/sync_github_items.py` (substitute this skill's real76 install path for `<skill>`). It takes the backlog dir as an argument and finds its template77 relative to itself, so it needs no bootstrap:7879 ```bash80 uv run --with pyyaml python <skill>/assets/scripts/sync_github_items.py docs/product --repo <owner/name> [--project <N>]81 ```8283 It writes nothing and prints: the backlog counts; per artifact whether it's a **create** (no84 `github_issue:` yet) or **update**; on a re-sync, whether each already-synced artifact has85 **changed** since last sync (unchanged ones are skipped); and advisory flags for **stubs** and86 **status drift** (see below).873. **Report and confirm — as "what I need before I sync".** Give a tight summary: the size read88 (recommend, don't gatekeep), what will be created vs. updated, and — because this reaches a89 shared, external system — an explicit note that already-synced artifacts are **updated, not90 duplicated**. Then list, as clear actions, anything you need from the user (repo confirmation,91 project details, how to handle stubs/drift). Offer to walk through them interactively. Get an92 explicit go-ahead before writing.934. **Apply** on confirmation by re-running with `--apply`. The script creates/updates issues, sets94 open/closed state from status, (with `--project`) puts them on the board with the right status,95 and records `github_issue:` / `github_project_item:` / `github_synced_digest:` back into96 frontmatter. Those frontmatter writes are the only local changes — leave committing to the user.975. **Report** what was created, updated, and skipped, with issue URLs.9899## Before you push — three things to catch100101The dry-run flags these; handle them with judgment rather than plowing ahead:102103- **Stubs.** An artifact still full of `<...>` template placeholders becomes a low-signal Issue.104 When you see stubs, say so, and rather than pushing noise, **offer to fill them out first** —105 hand that to `[[product-item]]`, which owns artifact content (a short question-and-answer pass106 per artifact). Also worth asking: does the real content live somewhere else (a doc, a107 spreadsheet, someone's notes) that should be brought in first?108- **Status drift.** The local `status:` is free text and can drift off the canonical set109 (`Proposed`, `Ready`, `In Progress`, `In Review`, `Done`, `Archived`). The script flags any that110 don't match; surface near-duplicates and reconcile them **before** mapping onto a Project field,111 rather than silently creating odd columns.112- **What changed since last sync.** On a re-sync the script marks each synced artifact113 changed/unchanged (by digesting its title + status + body) and skips the unchanged ones. Use114 that to tell the user what actually moved, not just "re-synced everything".115116## Mapping rules117118- **Title** — `` `<ID>` <title> `` (e.g. `EPIC-004 Payments platform`), greppable by id.119- **Body** — rendered from `assets/templates/issue-body-template.md`: a backlink, type/status/120 parent, and the artifact's content. Regenerated on each update; discussion lives in Issue121 **comments**, which the sync never touches.122- **Label** — the artifact type (`epic` / `story` / `task` / `plan`).123- **State** — `Done` / `Archived` close the Issue; every other status leaves it open.124- **Project status** (with `--project`) — the artifact `status:` maps 1:1 to a Project125 single-select field of the same name; create missing options once.126127## Safety128129- The only local writes are the bookkeeping frontmatter fields above — never an artifact's other130 content, its parent, or the roadmap.131- Plan-only by default; mutations need `--apply` and the user's OK.132- One-way sync (local → GitHub). Changes made **on** GitHub aren't pulled back; the local artifact133 wins on the next sync.134135## MCP fallback (no `gh`)136137When only the GitHub MCP server is available, do what the script would, in the same order, using138the MCP Issue/Project tools: read the artifacts, compute create-vs-update from `github_issue:`,139apply the mapping rules, then record the returned issue number (and project item id) back into140frontmatter. Keep the same pre-flight, plan-then-confirm, stub/drift awareness.141142## Acceptance checklist143144- The repo was confirmed to resolve before any plan was promised.145- A dry-run was shown and confirmed **before** any GitHub write.146- The size read was given as a recommendation (with an offer to proceed anyway), not a gate.147- Stubs and status drift were surfaced, not silently pushed.148- No duplicate issues: artifacts with a `github_issue:` were updated (or skipped if unchanged).149- Every synced artifact carries its `github_issue:` (and `github_project_item:` with a Project)150 afterward; issue state matches status; only frontmatter was written locally.151152Related: consumes artifacts from `[[product-item]]`; complements `[[shared-task-tracking]]`.