feat(tracker): agent triage surface — issue create, label ops, close (ADR-0014) #4
No reviewers
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab!4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/agent-triage-surface"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements the PRD in #3: the agent surface now covers the full triage set end-to-end — labctl → agent API → Tracker seam → both bindings.
What's in here
internal/tracker):CreateIssue(labels at creation),AddIssueLabels,RemoveIssueLabels,Labels,EnsureLabel, plus theErrUnknownLabelsentinel. Callers speak label names only; strict resolution on both bindings — a typo is a loud 400, never an implicit label create. The metrics decorator forwards the new ops (create_issue,label_add,label_remove,labels,label_ensure) and keeps forwarding capability interfaces (forwarding regression test extended).EnsureLabelis list-first with a re-list on the forge's duplicate answer (tested against the 409 race); colors normalized to#rrggbb; existing pagination/label-verification rules untouched.labels/issue_labelstables. Migration 0002 addsissues.author_kind/run_idmirroringissue_comments— the PRD asks for run attribution on created issues ("like comments today"), and the issues table had no author columns, so this is the one schema change. Run identity flows through the sameRunScoperseam as comments.POST /issues,POST|DELETE /issues/{n}/labels(label set in the body — names may contain/),POST /issues/{n}/close,GET|POST /labels(idempotent ensure, 200 with the label that exists afterwards). Incogni sanitization widened to every agent-authored body (issue, comment, PR/CR). Builtin mutations publishissue.changedso the operator UI refetches, mirroring the existingcr.changed.issue create --title --body [--labels],issue label add|remove <n> <a,b>,issue close <n>,label list,label create --name [--color --description];issue listgains created-at + labels columns (triage buckets from one call). Exit codes unchanged (0/1/2).Verification
Full suite + golangci-lint green. Also verified against the real binaries: seeded a builtin repo + active run, started
bin/lab, and drove every newlabctlcommand over the socket — five seeded labels listed, idempotentlabel createreturns the original on retry, issue created with labels and storedauthor_kind=run, label add/remove swaps roles, comment-then-close flow, typo'd label → exit 1 naming it, usage errors → exit 2, bad token → opaque 401.Closes #3
🤖 Generated with Claude Code
Land-PR audit — PR #4 (head
d894efb)Checked
main; Conventional-Commits title; workingCloses #3.ci / flake-check) sat at "Waiting to run" for the whole validation (runner not picking up jobs — older runs are waiting/cancelled too), so nothing vouched. Ran the project's own checks locally on the exact head commit instead:go build ./...,go test ./...(26 packages, all ok),golangci-lint run(0 issues). The diff touches noweb/source, so the vitest half of flake-check is unaffected.Findings — none blocking (edge-case races, error-mapping divergences, dedup cleanups):
internal/tracker/builtin/builtin.go:206— Add/RemoveIssueLabels loop one committed tx per label; a mid-loop failure (label deleted concurrently) commits a partial change, surfaces as 404 "not found" (reads as issue-gone, contradicting the handler's own 400-naming-the-label contract), and skipsissue.changed. Atomic precedents exist in the same diff (CreateIssueWithLabels,store.SetIssueLabels).internal/agentapi/handlers.go:543— label color is never validated: builtin stores garbage verbatim (200) while the forge's 422 surfaces as 502, turning a permanent caller typo into a retry-worthy "outage" under the retry-safe ensure contract.internal/agentapi/handlers.go:339— comment "body is required" checks the raw body, sanitization runs after; an attribution-only body on an incogni builtin repo stores an empty comment (sanitize-to-empty is pinned bysanitize_test.go).internal/agentapi/handlers.go:343— comment create is the only builtin agent mutation that skipspublishIssueChanged(operator comment create publishes; IssueDetail refetches on it).internal/tracker/forgejo/forgejo.go:392— label resolution is repo-scoped only; an org-level label an issue visibly carries can't be added/removed by name, and EnsureLabel would mint a shadowing repo-level duplicate (read/write asymmetry new in this diff).internal/tracker/forgejo/forgejo.go:354— Gitea-lineage forges likely answer 201 to duplicate-name label POSTs (no uniqueness check upstream), so the 409/422 recovery never fires and concurrent first-ensures can mint duplicate labels; sequential idempotency holds.internal/tracker/builtin/builtin.go:278— strict name→ID resolve-and-dedupe now exists in three parallel copies (httpapi/issues.go:654,builtin.go:278,forgejo.go:392); the GitHub binding (#1) would be a fourth. A generic helper intrackeris import-cycle-free.internal/tracker/forgejo/forgejo.go:262—defaultLabelColorre-declaresstore.LabelDefaultColor, kept equal only by a comment;forgejoimportingstoreis cycle-free.Refuted along the way: "issue titles bypass the incogni sanitizer" (body-only is the documented ADR-0014 contract and the line-stripper's design) and "forgejo per-label DELETE remove is unsafe" (the only subset-remove the API offers; idempotent under retry).
Verdict: PASS — merge gated on the maintainer's explicit confirmation.