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
Deferred (not active). This expands specsync from "specs↔issues" to
"specs↔issues↔commits↔PRs↔releases" — the graph-maintainer identity. That is a
deliberate future direction, tied to the naming/identity shift we are not
ready for yet. Kept here as a worked-out proposal so the thinking is preserved;
revisit once specsync has clearly earned the sync identity. Until then, do not
sync or build it.
The single question this answers: "I'm about to plan work on X — what already
relates to it, and what state is that work in?"
specsync already holds the authoritative core of that answer — links.md asserts
spec↔spec edges, and body markers + refs.json assert spec↔issue edges. What it
can't yet do is join that core with the git/gh facts that surround it (the PRs,
commits, and releases that touched the same work), so today an agent assembles
that picture by hand, differently every time. The valuable primitive is the deterministic join across sources specsync already shells out to — not a new
data source, and not a new kind of edge.
Principle: an asserted graph, never an inferred one
Every edge is asserted — authored in links.md/markers, or read as fact from git log / gh. The graph is therefore deterministic, free to compute, and
rebuildable from ground truth. No probabilistic, LLM-derived, or heuristic edges
ever enter it. This is the line that keeps the graph trustworthy, and it is why
inference tools stay outside it (see Non-goals).
Invariants hold: standard-library only, single binary, shells out to git / gh
/ openspec through the existing runner abstraction so it stays testable.
Solution
An in-memory work graph built on demand:
Nodes: spec (change), issue, commit, PR, release.
Edges, each from an existing shell-out:
spec↔spec — links.md (asserted)
spec↔issue — body marker / refs.json (asserted)
issue↔PR↔commit — gh native issue/PR linkage (search by issue number)
commit↔release — gh release / git tag --contains
work↔files — changed files from the linked PRs/commits
specsync relate <slug> (or -path <file>) builds the graph and prints the
slice connected to X: related specs with their issue + stage, and the PRs,
commits, and releases touching the same files — in stable, deterministic order.
It is read-only — it never mutates the tracker. This is the fast,
deterministic planning scan; it does only the join an agent does badly by hand,
not code search an agent already does well.
Optional delta annotation. When the OpenSpec CLI is on PATH, each spec node
is tagged with its requirement-delta summary (ADDED / MODIFIED / REMOVED counts).
When it is absent, the file-based baseline stands and the tag is omitted. Gated,
never required — mirroring how gh is treated.
Non-goals
Serialized graph.json export — no consumer exists yet; add it only when a
real one does, not speculatively.
Graphify / any inferred or code-symbol edges ("which function implements
this requirement") — inference would pollute an asserted graph; deferred until
there is a concrete consumer, and only ever as a separate referencing layer.
Release-plan report — a future query over this same graph, where deeper
OpenSpec delta consumption will land. Out of scope here so the join ships clean.
Tasks
Define Graph, Node (kind: spec|issue|commit|pr|release), and typed Edge in a new graph.go; deterministic ordering throughout
Build the asserted core: spec↔spec from links.md (reuse parseLinksMD), spec↔issue from markers/refs.json (reuse loadRefs)
Derive issue↔PR↔commit edges via gh (search PRs/commits by issue number); route through the existing gh runner abstraction so it is mockable
Derive commit↔release edges via gh release / git tag --contains
Derive work↔files edges from changed files of the linked PRs/commits
Add relate subcommand to cmd/specsync/main.go: -slug or -path, builds the graph, prints the connected slice (related specs + issue/stage, PRs, commits, releases) in stable order
Guarantee relate is read-only — no tracker writes, no refs.json/links.md mutation
Optional delta annotation: if openspec is on PATH, tag spec nodes with ADDED/MODIFIED/REMOVED counts; no-op and identical output when absent
Tests: deterministic slice for a fixture repo (git/gh calls mocked via the runner); delta gate off → byte-identical output; isolated spec (no edges) handled
Document relate + the asserted-graph principle in the skill + README; list the parked non-goals (graph.json, Graphify, release-plan)
Build, install, smoke-test specsync relate -slug <x> and -path <file>
Work graph
The single question this answers: "I'm about to plan work on X — what already
relates to it, and what state is that work in?"
specsync already holds the authoritative core of that answer —
links.mdassertsspec↔spec edges, and body markers +
refs.jsonassert spec↔issue edges. What itcan't yet do is join that core with the git/gh facts that surround it (the PRs,
commits, and releases that touched the same work), so today an agent assembles
that picture by hand, differently every time. The valuable primitive is the
deterministic join across sources specsync already shells out to — not a new
data source, and not a new kind of edge.
Principle: an asserted graph, never an inferred one
Every edge is asserted — authored in
links.md/markers, or read as fact fromgit log/gh. The graph is therefore deterministic, free to compute, andrebuildable from ground truth. No probabilistic, LLM-derived, or heuristic edges
ever enter it. This is the line that keeps the graph trustworthy, and it is why
inference tools stay outside it (see Non-goals).
Invariants hold: standard-library only, single binary, shells out to
git/gh/
openspecthrough the existing runner abstraction so it stays testable.Solution
An in-memory work graph built on demand:
links.md(asserted)refs.json(asserted)ghnative issue/PR linkage (search by issue number)gh release/git tag --containsspecsync relate <slug>(or-path <file>) builds the graph and prints theslice connected to X: related specs with their issue + stage, and the PRs,
commits, and releases touching the same files — in stable, deterministic order.
It is read-only — it never mutates the tracker. This is the fast,
deterministic planning scan; it does only the join an agent does badly by hand,
not code search an agent already does well.
Optional delta annotation. When the OpenSpec CLI is on
PATH, each spec nodeis tagged with its requirement-delta summary (ADDED / MODIFIED / REMOVED counts).
When it is absent, the file-based baseline stands and the tag is omitted. Gated,
never required — mirroring how
ghis treated.Non-goals
graph.jsonexport — no consumer exists yet; add it only when areal one does, not speculatively.
this requirement") — inference would pollute an asserted graph; deferred until
there is a concrete consumer, and only ever as a separate referencing layer.
OpenSpec delta consumption will land. Out of scope here so the join ships clean.
Tasks
Graph,Node(kind: spec|issue|commit|pr|release), and typedEdgein a newgraph.go; deterministic ordering throughoutlinks.md(reuseparseLinksMD), spec↔issue from markers/refs.json(reuseloadRefs)gh(search PRs/commits by issue number); route through the existing gh runner abstraction so it is mockablegh release/git tag --containsrelatesubcommand tocmd/specsync/main.go:-slugor-path, builds the graph, prints the connected slice (related specs + issue/stage, PRs, commits, releases) in stable orderrelateis read-only — no tracker writes, norefs.json/links.mdmutationopenspecis on PATH, tag spec nodes with ADDED/MODIFIED/REMOVED counts; no-op and identical output when absentrelate+ the asserted-graph principle in the skill + README; list the parked non-goals (graph.json, Graphify, release-plan)specsync relate -slug <x>and-path <file>