Skip to content

Sync issue dependencies (blocked-by / blocks) #26

Description

@androidand

Sync issue dependencies (blocked-by / blocks)

Why

Work that spans a backend and a frontend almost always has a direction: the
frontend change can't ship until the backend change lands. Today specsync can
record that two specs are "related" (## Related), but "related" is symmetric and
loses the dependency direction — the exact thing a planner and an agent need to
sequence the work.

GitHub now models this as a first-class relationship: issue dependencies
(blockedBy / blocking) went GA in 2025, with real mutations (addBlockedBy,
removeBlockedBy) and a summary field (issueDependenciesSummary), read/written
via gh api graphql (there is no native gh issue flag yet). So a directed
local edge should project onto a real dependency, not be flattened into "related".

What Changes

  • Add directed typed edges to links.md: ## Blocked by and ## Blocks
    (the inverse). Entries take the usual forms (#N / owner/repo#N / URL), so a
    backend issue in another repo can block a frontend change.
  • On sync, project each ## Blocked by entry onto a GitHub issue dependency
    via addBlockedBy, cross-repo by node id. ## Blocks is the same edge seen from
    the other end (write it as the blocker's blockedBy).
  • Reconcile dependencies both ways against a last-synced baseline (a
    gitignored snapshot in .specsync/, the merge base). Each edge is binary, so the
    3-way reconcile is unambiguous: an edge added in links.md is pushed
    (addBlockedBy); a dependency added on GitHub by a human is pulled into
    links.md
    ; an edge removed on either side is removed from the other
    (removeBlockedBy for the GitHub side). Dependencies converge in both
    directions — none go stale, and UI-added dependencies are honored, not discarded.

Out of scope / explicitly deferred

  • Parent/sub-issue hierarchy (→ epic-and-subissue-projection)
  • duplicateOf and other relationship types — add when a real need appears
  • Cycle detection across dependencies — GitHub rejects cycles itself; specsync
    surfaces the error rather than pre-validating
  • Non-GitHub providers — dependency sync is a GitHub capability; others gain an
    equivalent when they exist (pluggable-providers)

Capabilities

New Capabilities

  • issue-dependency-sync — project directed ## Blocked by / ## Blocks edges
    from links.md onto GitHub issue dependencies, delta-reconciled, cross-repo.

Impact

  • New code: parse ## Blocked by / ## Blocks in links.md; a dependency
    reconcile that shells gh api graphql (issueDependenciesSummary, blockedBy;
    addBlockedBy / removeBlockedBy).
  • Same asserted-graph and per-layer-reconcile model as the sibling changes;
    stdlib-only; shells out. Composes with epic-and-subissue-projection (both are
    typed links.md edges projected onto native GitHub relationships).

Tasks

Tasks: sync issue dependencies

Typed links (links.md, core)

  • Parse ## Blocked by and ## Blocks sections (entries: #N / owner/repo#N / URL)
  • Treat them as directed edges, distinct from the symmetric ## Related

Dependency projection (GitHub, gh api graphql)

  • Read current dependencies (issueDependenciesSummary, blockedBy, blocking)
  • Resolve node ids for cross-repo references; addBlockedBy for ## Blocked by
  • ## Blocks projects as the named issue's blockedBy (the inverse edge)
  • Maintain a gitignored .specsync/ baseline of the last-synced dependency set (the merge base)
  • Reconcile both ways against the baseline: push local adds, pull GitHub adds into links.md, removeBlockedBy for local removals, remove from links.md for GitHub removals; update the baseline to the converged set
  • Surface GitHub's error on an invalid/cyclic dependency rather than pre-validating

Boundaries & tests

  • Stdlib-only; shell out to gh api graphql; boundary_test.go green
  • Fake-runner tests: add blocked-by, inverse ## Blocks, cross-repo, remove-on-removal, unmanaged-edge gap, cycle-error surfaced
  • Update the specsync skill with the ## Blocked by / ## Blocks syntax

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions