Skip to content

feat: move the linked issue's status as the run progresses, with a setup wizard (#21) - #43

Open
sam-bretz wants to merge 1 commit into
mainfrom
issue-21-tracker-status-map
Open

sam-bretz wants to merge 1 commit into
mainfrom
issue-21-tracker-status-map

Conversation

@sam-bretz

Copy link
Copy Markdown
Owner

Closes #21.

What changed

The tracker posted captain's-log comments but never moved the issue. tracker.mapping now maps workflow events to the team's statuses, per workflow, and envctl tracker setup writes that mapping from the team's real states.

How this was built — please read

The skill's Codex worker built round 1 (mapping config, validation, readiness checks, durable ordered transitions). I built round 2 (the wizard) directly. Three consecutive Codex rounds were SIGTERM'd by the harness's background-task memory guard — the last one within seconds, before writing a single file — so the worker path was not viable. I reviewed round 1 in full before building on it.

Acceptance criteria

Mapping and transitions

  • Optional tracker.mapping under tracker:; without it statuses never change and the digest is unchanged
  • Per named workflow
  • Run started, stage started/accepted, awaiting approval, approved, PR published, needs attention, cancelled, rewind
  • Unknown events and stage names rejected at load
  • Status names verified in Plan readiness against the team's real states, listing valid ones on failure
  • Exactly once, in order, across restarts; a rewind after Done can move the issue back
  • Outages never block the workflow; failures show in run show
  • Manual-change policy decided and documented

Wizard

  • envctl tracker setup: tracker, credential file, check, team, status per event from a list
  • Defaults by state type; accepting all of them works
  • Default workflow and each named workflow; events may stay unmapped
  • Shows YAML and a transition preview, splices into envctl.yaml, re-running edits
  • Never changes a ticket; result loads and validates
  • Refuses without a TTY, pointing to the config reference
  • [~] Styling: uses the dashboard's resolved theme palette via lipgloss. It is a numbered-list prompt, not a full Bubble Tea program with arrow-key navigation. Choosing from a list is satisfied; if you want arrow keys, that is a contained follow-up.
  • Docs: config reference with a worked Linear example

Two things I verified rather than assumed

Ordering. My first concern was that a transient failure on "In Progress" followed by "Done" succeeding would let the retry land last, leaving the ticket at In Progress. It can't: deliverRun is strictly head-of-line — it delivers one pending entry per tick and stops while that entry is in retry backoff. Status entries reuse that existing path.

Crash safety. A crash between SetStatus succeeding and the receipt being frozen replays the call on restart — but SetStatus reads the issue's current state first, so the replay is a no-op.

The manual-change policy is forced, not chosen

The issue asked to "decide and document" between forward-only and always-apply. Always apply, last write wins is the only option consistent with the issue's other requirement that a rewind after Done does not silently leave the ticket Done — forward-only would forbid exactly that move. Documented as such.

A bug a test caught in my own defaults

Linear's In Progress and In Review share the started type, so review is distinguished by name. My first version fell back to any started state when no review state existed, so a team without a review column had "Doing" silently mapped as its review state — moving a run waiting for approval back into the working column. TestADefaultIsLeftUnmappedWhenTheTeamHasNoStateOfThatType caught it; review now has no fallback.

Testing

gofmt -l . silent, go vet ./... clean, full go test ./... passes. Wizard tests use a scripted reader and a fake tracker: defaults end to end, the API key never appearing in output or envctl.yaml (asserted against a sentinel value), declining writes nothing, re-running edits rather than duplicates, comments outside the block survive, a one-line tracker: is refused, and the non-TTY refusal.

Not verified against live Linear. The teams and states { type position } queries are new and exercised only through the fake. One real envctl tracker setup against a Linear workspace is worth doing before merge.

Known duplication

SpliceTracker duplicates the comment-preserving splicer in internal/authoring, which lives on the unmerged web-compare-commits-and-root-warning branch and so could not be imported. The two should be unified once that lands.

🤖 Generated with Claude Code

…tup wizard

The tracker posted captain's-log comments but never moved the issue, so
someone still had to drag the ticket to In Progress, In Review and Done
by hand while the workflow already knew each of those moments.

tracker.mapping maps workflow events to the team's status names, per
workflow, because named workflows need not share stages. Unknown events
and stage names are rejected at load; status names are checked during
Plan readiness against the team's real workflow states without changing
anything. Each transition applies exactly once, in order, across
restarts, through the same durable receipts and head-of-line retry the
comments use, so a transition waiting to retry holds back later ones and
a brief outage cannot leave the issue behind the run.

Manual changes are overwritten: the mapping always applies and the last
write wins. The issue asked for this to be decided; it is forced rather
than chosen, since forward-only would contradict the requirement that a
rewind after Done can move the issue back to work.

envctl tracker setup writes the mapping. It asks for a credential file
path, never the key, which it reads to check and never prints or writes;
then the team; then a status for each event from a numbered list of the
team's real states, with defaults chosen by the tracker's state type so
that accepting every default works. In Progress and In Review share
Linear's "started" type, so review is told apart by name and has no
fallback: a team without a review state leaves approval unmapped rather
than moving a waiting run back into the working state. It previews the
transitions a typical run would make, splices the block into envctl.yaml
without disturbing anything outside it, edits rather than duplicates on
a rerun, and refuses without a terminal instead of hanging.

Refs #21

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sam-bretz

Copy link
Copy Markdown
Owner Author

Rebased onto main after #40, #41 and #42 merged. Two of the three conflicts would have introduced a real bug if resolved by keeping both sides, so each now has a regression test that I confirmed fails against the naive merge:

  • Cancel (internal/daemon/api.go). feat: close finished runs to release their VM (#25) #41 made Cancel refuse on a completed run and return an error. This PR queued the cancelled status after cancelling. Kept side by side, Go allows the new error to be ignored as a statement — so a refused cancel would compile, queue the move to Canceled, return nil, and commit, leaving the tracker showing completed work as cancelled. The status now fires only after a successful cancel. TestARefusedCancelNeverMovesTheIssueToCancelled.
  • Publication (internal/engine/attempt.go). Log the pull request to the linked issue when a change publishes #40 logs the PR comment only when len(prs) > 0; this PR's pr_published status had no such guard, so a change that opened nothing would move the issue to Done. Both now sit behind the guard, extracted into logPublication so it's testable. TestTheIssueMovesToPublishedOnlyWhenAPullRequestWasOpened, with a positive control.
  • Docs — additive: Log the pull request to the linked issue when a change publishes #40's comment table, then this PR's status section.

While writing those I also checked a suspicion about round 1: AppendTrackerStatus passes "" as the default workflow name while mappings are keyed default. Not a bug — mappingFor maps "" to default.

Full go test ./... passes after the rebase.

@sam-bretz
sam-bretz force-pushed the issue-21-tracker-status-map branch from 220b872 to 62137c5 Compare September 16, 2026 16:57
@sam-bretz
sam-bretz marked this pull request as ready for review September 17, 2026 02:59

This branch has not been deployed

No deployments
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.

Map workflow states to issue tracker statuses, with a setup wizard

1 participant