Skip to content

Epic scaffold command: mint the epic and wire the family in one step #100

Description

@github-actions

Epic scaffold command: mint the epic and wire the family in one step

Why

The cross-repo stack is planned as clean layers — and, as of 2026-07-30, partly
shipped: cross-repo-linked-issues (archived) gave link + --repo;
link-by-issue-reference (16/16) lets one link call span repos with no local
scaffolding, proven live today by cross-linking
androidand/brick-now#217 ↔ androidand/tengil#70 ↔ androidand/tengil#35.
epic-and-subissue-projection will project ## Parent edges onto native GitHub
sub-issues, and issue-dependency-sync will add direction.

But the stack has no front door. The maintainer's canonical scenario —

"We want feature X. It needs Y added to the backend repo and Z to use it in
the frontend repo."

— still requires the operator to hand-craft the epic: create a coordination
issue somewhere, label it type:epic by hand, then (once sub-issue projection
exists) edit each child's links.md or attach sub-issues in the GitHub UI.
epic-and-subissue-projection deliberately defines epics by convention and
does not create them; link-by-issue-reference explicitly defers issue
creation. Both deferrals are individually right — which leaves the composed
workflow owned by nobody. This was nearly lost once already: the maintainer
believed the cross-repo capability had been deleted, because the pieces are
scattered across five changes and the shipped part wasn't even in the published
npm package (the 16/16 link-by-issue-reference exists only in git — npm
still serves 0.9.1 without it).

What Changes

  • specsync epic <title> [--repo owner/name] [--child <slug|owner/repo#N|url>]...
    One command that:
    1. creates the epic as a coordination issue (type:epic label, no local
      change directory — an epic is not a spec) in the target repo;
    2. attaches every --child — a local change slug (synced first if needed,
      honoring the existing --repo behavior) or an existing issue reference in
      any repo — as a native GitHub sub-issue once epic-and-subissue- projection lands, and as a managed ## Related cross-reference until
      then (graceful degradation, same body-upsert helper);
    3. prints the epic URL and the child mapping, idempotently re-runnable —
      re-invoking with the same title+children converges instead of duplicating.
  • Epic body is a roll-up, not a spec: children listed with live state
    (from subIssuesSummary when available; from synced checkbox stage until
    then). No proposal.md is scaffolded for the epic itself.
  • Release-gap guard: specsync --version gains the git describe / build
    info needed to tell "repo dev build" from "published release", and the
    release checklist grows one line: a capability shipped 16/16 MUST be
    published before its change is archived — the gap that hid
    link-by-issue-reference from every installed copy for two weeks.

Acceptance — the maintainer's scenario, verbatim

From any repo:

specsync epic "Feature X: cross-repo widgets" --repo androidand/planning \
  --child androidand/backend#12 \
  --child frontend-widget-view        # local slug in this repo's openspec tree

produces: one type:epic issue in androidand/planning; backend#12 and the
frontend change's issue attached (sub-issues when projection exists, Related
until then); each child body pointing back at the epic; a second identical
invocation changing nothing. With issue-dependency-sync landed, adding
--blocked-by androidand/backend#12 on a child records real direction — that
flag belongs to that change and is only reserved here.

Release note

Add specsync epic <title> --repo owner/name --child ... (repeatable --child):
mints a type:epic coordination issue and wires cross-repo children to it —
local change slugs or existing issue references — idempotently. Falls back to
a managed ## Related cross-link until native sub-issue projection lands.

Out of scope

  • Sub-issue projection mechanics and ## Parent reconciliation
    (epic-and-subissue-projection owns them; this command becomes their
    consumer).
  • Dependency direction (issue-dependency-sync).
  • Cross--repo local discovery — worktree/workset awareness
    (openspec-references-coordination).
  • Non-GitHub providers (pluggable-providers).

Capabilities

New Capabilities

  • epic-scaffold: specsync epic creates a type:epic coordination issue and
    idempotently wires cross-repo children to it (slugs and issue references),
    degrading gracefully to a managed ## Related cross-link until native
    sub-issue projection lands.
  • release-gap-guard: specsync --version distinguishes a repo dev build from
    a published release, and the release checklist requires publishing a
    16/16-complete change before it is archived, so a shipped capability can't
    silently miss the published package the way link-by-issue-reference did.

Modified Capabilities

(none)

Impact

  • New epic.go + epic subcommand in cmd/specsync/main.go; reuses
    NewGitHubProviderWithRepo, classifyArg classification (from
    link.go), and the shared ## Related upsert (UpsertRelatedSection) from
    link-by-issue-reference.
  • releasetool.go / release checklist: the publish-before-archive rule.
  • Depends on published link-by-issue-reference (task 1 is the npm release).

Tasks

Tasks

  • 1. Close the release gap first: link-by-issue-reference archived
    (issue Link existing issues by reference, without scaffolding specs #18 closed, spec:archived added). The npm publish itself happens
    via the release tag cut right after this change is archived — the same
    tag that ships this change's own code — so both land in the same release.
    The "publish-before-archive checklist line" turned out to already exist
    (see task 6's correction): no separate checklist code was needed.
  • 2. epic subcommand skeleton: parse <title>, --repo (optional,
    default auto-detect from git remote, same as relate/link), repeated
    --child via the existing stringSlice flag type (classify each as slug
    vs issue ref via classifyArg from link.go), --dry-run. (Flags use the
    double-dash form for this new command; existing commands are untouched.)
  • 3. Epic creation: type:epic + specsync labels (explicit
    WorkItem.Labels, bypassing the stage/priority default), roll-up body
    listing children; idempotent — reuse Push/Find/marker() unchanged
    by giving the epic WorkItem.Slug: "epic:" + slugify(title) (the existing
    title-to-slug normalizer in pull.go), so Find locates the existing epic
    by that slug's marker before creating (no new marker format, no local ref
    cache — see design.md).
  • 4. Child wiring, degraded mode: managed ## Related upsert in both
    directions (epic body ↔ each child), reusing the shared renderer
    (PushRelatedEdit, extracted from runLink's reference-edit sequence).
    Slug children are synced first if they have no ref.
  • 5. Child wiring, full mode seam: a type-asserted SubIssueAttacher
    capability check in Epic() — no provider implements it yet
    (epic-and-subissue-projection hasn't landed), so every run takes the
    degraded ## Related path today; a future provider implementation slots in
    without changing the caller.
  • 6. --version build info: versionString() (cmd/specsync/version.go)
    reports the VCS revision (via runtime/debug.ReadBuildInfo(), no ldflags
    needed) for a local dev build, so it reads e.g. dev (4075f69) instead of
    a bare dev — told apart from both a released binary and another dev
    build.
    Correction from design.md: the other half of this task — "the release
    checklist grows one line: a capability shipped 16/16 MUST be published
    before its change is archived" — turned out to already exist and already be
    enforced in CI: specsync release-plan -fail-on-archive-candidates (in
    .github/workflows/release.yml, "Enforce OpenSpec archive hygiene", runs
    before every tag's goreleaser step) fails the release if a complete, shipped
    change is still sitting unarchived. No new checklist code was needed; this
    task only added the --version diagnostic.
  • 7. Tests: idempotent re-run, mixed slug+ref children, cross-repo
    children, dry-run parity with real run (the dry-runner pattern) — see
    epic_test.go.
  • 8. Skill + README: document the epic workflow with the canonical
    scenario ("feature X needs Y in backend, Z in frontend"), including how it
    composes with issue-dependency-sync once that lands.

Plan changes

8 done

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions