yf-beads-upstream
Configurable, GitHub-first upstream-tracking skill for beads. Pushes open/deferred beads to an issue tracker (GitHub/Git
Configurable, GitHub-first upstream-tracking skill for beads. Pushes open/deferred beads to an issue tracker (GitHub/GitLab/Jira) as a land-the-plane step, and enumerates upstream issues as the authoritative worklist on status/pull.
At a glance
- Category:
beads - Invocation:
/yf-beads-upstream(user-invocable) - Requires tools:
bd,uv,gh - Depends on skills:
yf-beads-extra - Source:
SKILL.md·README.md
When it fires
/yf-beads-upstream invoked; "set up upstream tracking" / "configure upstream" (init); mid-session intent to send beads to the issue tracker — "push beads upstream" / "push open work to GitHub" / "push/sync upstream" / "sync issues upstream" / "mirror this bead upstream" / "file/hoist this as a GitHub issue"; asking for project status, available work, or the worklist when upstream tracking is configured (status/pull). NOTE: "push/sync upstream" here means THIS `gh`-based issue mirror, which is ORTHOGONAL to `bd dolt push` (Dolt DB replication) — on push-upstream intent do NOT reach for `bd dolt push`.
When to skip it
routine local `bd ready` / `bd show` / `bd close` (use `beads`); direct-CLI `bd` scripting gotchas (use `yf-beads-extra`); authoring beads-backed skills (use `yf-beads-authoring`). The close-time / land-the-plane push trigger is NOT carried in this description — it lives in the always-loaded companion rule (protocols/UPSTREAM_TRACKING.md).
Prerequisites
bd(beads) >= 1.1.0 — provides first-classbd github/bd gitlab/bd jirasync.- For the GitHub backend: the
ghCLI authenticated (gh auth status), used to mint an inline token (gh auth token). uv— runs thescripts/Python helper (PEP 723 inline deps).git— remote-URL detection duringinit.
Usage
User-invocable (/beads-upstream). Operations:
/beads-upstream init— detect git remote → propose backend (github|gitlab|jira|none) → write<backend>.*config.nonefully disables upstream tracking (a first-class, re-enableable choice)./beads-upstream(push) — land-the-plane: push open/deferred beads upstream (scoped, dry-run-first)./beads-upstream status— enumerate the upstream worklist (or fall back to localbdwhen disabled).
The close-time / land-the-plane push trigger is not invoked by intent language; it is bound
by the always-loaded companion rule protocols/UPSTREAM_TRACKING.md.
Hoist / land-the-plane / un-hoist
At land-the-plane, follow-on beads can be hoisted upstream and removed locally so the local DB
stays "active work only." The scripts/upstream.py helper exposes:
hoist --issues <ids> --dest <d> [--apply]— ensure an upstream issue per granularity (create-or-map via the bdExternal:mapping), dry-run the push first, thenbd close -reach bead with a destination-recording reason — a reversible tombstone, neverbd delete.land --parent <id> --intake <ts> --dest <d> [--apply]— detect follow-ons under a plan subtree and hoist them. Default = propose-with-confirm (emit the batch, require--apply). The no-prompt path runs only whencustom.upstream.auto_hoist_followons=trueand is restricted to the narrow signal (discovered-frominto the subtree AND non-active). The broad signal (created-after-intake) may catch a bead still being worked, so it is never auto-hoisted.unhoist (--issues <ids> | --record <file>) [--apply]— reopen a wrongly-hoisted bead from its tombstone (bd update <id> --status open); the upstream issue stays.
Preserved safety invariants: never a bare bd <backend> sync (scoped --issues, dry-run first);
auth is inline-only, never persisted; removal is bd close -r, never bd delete.
Config knobs
custom.upstream.granularity—coarse(default) files one tracking issue per plan-scale effort;granularfiles one per hoisted bead. Unset/unrecognized →coarse.coarseis the tested happy path;granularis implemented but not the tested-happy-path. They coexist: flippingcoarse→granularleaves existing coarse trackers intact, since hoist is create-or-map via theExternal:dedup (an already-mapped bead updates in place, not re-created).custom.upstream.auto_hoist_followons— default-deny (enabled only on literaltrue, mirroringcustom.upstream.enabled); opt-in for the unattended no-prompt land-the-plane path, restricted to the narrow signal. Inspect both viaupstream.py config --json.
Behavior model
No phase model (utility skill). Three independent operations, each honoring the disabled (none) flag:
init → detect remote → propose backend → bd config set <backend>.* (or custom.upstream.enabled=false)
push → read config → disabled? no-op : enumerate open/deferred → --dry-run scoped push → confirm → push → record External: map
status → read config → disabled? local bd ready/list : enumerate upstream issues as worklist
hoist → ensure issue per granularity (create-or-map via External:) → --dry-run push → push → bd close -r (reversible)
land → detect follow-ons (narrow vs broad) → propose-with-confirm (default) | narrow-only no-prompt (opt-in) → hoist
unhoist → reopen wrongly-hoisted bead from close_reason tombstone (upstream issue stays)
Safety invariant (everywhere): never a bare bd <backend> sync; always --push-only + scoped
--issues/--parent + --dry-run first. Auth is inline-only, never persisted. Removal is
bd close -r (reversible tombstone), never bd delete.
Layout
skills/beads-upstream/
├── SKILL.md # entry point: trigger split, backends, init/push/status, safety invariants
├── README.md # this file
├── protocols/
│ ├── UPSTREAM_TRACKING.md # always-loaded companion rule (close-time trigger + safety invariant)
│ └── manifest.json # hash/version manifest for UPSTREAM_TRACKING.md
├── scripts/
│ ├── upstream.py # enumerate open/deferred beads + parse External: mappings (uv/PEP723)
│ └── manifest_update.py # recompute manifest hashes + bump versions (vendored)
└── spec/
├── operations.md # init/push/status behavioral contract (REQ-OP-*)
├── safety.md # never-bare-sync, inline auth, idempotency, disabled no-op (REQ-SAFE-*)
└── backends.md # backend coverage, flag divergence, trigger split (REQ-BE-*)