Skip to content

Planning

Most forges treat project management as an afterthought: issues with labels, and a board bolted on. Teams that want real planning run a second tool alongside and then spend their time keeping the two in sync.

neosource treats planning as a first-class surface with the same weight as the repository. This page describes the model — the two kinds of work item, and how a change links back to the work it belongs to.

This is the load-bearing difference, and everything else follows from it.

Every work item has a team. A repository is optional, and whether it is set is what separates the two tracks:

Kind Repository Numbered as Example What it is
Ticket none team key ENG-7 the team’s own planning work
Issue set repo number api#42 intake against one repository

A ticket is what a team files for itself: the work it has decided to do, living on the team board, drawn from the team’s own sequence and carrying the team key, which matches ^[A-Z][A-Z0-9]{1,5}$.

An issue is intake against one repository — a bug report, a request from outside the team, something automation filed. It draws its number from the same sequence as that repository’s pull requests, so #42 is unambiguous within a repo.

The reason tickets exist at all is that real planning work frequently spans several repositories, or none. “Migrate the auth flow” touches four services; “decide the retention policy” touches zero. Neither has a natural home in a repository-scoped tracker, and forcing one is how planning ends up in a second product.

Each kind has one home. A team’s Board is its tickets; a repository’s issue list is its issues. The two are joined rather than merged: a ticket can formally track the issues it subsumes, so a team plans an incoming report by filing the ticket that tracks it and works the ticket.

Status is a fixed set, not a per-repo vocabulary, and it is the same set for both kinds:

backlog · todo · in_progress · in_review · done · cancelled · duplicate

The last three are terminal — they close the item. done, cancelled and duplicate are distinguished deliberately: “we shipped it”, “we decided not to” and “this was already filed” are different outcomes, and collapsing them into one closed state destroys the only signal that tells you which.

Priority is none · urgent · high · medium · low, defaulting to none.

A cycle is a team-scoped planning window with a start and an end — a sprint, an iteration, whatever your team calls it. A ticket or issue binds to at most one cycle.

Cycles belong to the team rather than to a repository, which is the same argument as above: a two-week window covers whatever that team is working on, and that is rarely one repo’s worth of work.

A label is scoped to either a repository or a team, never both. Repo labels sort a repository’s own issues and PRs; team labels sort the team’s tickets and whatever cross-cutting work it has picked up.

Tickets and issues relate to each other through typed links. Every link is stored with its inverse, so the relationship reads correctly from both ends:

Link Inverse
blocks blocked_by
related related
duplicate_of duplicates
tracks tracked_by

blocks is the one with behaviour attached: an item with an unresolved blocked_by link carries a blocked flag, maintained by the system rather than by someone remembering to set it. When the blocker closes, the flag is recomputed.

tracks is the link that joins the two tracks, and it is the one with a direction the product constrains: a ticket tracks an issue, never the reverse. A team that decides to take on a repo issue links the ticket to it rather than copying the text into a second item — so the reporter keeps their issue and the team keeps its plan, and the two stay one relationship rather than two lists someone reconciles. tracked_by is only ever the inverse row the system writes.

This is where planning being in the forge stops being a filing convention.

Write a linking verb in a pull request body and the link becomes a fact the system holds:

Fixes #42
Closes ENG-7
Part of #13

Two families of verb, differing in whether they move the item:

Verbs Link kind Effect
fixes, closes, resolves fixes moves the item (below)
refs, references, part of references advisory backlink only

Verbs are matched case-insensitively and may appear anywhere in the body — at the start of a line, mid-sentence, in a list. Fixes: #42 and Fixes — #42 both parse.

References take three forms: #42 for an issue in the PR’s own repository, KEY-7 for a ticket in the PR’s workspace, and acme/api#42, which parses but currently links only when it names the PR’s own repo. Cross-repo and cross-workspace linking are not yet supported.

  • When the PR is opened or its body is edited — a ticket or issue sitting in todo or backlog moves to in_progress. One already further along is left alone, so re-parsing a body never drags anything backwards.
  • When the PR merges — it closes as done, attributed to the PR’s author, unless it is already in a terminal status. Something someone deliberately marked cancelled or duplicate is not resurrected and re-closed.

A references link never transitions anything. It exists so “this work is part of that” is recorded without asserting that merging finishes it.

The payoff is that the connection between a change and the work it belongs to is something the system knows, rather than a convention you maintain by pasting URLs between two products.

  • This is planning for engineering teams. It is not a general-purpose work tracker and is not trying to replace company-wide project management.
  • Cross-repo and cross-workspace references are not implemented. The acme/api#42 form parses but resolves only within the PR’s own repository.
  • Issue links relate items to items; there is no dependency graph across cycles or teams.

If you are importing from GitHub, its issues arrive as issues on the repository they came from, and can be kept in sync — GitHub keeps the conversation, neosource holds the planning overlay on top. Filing them as tickets would be the wrong shape: they are intake against a repo, and a ticket is a team’s own plan. See Migrating from GitHub.