You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
creates the epic as a coordination issue (type:epic label, no local
change directory — an epic is not a spec) in the target repo;
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);
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.
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) gavelink+--repo;link-by-issue-reference(16/16) lets onelinkcall span repos with no localscaffolding, proven live today by cross-linking
androidand/brick-now#217 ↔ androidand/tengil#70 ↔ androidand/tengil#35.epic-and-subissue-projectionwill project## Parentedges onto native GitHubsub-issues, and
issue-dependency-syncwill add direction.But the stack has no front door. The maintainer's canonical scenario —
— still requires the operator to hand-craft the epic: create a coordination
issue somewhere, label it
type:epicby hand, then (once sub-issue projectionexists) edit each child's
links.mdor attach sub-issues in the GitHub UI.epic-and-subissue-projectiondeliberately defines epics by convention anddoes not create them;
link-by-issue-referenceexplicitly defers issuecreation. 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-referenceexists only in git —npmstill serves 0.9.1 without it).
What Changes
specsync epic <title> [--repo owner/name] [--child <slug|owner/repo#N|url>]...One command that:
type:epiclabel, no localchange directory — an epic is not a spec) in the target repo;
--child— a local change slug (synced first if needed,honoring the existing
--repobehavior) or an existing issue reference inany repo — as a native GitHub sub-issue once
epic-and-subissue- projectionlands, and as a managed## Relatedcross-reference untilthen (graceful degradation, same body-upsert helper);
re-invoking with the same title+children converges instead of duplicating.
(from
subIssuesSummarywhen available; from synced checkbox stage untilthen). No proposal.md is scaffolded for the epic itself.
specsync --versiongains the git describe / buildinfo 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-referencefrom every installed copy for two weeks.Acceptance — the maintainer's scenario, verbatim
From any repo:
produces: one
type:epicissue inandroidand/planning; backend#12 and thefrontend 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-synclanded, adding--blocked-by androidand/backend#12on a child records real direction — thatflag belongs to that change and is only reserved here.
Release note
Add
specsync epic <title> --repo owner/name --child ...(repeatable--child):mints a
type:epiccoordination issue and wires cross-repo children to it —local change slugs or existing issue references — idempotently. Falls back to
a managed
## Relatedcross-link until native sub-issue projection lands.Out of scope
## Parentreconciliation(
epic-and-subissue-projectionowns them; this command becomes theirconsumer).
issue-dependency-sync).(
openspec-references-coordination).pluggable-providers).Capabilities
New Capabilities
epic-scaffold:specsync epiccreates atype:epiccoordination issue andidempotently wires cross-repo children to it (slugs and issue references),
degrading gracefully to a managed
## Relatedcross-link until nativesub-issue projection lands.
release-gap-guard:specsync --versiondistinguishes a repo dev build froma 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-referencedid.Modified Capabilities
(none)
Impact
epic.go+epicsubcommand incmd/specsync/main.go; reusesNewGitHubProviderWithRepo,classifyArgclassification (fromlink.go), and the shared## Relatedupsert (UpsertRelatedSection) fromlink-by-issue-reference.releasetool.go/ release checklist: the publish-before-archive rule.link-by-issue-reference(task 1 is the npm release).Tasks
Tasks
link-by-issue-referencearchived(issue Link existing issues by reference, without scaffolding specs #18 closed,
spec:archivedadded). The npm publish itself happensvia 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.
epicsubcommand skeleton: parse<title>,--repo(optional,default auto-detect from git remote, same as
relate/link), repeated--childvia the existingstringSliceflag type (classify each as slugvs issue ref via
classifyArgfromlink.go),--dry-run. (Flags use thedouble-dash form for this new command; existing commands are untouched.)
type:epic+specsynclabels (explicitWorkItem.Labels, bypassing the stage/priority default), roll-up bodylisting children; idempotent — reuse
Push/Find/marker()unchangedby giving the epic
WorkItem.Slug: "epic:" + slugify(title)(the existingtitle-to-slug normalizer in
pull.go), soFindlocates the existing epicby that slug's marker before creating (no new marker format, no local ref
cache — see design.md).
## Relatedupsert in bothdirections (epic body ↔ each child), reusing the shared renderer
(
PushRelatedEdit, extracted fromrunLink's reference-edit sequence).Slug children are synced first if they have no ref.
SubIssueAttachercapability check in
Epic()— no provider implements it yet(
epic-and-subissue-projectionhasn't landed), so every run takes thedegraded
## Relatedpath today; a future provider implementation slots inwithout changing the caller.
--versionbuild info:versionString()(cmd/specsync/version.go)reports the VCS revision (via
runtime/debug.ReadBuildInfo(), no ldflagsneeded) for a local
devbuild, so it reads e.g.dev (4075f69)instead ofa bare
dev— told apart from both a released binary and another devbuild.
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", runsbefore 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
--versiondiagnostic.children, dry-run parity with real run (the dry-runner pattern) — see
epic_test.go.scenario ("feature X needs Y in backend, Z in frontend"), including how it
composes with
issue-dependency-synconce that lands.Plan changes
8 done