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-class bd github / bd gitlab / bd jira sync.
  • For the GitHub backend: the gh CLI authenticated (gh auth status), used to mint an inline token (gh auth token).
  • uv — runs the scripts/ Python helper (PEP 723 inline deps).
  • git — remote-URL detection during init.

Usage

User-invocable (/beads-upstream). Operations:

  • /beads-upstream init — detect git remote → propose backend (github|gitlab|jira|none) → write <backend>.* config. none fully 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 local bd when 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 bd External: mapping), dry-run the push first, then bd close -r each bead with a destination-recording reason — a reversible tombstone, never bd 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 when custom.upstream.auto_hoist_followons=true and is restricted to the narrow signal (discovered-from into 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.granularitycoarse (default) files one tracking issue per plan-scale effort; granular files one per hoisted bead. Unset/unrecognized → coarse. coarse is the tested happy path; granular is implemented but not the tested-happy-path. They coexist: flipping coarsegranular leaves existing coarse trackers intact, since hoist is create-or-map via the External: dedup (an already-mapped bead updates in place, not re-created).
  • custom.upstream.auto_hoist_followonsdefault-deny (enabled only on literal true, mirroring custom.upstream.enabled); opt-in for the unattended no-prompt land-the-plane path, restricted to the narrow signal. Inspect both via upstream.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-*)