Tickets and issues
neosource carries two kinds of work item on one storage row, and which one you are looking at is decided by a single field: whether it belongs to a repo.
| Ticket | Issue | |
|---|---|---|
| Addressed | ENG-7 |
acme/api#1526 |
| Scoped to | a team | a repo |
| Numbered by | the team’s sequence | the repo’s sequence |
| Filed by | people on the team | anyone who can see the repo |
| Command group | neo ticket |
neo issue |
A ticket is planning work. It lives on a team’s board, it is numbered
KEY-N from that team’s own sequence, and it has no repo at all — which is
the point, because the work it describes usually spans several, or none yet.
An issue is intake. It belongs to one repository, it is numbered from that repository’s sequence, and it is what an outside reporter, a crash handler or a bot files when something is wrong with that specific codebase.
Neither is a lesser form of the other, and neither converts into the other. A repo issue is a real report against a real repo; a ticket is the planning work someone decided to do about it. Collapsing them would either force every report to pick a team it has no way of knowing, or force every planning item to pretend it belongs to one repo.
Tracks joins them
Section titled “Tracks joins them”The link between the two tracks is a tracks relationship, and it has a
fixed direction: a ticket tracks an issue, never the reverse.
ENG-7 ──tracks──▶ acme/api#1526 ◀─tracked_by─neo ticket track writes it; the inverse tracked_by row is written by the
server in the same transaction, so the relationship reads correctly from
either end and is never half-present. You cannot create a tracked_by row
directly, and you cannot point tracks from an issue at a ticket — the
server refuses both.
neo ticket track ENG-7 acme/api#1526neo ticket untrack ENG-7 acme/api#1526One ticket may track many issues, which is the usual shape: three bug reports across two repos, one piece of planned work that resolves them.
Triage
Section titled “Triage”A team’s triage is its intake queue: the open issues, in the repositories the team holds a grant on, that no ticket tracks yet. Only repositories you can read contribute rows, and repositories mirrored from GitHub are left out.
neo ticket triage --team ENGAccepting an item files a ticket that tracks it. The ticket and the link are written in one transaction, and the issue stays open with its own number: accepting links the two, it does not convert one into the other.
neo ticket create --team ENG --tracks acme/api#1526 --status todo --title "Fix the login redirect"An item that belongs to work already planned is linked with
neo ticket track ENG-7 acme/api#1526 instead; one that will not be done is
closed with neo issue close 1526 acme/api --reason cancelled.
The two command groups
Section titled “The two command groups”Every neo ticket verb has a neo issue twin, and they run the same code —
neo ticket is the team-scoped path with the team supplied by the reference
itself rather than by a flag.
| Ticket | Issue |
|---|---|
neo ticket list --team ENG |
neo issue list acme/api |
neo ticket create --team ENG --title … |
neo issue create acme/api --title … |
neo ticket view ENG-7 |
neo issue view 1526 acme/api |
neo ticket claim ENG-7 |
neo issue claim 1526 acme/api |
neo ticket’s numbered verbs take no team flag and no repo target, because
ENG-7 already says which board it is on. Where the team lives in a
non-personal workspace, qualify it: neo ticket view acme/ENG-7.
The numbered neo issue verbs accept both spellings. A bare number is scoped
by the repo target or --team exactly as it always was, and a KEY-N
reference carries its own team, so it combines with neither:
neo issue view 1526 acme/api # repo-scoped, by targetneo issue view 7 --team ENG # team-scoped, by flagneo issue view ENG-7 # team-scoped, by referenceneo ticket list shows the team’s board, which is its tickets — every row
renders KEY-N. A repo-bound issue never appears there even when it carries
the same team; it lives on its repo’s list (neo issue list <owner>/<repo>,
rendering owner/repo#N), and the way a team plans one is to file a ticket
that tracks it.
Fields, labels and assignees
Section titled “Fields, labels and assignees”create and edit set the same fields on both kinds: --title, --body
(or --body-file, - for stdin), --priority and --status. Labels and
assignees are named, never addressed by id:
neo ticket create --team ENG --title "Fix the login redirect" \ --label bug --label auth --assignee @meneo ticket edit ENG-7 --status in_review --add-label ui --remove-label auth \ --add-assignee alice --remove-assignee @meneo issue labels acme/api # what --label resolves againstneo ticket labels --team ENGA label name resolves against the labels its repo or team offers (exact,
else the one case-insensitive match), and a miss lists the available ones.
An assignee is a handle, with or without the @; @me is you. Every name
resolves before the first write, so a typo sends nothing.
Labels and assignees are separate writes after the create or the field
update. If one fails partway, the error says which writes already landed
and which were never attempted, so a rerun can be aimed. edit compares
against the issue’s current state and sends only what differs; an edit
that changes nothing sends nothing.
A status change is sent by transition, the way the web UI sends it, because
closing is more than a status: moving to done, cancelled or duplicate
closes the issue with that reason (stamping when it was closed, again if it
was already closed); moving a closed issue to an open status reopens it,
then sets the status if it is not todo; moving between open statuses is
the plain field update. --status and a document’s status: behave the
same.
The issue document
Section titled “The issue document”--edit (-e) and --from-file hand create and edit one document
format: a frontmatter of five keys between two --- lines, then the
markdown body.
---title: Fix the login redirectstatus: todopriority: highlabels: [bug, auth]assignees: [alice, @me]---
The redirect drops the `next` parameter when…- The keys are
title,status,priority,labelsandassignees, and nothing else: an unknown key, a misspelt status or a key set twice is an error naming its line. Lines starting with#are comments. - Lists are
[a, b](the brackets are optional). Quote a value with"…"(escapes\",\\,\n) or'…'when it has a comma, a bracket or surrounding spaces. - On
edit, a key the document leaves out keeps its current value, and a--from-filedocument with nothing after its closing---leaves the body as it is: clearing a body that way takes an explicit--body "". In--editthe body is prefilled, so deleting it and saving clears it. A list that is present is the whole set:labels: []removes every label. - Flags given beside a document win over it:
--titlereplaces the document’s title,--labelits labels, and onedit--add-label/--remove-labelapply on top of the document’s list.
--edit opens $VISUAL, else $EDITOR, else vi, on a scratch file in the
system temp directory: a template on create, the current values on
edit. Saving the template untouched with an empty title on create
aborts with nothing sent. If anything fails after you save — a document
that does not parse, a label that does not resolve, a refused request — the
scratch file is kept and its path is in the error, so rerunning with
--from-file <path> loses nothing. With --json, the editor runs on the
terminal (/dev/tty) so that stdout stays one JSON document; without a
terminal, --edit --json is refused.
--from-file <PATH> reads the same document without an editor; - reads
stdin. That is the editor-plugin seam: a plugin pipes a buffer in.
view --document prints an issue in the same format, labels and assignees
included, so a plugin’s round trip is three commands, and feeding the
output straight back changes nothing:
neo ticket view ENG-7 --document > buffer.md # open the bufferneo ticket edit ENG-7 --from-file - --json < buffer.md # on saveneo ticket create --team ENG --from-file - --json < buffer.mdneo ticket list --team ENG --search login --label bug --assignee @me --priority highneo issue list acme/api --state closed --limit 20neo issue mine # assigned to you, both kindsneo ticket mine --created --state allA team list hands every filter to the server and follows every page, up to
--limit rows. A repo list is one server page (--search is applied by the
server; the list is capped at 500 rows) with the label, assignee and
priority filters applied to its rows. mine is the same list under both
command groups: the issues and tickets assigned to you (or --created by
you), across every repo and team, each under its own reference.
JSON output
Section titled “JSON output”Every neo issue and neo ticket verb takes --json, which is a stable
contract for scripts and UIs to pipe into jq:
- stdout carries exactly one JSON document and nothing else; progress and
errors go to stderr, with the exit codes in
neo docs exit-codes. - A single issue (
create,edit,close,reopen,claim,release) is the server’sIssueResponseverbatim. Aftercreateoredittouched labels or assignees,labelsandassigneescarry the state read back from the server afterwards; otherwise they arenull(not read), never[]for “unknown”. viewis{issue, comments}(--documentprints the issue document instead, and does not combine with--json);contextis the context pack (always JSON).listis{issues}, every fetched page merged;mineandtriageare{items, next_cursor}with every page merged andnext_cursor: null;labelsis{labels}.commentis the created comment;trackthe created link;untrackthe ticket’s{links}as they stand after the removal.