Skip to content

brigade: record a kind and a goal on each ticket and work item, and add the goal commands - #178

Merged
creedants merged 19 commits into
mainfrom
landing/e167
Oct 11, 2026
Merged

creedants merged 19 commits into
mainfrom
landing/e167

Conversation

@creedants

Copy link
Copy Markdown
Owner

The coordinator's store now records what kind of work each ticket and work item is, and which goal it serves.

  • Every ticket and work item carries a kind, one of feature, fix, polish, upkeep, and study, and an optional goal. Goals live in a new table, goals.tsv.
  • ticket add --summary needs --kind and takes --goal G<n>. ticket set --kind, --goal, and --no-goal change a waiting ticket.
  • goal add, goal set, and goal list record, change, and list goals. Closing a goal as done or dropped needs --evidence and is refused while work under it is still open.
  • fire gives the new work item its first ticket's kind and goal. A ticket from another goal may ride along only when --ride-along names it, it is low priority, and it is not a feature.
  • ticket add --from-report gives each follow-up the goal of the report's item and the kind polish.
  • ticket list, startable, status, and close print kinds and goals. status adds kinds:, goals:, and untagged: lines.
  • A kind or goal cell that holds something the tool does not know reads as empty, is warned about on standard error, and is kept as written when the table is rewritten.
  • A store written before this change reads with every ticket and item unassigned and with no kind. Once this version writes to a store, an older brigade.py calls that table malformed.
  • activity.py reads a work item row with or without the two new cells and prints the same page for both.

One accepted limit. The shortened flags --no and --r stop working, because --no-goal and --ride-along make them ambiguous. The full names --no-decision and --reason work as before. The reviewer raised this as a rare break, and it is accepted as a limit.

Reviewed at 30bb4512.

… row, and a goals table

`rail.tsv` and `dishes.tsv` each gain two trailing columns, `kind` and
`goal`. A row written before this change lacks both cells and reads with
each one empty. A read leaves the file as it is. The next rewrite of the
table writes each of its rows at the full width. `open` writes the new
`goals.tsv` with its header into a new store. A store with no
`goals.tsv` reads as a store with no goals, and `status`, `watch`,
`startable`, `ticket list`, and `close` do not create the file.

`read_labels` reads the two cells and `goals.tsv`. A kind that is not
one of the five kinds reads as no kind, and a goal with no row in
`goals.tsv` reads as unassigned. No command calls it yet.

`activity.py` reads a work item row of 15, 16, or 17 cells and prints
what it printed for the first 15.
`goal add` appends one open goal to `goals.tsv`, logs one `goal` row,
and prints the new id. It refuses the name of an open or paused goal,
so the same command run twice adds one goal.

`goal set` changes a goal's state, name, purpose, done test, or share.
An open or paused goal moves to any other state, and a done or dropped
goal moves only to open. A move to done or to dropped needs
`--evidence`, which is stored with the time in the goal's `closed`
cell, and is refused while the goal has a waiting ticket, an item in
flight, or a ticket riding along. No command gives a ticket a goal yet,
so the tests of that refusal come with the commands that do.

`goal list` prints each open or paused goal with its counts, then the
counts of unassigned work. `--all` adds the done and dropped goals.
`count_labels` derives each count from `rail.tsv`, `dishes.tsv`, and
`pass.tsv` on every call. No count is stored.

A goal state that is not one of the four reads as open, and a share
that is not a whole number of 1 or more reads as no limit. `goal list`
prints one warning on standard error for each, and `goal set` rewrites
the cell. Paused and share change no start.
`ticket add --summary` needs `--kind`, one of feature, fix, polish,
upkeep, and study, and takes `--goal` with the id of an open or paused
goal. The two new checks run after the owner check and after every
refusal the command gave before, so a command line that was refused
before is refused with the same words.

`ticket set --kind`, `--goal`, and `--no-goal` change a waiting ticket.
On a ticket in another state they are refused before any write.

`ticket add --from-report` gives each ticket the goal of the report's
item and, when that item has a kind, the kind polish. It refuses
`--kind` and `--goal`. No command gives an item a kind or a goal yet.

`ticket move` writes the ticket's kind into the handoff file and leaves
the goal behind, because a goal id names a row of one store. Its line
says which goal the ticket leaves. `ticket take` files a handoff with no
`kind` key, or with a kind that is none of the five, as a ticket with no
kind.

A script gave each of the 189 `ticket add --summary` calls in
`tests/test_brigade.py` an explicit `--kind fix`. Seven assertions on
stored rows and handoff files gain that word, and one gains the three
new flags in the `ticket set` refusal.
fire now writes a kind and a goal on the item it starts. The goal is the
lead ticket's, the first id in --tickets. The kind is the lead ticket's
when it has one, then --kind, then the station's. Station bug-fix and
perf-issue give fix, and investigation gives study. A fire that finds no
kind is refused. The lead ticket's kind cell takes the item's kind in the
same rewrite that assigns the tickets, so the two rows agree.

A ticket after the lead must have the lead ticket's goal or no goal.
--ride-along names the ones that may be under another goal. Each must
have priority low and must not be a feature, and each keeps its own kind
and goal.

The check runs under both locks. Its first run comes after the owner
check, the not-waiting check, and the worker cap, so a fire one of those
refused or blocked before gets the same words. It comes before the lease
claim, so a fire it refuses claims no lease and writes nothing. Its second
run catches a ticket set made while the claim ran, and releases the lease.

A block note records --kind and --ride-along when the blocked fire had
them, and watch reprints them. A note with neither holds the keys it held
before and reprints the same line.

One existing assertion changes. A follow-up ticket of an item started on
station bug-fix now has the kind polish in its kind cell.
… close

`ticket list` and `startable` print a ticket's kind and then its goal
after the priority. A ticket with neither prints the line it printed
before.

`status` prints up to three lines after its workers line, or after its
counts line in the executive admin's store. `kinds:` and `goals:` hold
the waiting tickets and the items in flight under each kind and each
goal, and `untagged:` counts the waiting tickets with no kind. A line
with nothing to count is absent, so a store with no kind and no goal
gains `untagged: <n>` when a ticket waits and nothing else.

`close` prints the kind before the id on the lines of Merged, In
progress, and New tickets, not started. When a line of one of those
sections has a goal, the section prints its lines under one heading per
goal and an Unassigned heading. The three titles come from `SECTIONS` by
key, so a renamed title cannot leave the grouping empty.

Each of the four commands prints one warning on standard error for each
kind cell that holds no kind and each goal cell that names no row of
`goals.tsv`, reads the cell as empty, and exits as before. `plan()`,
`counts()`, and `status_line()` are unchanged, so the order and the
standings `startable` gives do not depend on a kind or a goal.

A script added the kind word to 92 expected `ticket list` and
`startable` lines in `tests/test_brigade.py`. Six `status` expectations
gain a `kinds:` line, five `close` expectations gain the word `fix`, and
the `status` help assertion takes the new sentence at 300 columns, where
it fits on one line.
One bullet for the changelog. It names each new command and flag, what fire, status, close, ticket list, and startable now print or store, and what a store written before this change reads as.
…ket a kind

The goal set refusal for a goal with waiting tickets named --no-goal as a
way to give a ticket another goal. It now names it as the way to give none.

ticket take said a kind was unknown and stopped there. It now names the
ticket set command that gives the ticket a kind.

The change note said every cell that holds none of the five kinds is warned
about, which took in an empty cell. It said fire warns for every ticket it
names, which left out a fire refused before its kind and goal check. It said
a rewrite adds two cells to each row, which is false for a row older than
the last column change.
…e than the code does

The fire refusal for a ticket under another goal told the reader to name it
in --ride-along. That fails for a ticket that is not low or is a feature, so
the message now says what --ride-along takes.

The fire docstring said its check runs under both locks. It runs twice under
the store lock. A comment in ticket set said fire copies the lead ticket's
kind, which is false when the kind came from --kind or the station.

The change note said --state done needs --evidence. A goal that is already
done takes the flag with no evidence, so the note now says a move to done.

Two test names said each reading command, and the tests run eight commands
and seven. One test name said close groups Merged by goal, and its merged
item has no goal. Two test docstrings are corrected the same way.
…m in flight names, and warn of a goals.tsv row with no id or a repeated id
@creedants
creedants merged commit 68a1113 into main Oct 11, 2026
2 checks passed
@creedants
creedants deleted the landing/e167 branch October 11, 2026 21:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant