Skip to content

Link existing issues by reference, without scaffolding specs #18

Description

@androidand

Link existing issues by reference, without scaffolding specs

Why

specsync link today accepts only local change slugs, and each must already
be synced to an issue (link.go errors with "has no synced ref" otherwise). So
to cross-reference two issues that already exist — especially across repos, e.g.
FusionHub#3489 ↔ portal#4084 — you must first pull each into a local OpenSpec
change just to add a ## Related line, littering two repos with specs you never
wanted. The visible outcome is one cross-reference; the cost is full spec
scaffolding on both sides.

This is friction in exactly the cross-repo case cross-repo-linked-issues set out
to serve. The link mechanism is right; its only entry point is too narrow.

What Changes

  • specsync link accepts issue references, not just slugs. An argument may be
    a slug (as today), a bare #N (the -repo/auto-detected repo), owner/repo#N,
    or a full issue URL. Slugs and references may be mixed in one invocation.
  • A reference argument is linked directly on GitHub, with no local spec.
    specsync fetches the issue, upserts a managed ## Related section pointing at
    the other linked issues, and pushes the edited body — no change directory, no
    links.md. The cross-reference lives only in the issue bodies, which is the
    whole point.
  • The ## Related section is managed idempotently in any body. A shared
    renderer upserts the section (replace-in-place, never append-duplicate) whether
    the body comes from a rendered spec (WorkItemFor) or a fetched raw issue, so
    re-running is safe and the two paths can't drift in format.
  • Cross-repo targeting is per reference. Each owner/repo#N or URL resolves to
    its own repo; specsync edits each issue through a provider bound to that repo,
    so one link call spans repos.

Out of scope / explicitly deferred

  • Creating issues — link only cross-references issues that already exist
  • Any local artifact for pure-reference links — no change dir, no links.md
    (slug arguments still write links.md exactly as today)
  • Providers other than GitHub — the reference forms are GitHub-shaped; other
    providers gain an equivalent when they exist (pluggable-providers)
  • Removing a link — this change only adds/updates ## Related; unlinking is a
    later, separately-earned addition

Capabilities

New Capabilities

  • issue-reference-linkinglink accepts issue references (#N,
    owner/repo#N, URL) and cross-references existing issues directly on the
    tracker, with no local spec, idempotently and across repos.

Impact

  • link.go / cmd/specsync (runLink): classify each argument as slug vs issue
    reference; for references, resolve the issue via the existing IssueReader.Get
    and push an edited body; keep the slug path unchanged.
  • sync.go: factor the ## Related rendering in WorkItemFor into a shared
    upsert helper reused by the reference path; no behavior change for slug syncs.
  • Builds on shipped pieces: -repo + github:owner/repo ref keys, resolveEntry
    (URL/shorthand classification), IssueReader.Get, and the [owner/repo#N](url)
    autolink label. Extends cross-repo-linked-issues; depends on no other in-flight
    change. Stays stdlib-only and shells out to gh.

Tasks

Tasks: link existing issues by reference

Argument classification (core + cmd/specsync)

  • Classify each link argument as slug vs issue reference using the
    resolveEntry rules (URL / owner/repo#N), adding a bare #N / N form
    resolved against -repo or the auto-detected remote
  • Allow slugs and references to be mixed in one invocation
  • An unresolved reference (no such issue) is an error; never create an issue

Shared ## Related renderer (sync.go)

  • Extract upsertRelatedSection(body string, links []Ref) string: replace an
    existing ## Related block (up to the next ## or EOF) in place, else append
  • Refactor WorkItemFor to call it instead of the inline append (no behavior
    change for slug syncs; assert via existing tests)

Reference link path (link.go)

  • For a reference argument: IssueReader.Get the issue, upsert ## Related
    with the other arguments' URLs, push the edited body via the provider
  • Write no change directory and no links.md for reference arguments
  • Build one provider per distinct repo (NewGitHubProviderWithRepo); edit each
    issue through the provider bound to its repo
  • Keep the slug path unchanged (still writes links.md, still syncs)

Dry-run (cmd/specsync)

  • link -dry-run prints, per referenced issue, the issue-edit and the
    ## Related block it would write; makes no GitHub calls

Tests

  • Classification table: slug, #N, owner/repo#N, URL, mixed
  • upsertRelatedSection: insert into a body with none, replace an existing one,
    idempotent re-run, preserve trailing ## sections
  • Reference link via fake provider: asserts the edited body and per-repo routing,
    and that no file is written
  • Mixed slug + reference: slug gets links.md, reference gets a body edit

Boundaries & docs

  • Stdlib-only; boundary_test.go stays green
  • Update the specsync skill: link accepts issue references, not just slugs;
    show the cross-repo owner/repo#N example

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions