A true round-trip GitHub Issues integration for pm-cli: import, export, status-sync, search, and validate.
Import issues from any GitHub repo as pm items, export pm items back to GitHub (safely, dry-run by default), push status changes upstream, reach GitHub from pm search, and track GitHub provenance on each item. Works unauthenticated (60 req/hr); set GITHUB_TOKEN/GH_TOKEN or run gh auth login for 5000 req/hr and private repos.
pm install github.com/unbraind/pm-github --global| SDK capability | What it provides |
|---|---|
importers |
pm github import <owner/repo> — idempotent native import pipeline |
importers (exporter) |
pm github export — pm items → GitHub issues (dry-run by default; upsert) |
commands |
pm gh-issues import (legacy import alias), pm github sync (push status), pm github validate (diagnostics), pm github project list|fields|import|sync (Projects v2) |
schema |
declares github_url, github_number, github_state, github_author, github_created_at, github_updated_at item fields |
hooks |
afterCommand — opt-in sync reminder (PM_GITHUB_SYNC) for linked items |
preflight |
early warning when a mutating github command lacks a token |
search |
github search provider — pm search reaches GitHub for imported items |
pm github import unbraind/pm-cli
pm github import owner/repo --state all
pm github import owner/repo --labels bug,enhancement
pm github import owner/repo --since 2026-01-01T00:00:00Z # incremental sync
pm github import owner/repo --assignee octocat
pm github import owner/repo --milestone "v1.0"
pm github import owner/repo --include-prs
pm github import owner/repo --atomic
pm github import owner/repo --link-deps # map issue-body dependencies to pm edges
pm github import owner/repo --dry-runFlags
| Flag | Type | Description |
|---|---|---|
--all |
boolean | Include closed issues (shorthand for --state all) |
--state <state> |
string | open | closed | all (default: open) |
--labels <labels> |
string | Comma-separated label filter |
--since <iso> |
string | Only issues updated after this ISO timestamp (incremental sync) |
--assignee <login> |
string | Filter by assignee login |
--milestone <name> |
string | Filter by milestone title |
--include-prs |
boolean | Include pull requests (default: skip PRs) |
--skip-drafts |
boolean | Exclude draft pull requests (only meaningful with --include-prs) |
--with-comments |
boolean | Fetch issue comments and append them to the item body |
--comments-mode <mode> |
body|annotations|both |
How fetched GitHub comments are persisted (default body). annotations syncs comments into the pm item's native comments collection via the SDK; both writes the body section AND native comments. annotations/both are idempotent on re-import (dedupe by GitHub comment id) |
--atomic |
boolean | Commit every create, update, close, and reopen in one workspace-writer-locked, crash-resumable transaction (requires pm CLI/SDK >=2026.7.20). Normal failure compensation restores updated/closed items and deletes newly created items; an incomplete compensation is reported explicitly for retry or repair. |
--link-deps |
boolean | After import, map dependency references in issue bodies (Blocked by #N, Depends on owner/repo#N, Blocks #N) to pm dependency edges between the linked items. Idempotent, best-effort, and re-runnable; see Dependency linking below. |
--dry-run |
boolean | Preview without writing |
--type <type> |
string | Override pm item type (default: Issue) |
Each imported item records GitHub provenance: the gh:owner/repo#N idempotency tag, a github_author:<login> tag, and an enriched description (author @<login> · state reason <reason> · created <iso> · updated <iso>). GitHub issues closed as not_planned import as pm canceled instead of closed, preserving the difference between completed work and deliberately dropped work. The integration declares the github_url, github_number, github_state, github_author, github_created_at, and github_updated_at schema fields.
--atomic derives its durable transaction identity from the repository, complete rendered issue state, and exact ordered mutation plan (including target item ids), and derives each create id from the stable owner/repo#number external key rather than fetch position. Retrying the same response in a different order therefore resumes without duplicates; changed content, workspace prefixes, or resolved targets create a fresh compatible transaction. Native comment annotations run only after the item transaction commits and retain their cross-process deduplication lock.
By default (--comments-mode body, or --with-comments), fetched GitHub issue comments are flattened into the item body as blockquoted markdown under a ### GitHub comments (N) heading — the historical behavior, byte-identical across releases.
--comments-mode annotations instead syncs each GitHub comment into the pm item's native comments collection via the public SDK comments() primitive, so agents get structured, queryable comments (pm comments <id>) instead of body-embedded text. Each stored comment carries a hidden marker with the GitHub comment id (<!-- pm-github:comment:N -->), so re-running import is idempotent — already-synced comments are skipped and never duplicated. --comments-mode both writes the body section and the native comments.
When --with-comments is combined with --comments-mode annotations, the two are reconciled to both (the legacy flag asks for body embedding, the mode asks for native comments → both), so neither is silently dropped.
pm github import owner/repo --comments-mode annotations # native comments only
pm github import owner/repo --comments-mode both # body section + native comments
pm github import owner/repo --with-comments # legacy body embedding (default shape)
pm github import owner/repo --with-comments --comments-mode annotations # same as --comments-mode bothGitHub issue authors declare cross-issue dependencies in prose — Blocked by #12, Depends on owner/repo#5, Blocks #9. A flat import throws that structure away, leaving pm items with no blocker graph. --link-deps runs an opt-in second pass that parses those references from each issue's body and materializes them as pm dependency edges between the corresponding pm items, resolved through the same gh:owner/repo#N provenance tags the import writes.
The effect is context you can act on: pm next and pm deps surface the real ready/blocked ordering instead of an undifferentiated list.
pm github import owner/repo --link-deps
pm github import owner/repo --atomic --link-deps
pm github import owner/repo --link-deps --dry-run # reports candidate reference count onlyReference grammar (case-insensitive; -/: glue tolerated; multiple refs like #1, #2 and #3):
| Phrase | pm edge on the source item |
|---|---|
Blocked by #N |
blocked_by → the referenced item |
Depends on #N |
blocked_by → the referenced item |
Blocks #N |
blocks → the referenced item |
Both bare #N (resolved against the imported repo) and explicit owner/repo#N cross-repo references are supported, the latter only when that issue is also present in the workspace.
Guarantees
- Idempotent — edges dedupe by id + kind, so re-running import never duplicates them.
- Path-agnostic — runs identically after the normal and
--atomicimport. - Safe — references inside fenced/inline code spans are ignored; self-references and references to issues not in the workspace are skipped (the latter counted as
unresolvedDependencyRefs); a failed individual edge never fails the import (it is reported independencyLinkFailuresand the pass is re-runnable). - Cycle-aware, not cycle-blocking — ordering cycles the mapped edges introduce are reported in
orderingCycleWarnings(computed via the pm SDKcollectNewOrderingCycleWarningsadvisory over the workspace before/after the pass) rather than rejected, mirroring the SDK's warn-don't-reject contract for legacy graph debt.
The import result gains linkedDependencies, unresolvedDependencyRefs, orderingCycleWarnings (and dependencyLinkFailures when non-empty); a --dry-run --link-deps result reports wouldLinkDependencyCandidates.
Safe by default. Export previews the create/update plan and writes nothing unless you explicitly opt in with --apply and name a --repo. With a --repo, items already linked to an issue in that repo (via the gh:owner/repo#N provenance tag) are updated (upsert) instead of duplicated.
pm github export --repo owner/repo # DRY-RUN: print the create/update plan, write nothing
pm github export --repo owner/repo --format md
pm github export --repo owner/repo --ids pm-12,pm-34
pm --json github export --repo owner/repo # return the plan as JSON
pm github export --repo owner/repo --apply # actually create/update issues (requires a token)
pm github export --repo owner/repo --ids pm-12,pm-34 --apply| Flag | Type | Description |
|---|---|---|
--repo <owner/repo> |
string | Target repo; decides create-vs-update and is required for --apply |
--ids <pm-1,pm-2> |
string | Scope export to specific pm item IDs (comma-separated); unknown IDs fail fast |
--format <json|md> |
string | Dry-run output format (default: json) |
--apply |
boolean | Perform real GitHub writes (alias: --no-dry-run, legacy --push). Requires a token + --repo |
--dry-run |
boolean | Force preview even alongside --apply (dry-run always wins) |
Push pm status changes back to GitHub: close/reopen the linked issue to match the pm item's status. Requires a token and explicit --repo.
pm github sync --repo owner/repo --dry-run # preview the close/reopen plan
pm github sync --repo owner/repo --ids pm-12,pm-34 --dry-run
pm github sync --repo owner/repo # push the changes--ids scopes sync to specific pm item IDs (comma-separated). Unknown IDs fail fast so agent runs do not silently skip typoed targets.
GitHub Projects v2 boards are a GraphQL-only surface, distinct from Issues. These commands keep a pm workspace and a Projects v2 board in lockstep — project management = context management — without ever losing data: nothing is deleted or archived on either side, every action is idempotent via a gh-project:owner/number#itemId provenance tag, and a pm status (or board Status) that has no clear counterpart is skipped, never guessed.
Needs a token with project/read:project scope (GITHUB_TOKEN/GH_TOKEN or gh auth login).
Discover the Projects v2 owned by a user or org (read-only).
pm github project list unbraind
pm github project list unbraind --jsonIntrospect a board's fields and — crucially — its Status single-select options, so you can design a --status-map (read-only).
pm github project fields unbraind/5Import every board item (draft issues included) as pm items. Idempotent: an item already linked (by project tag, or by the gh:repo#N issue it wraps) is updated, not duplicated. The board's Status option maps to the pm status.
pm github project import unbraind/5 --dry-run
pm github project import unbraind/5
pm github project import unbraind/5 --status-map in_progress=Doing,closed=ShippedBidirectionally sync pm items and a board. Safe by default: with no --apply it previews both directions and writes nothing.
--push(pm → board): adds missing pm items to the board — attaching the existing GitHub issue when the pm item is issue-linked, otherwise creating a draft issue — and sets each item's Status from its pm status.--pull(board → pm): updates each linked pm item's status from the board's Status column (status only — never touches title/body/tags).--applywrites; with neither direction flag it defaults to--push(so pm is never mutated silently).--prefer pm|githubresolves conflicts when applying both directions (defaultpm).
pm github project sync unbraind/5 # preview both directions
pm github project sync unbraind/5 --push --apply # pm → board
pm github project sync unbraind/5 --pull --apply # board → pm
pm github project sync unbraind/5 --push --pull --apply --prefer pm
pm github project sync unbraind/5 --push --apply --ids pm-1,pm-2 # scope by id
pm github project sync unbraind/5 --push --apply --no-add-missing # only reconcile linked itemsThe GitHub Projects v2 item node id is case-sensitive but pm normalizes tag values to lowercase, so the node id is hex-encoded inside the provenance tag to round-trip losslessly. This is what makes re-sync idempotent instead of silently double-adding.
Registers a github search provider so pm search ... --semantic can reach GitHub. It asks GitHub which issues in the target repo match your query, then returns hits for the pm items you've already imported from those issues (matched by the gh:owner/repo#N provenance tag).
pm config project set ... # set search.provider = "github" in .agents/pm/settings.json
export PM_GITHUB_REPO=owner/repo # or pass the repo another way
pm search "uppercase dashes" --semantic # hits = imported items whose upstream issue matchesEnable it by setting search.provider to "github" in .agents/pm/settings.json and pointing it at a repo via the PM_GITHUB_REPO env var.
Read-only check of the integration: gh CLI presence, token resolvability (and source), and—with --repo—repo accessibility. When --repo is given it also surfaces the remaining GitHub API quota (X-RateLimit-Remaining/Limit/Reset) and warns when the quota is running low. Never mutates anything.
pm github validate
pm github validate --repo owner/repo
pm --json github validate --repo owner/repoMIT
This package is release-ready for GitHub, npm, and Bun-compatible installs. CI runs type checking, build, production dependency audit, package packing, Bun install verification, and pm-changelog validation. The daily release workflow publishes only when commits exist after the latest release tag and uses pm-changelog to generate CHANGELOG.md and GitHub release notes.
This repo tracks its project management in .agents/pm/ and ships a committed .gitattributes
that maps those tracker artifacts to pm-cli's field-aware Git merge drivers, so concurrent-branch
tracker edits merge cleanly. The driver definitions live in per-clone Git config; npm install /
npm ci wires them automatically via the prepare script (a portable Node guard, scripts/prepare-merge-driver.mjs: it runs pm merge install only when the pm CLI is on PATH, and no-ops cleanly otherwise so production / --omit=dev installs are not broken; being Node-based it behaves identically on POSIX shells and Windows cmd.exe). To (re)run
manually: npm run merge:install.
After merging a branch that touched .agents/pm/, reconcile any residual history-hash drift with
pm merge reconcile (pm-cli ≥ 2026.7.22): preview with pm merge reconcile --dry-run, apply with
pm merge reconcile --message "post-merge reconcile", then confirm with pm validate, which scans the
whole tracker and flags remaining history drift across every affected item (pm merge reconcile
itself lists each affected stream in its output; pm history --verify <id> spot-checks one item). The field-aware driver already unions every author's
content, so reconcile only re-greens the hash chain (no data loss) — see the authoritative
pm-cli merge-safety guide. The
older blunt pm history-repair --all remains available as a lower-level primitive.