iru-go-code-one-task
Implement a single task from a Go `implementation_plan.md`-style task list — just the implementation and its tests — in any Go module: an agent-friendly command-line tool (Cobra, optional Bubble Tea v
SKILL.md.claude/skills/iru-go-code-one-task/SKILL.md
GitHubImplement one plan task (Go)
Carry out a single task's implementation, tests, and checkbox update in a Go module — nothing else. This is the
narrowest execution unit for a go task in the iru-code pipeline: iru-go-code-one-task-group calls it once per
task in a bucket (potentially many at once, in parallel agents), then handles license headers, doc-comment auditing,
tests, coverage, the race detector, golangci-lint and govulncheck itself, once for the whole bucket. It uses a medium
model on purpose — the plan carries the hard reasoning; this skill executes one task against code agreements that live
in reference/.
Two things shape how a Go task is done here:
- Logic lives in
internal/packages,mainstays thin.cmd/<bin>/main.goonly builds a context and callscli.Main(ctx, args, stdin, stdout, stderr); commands are small Cobra constructors over plain, unit-testable functions. That's what makes the group's 80%-per-file coverage gate reachable without a TTY, a network, or a container — so where code goes is decided first. - A CLI's observable behaviour is its API. Exit codes, stdout vs stderr, the JSON shapes and the error envelope
follow
reference/cli-contract.md; scripts and agents depend on them, so a CLI task changes them only as the task says, never renumbers an exit code, and pins every change with a test.
Step 0 — Detect the project shape once
Settle these from the files, never from assumptions (this skill is copied into other repositories):
- Module(s): the nearest
go.modabove the task's paths — itsmodulepath and itsgodirective (the newest language/library features you may use) andtoolchainline;go versionfor the installed toolchain (an older installed Go than thegoline withGOTOOLCHAIN=local→ "compile-check unavailable", Step 5). Ago.workat the root → a workspace: note everyusedirectory;./...doesn't cross module boundaries, so build each touched module from its own directory. Nogo.modanywhere → this isn't a Go module: report it as a blocker (the task was mis-tagged). - Layout:
cmd/<bin>/main.go(one or more binaries) or a rootmain.gowithpackage main;internal/packages (internal/cli,internal/tui,internal/version, …); atools//internal/tools/gendocsgenerator;vendor/(then builds use-mod=vendorautomatically — never add a dependency without re-vendoring as the task says). - CLI framework:
github.com/spf13/cobra(+cobra/doc),github.com/urfave/cli/v3,github.com/alecthomas/kong, or the standardflagpackage, fromgo.mod'srequireblock. The scaffold's contract files —internal/cli/exitcode.go,errors.go,output.go,term.go,signals.go,config.go,root.go(theApptype,(*App).exitCode,finishTree/wrapRunE,a.emit,safety(…)) — tell you how the repository maps errors, formats output and decides colour; read the ones the task touches before writing a command. No CLI framework and nopackage main→ a library/service: the CLI references don't apply. - TUI:
charm.land/bubbletea/v2/charm.land/lipgloss/v2(v2 vanity paths) or the v1github.com/charmbracelet/...paths — follow whichever the module uses;reference/bubbletea.mdis v2. - Tests: the assertion style of neighbouring
_test.gofiles (standard library, testify, go-cmp); testscript (grep -rn "testscript.Main\|testscript.Run" --include=*_test.go) and itsDir(testdata/script/); teatest; golden files undertestdata/and the-updateflag that refreshes them;//go:build integrationfiles (container-backed — testcontainers-go). - Quality config:
.golangci.yml/.golangci.yaml/.golangci.toml(version: "2"→ golangci-lint v2; its enabled linters,goimports.local-prefixes, exclusions), so the code you write passes the group's gate. - Generated code:
//go:generatedirectives (grep -rn "^//go:generate") and files carrying// Code generated … DO NOT EDIT.
Step 1 — Decide the package(s), then read only the reference material this task needs
A path in the task wins. Otherwise: domain logic and its types → the internal/<area> package that owns them; a new
or changed command → internal/cli/<command>.go (constructor) calling a plain function (in internal/cli or a
domain package); TUI state → internal/tui; version metadata → internal/version; nothing new in cmd/<bin>/ beyond
wiring. Genuinely ambiguous → the most specific existing internal/ package, and say so in Step 8. Record the
packages touched — Step 5 derives go vet from them.
This skill ships a reference/ directory next to this file. Read reference/README.md first — it is short and routes
you to the rest — then read only what it points at. The routing, restated:
| Read | When |
|---|---|
reference/code-style.md |
Always. |
reference/errors.md |
Always for code that returns, wraps, compares, or maps errors (nearly every task). |
reference/testing.md |
The task writes or updates tests — nearly every task. |
reference/cli-contract.md |
A CLI task adding/changing a command, flag, output, exit code, prompt, or error message. |
reference/cobra.md |
A Cobra command, flag, Args validator, flag group, completion, or the generated reference/man pages. |
reference/output-and-terminal.md |
stdout/stderr writes, --format/JSON payloads, Lip Gloss styling, prompts, TTY detection, signals, broken pipes, file writes. |
reference/bubbletea.md |
The Bubble Tea model or the command that starts it. |
reference/ui-contract.md + reference/ui-implementation.md |
The task adds or changes a TUI screen, component, list or other UI. |
reference/godoc.md |
A new/changed exported identifier or a new package. |
reference/security.md |
Child processes, paths from arguments/env/config, secrets/tokens, network I/O, or a gosec finding. |
reference/concurrency.md |
Goroutines, channels, sync, errgroup, timeouts/cancellation, signal handling. |
If reference/ is missing (the skill directory was copied without it), proceed using the repository's own
CLAUDE.md conventions and the surrounding code's style, and say so in Step 8 — don't guess at what the reference
would have said.
Step 2 — Re-check the current code state
Read the actual current content of every file the task touches before editing — other tasks in the same bucket may be
landing concurrently. For a command, also read root.go (global flags, PersistentPreRunE, where errors are mapped
to exit codes), the exit-code/envelope files, the command's existing tests and testscript files, and any golden files
it writes: a changed field name or exit code there is an interface change. Note what the surrounding code does about
the things reference/ has opinions on; a package that consistently does something else wins, and the divergence goes
in Step 8.
Step 3 — Implement exactly what the task specifies
Apply reference/code-style.md, reference/errors.md, and whichever optional files Step 1 selected, plus the
repository's own CLAUDE.md where more specific. Work in dependency order: domain types/functions → command or TUI
wiring → registration in the root → generator inputs.
Non-negotiable, because a gate or a caller enforces them:
No
os.Exit,os.Stdout/os.Stderr,fmt.Println, or global logger belowcmd/<bin>/— write through the injected streams (cmd.OutOrStdout()/cmd.ErrOrStderr()) and return errors;cli.Mainmaps them to exit codes in one place (reference/errors.md). Results on stdout only; everything else on stderr.Exit codes and JSON are API — use the existing constants (never a bare number), keep JSON struct fields in alphabetical JSON-name order with
schemaVersion, never rename/remove a field or renumber a code unless the task says so (then the golden files andschemaVersionchange with it).Doc comments on every exported identifier and package you add (
reference/godoc.md) — revive and godot fail the group's gate otherwise.Context first, argv only —
ctx context.Contextas the first parameter of anything that blocks;exec.CommandContextwith an argument list and--before operands, neversh -c(reference/security.md).No new module dependency,
godirective bump,replace, or new binary undercmd/unless the task explicitly calls for it — a blocker to report (Step 6) otherwise. If it does:go get <module>@<version resolved live>, thengo mod tidy, and list thego.mod/go.sumchange in Step 8.Never hand-edit generated code. If the task changes a
//go:generateinput, run that package'sgo generate ./<pkg>and keep the regenerated output.Terminal UI work decomposes first (
reference/ui-contract.md§2): write theScreen › Region › Componenttree (inputs, intent messages, visual states, what is reused) before coding, build leaves first, keep raw key messages inside the component that owns them, render collections through Bubbles or a windowed slice (reference/ui-implementation.md), and include the tree in the Step 8 report.
Don't add anything the task didn't ask for — no speculative interfaces, no unrelated cleanup, no restyling.
Step 4 — Write or update the tests
Follow reference/testing.md and the neighbouring test style: table tests next to the code; parallel where safe
(never with t.Setenv/t.Chdir); t.TempDir/t.Context; in-process command tests through the repository's
runCLI-style helper asserting exit code, stdout, stderr, and the envelope; a new or updated golden file for any
changed JSON output (created/refreshed only through the test's -update flag, then read); a .txtar script under the
testscript Dir when the task adds a command or changes end-to-end behaviour (help, exit code, no-TTY output); pure
Update tests (plus a teatest program test when the task asks for one) for the TUI — and, for a screen/component
the task added or changed, the UI tests of reference/ui-implementation.md §4–§6: every visual state with
minimal/typical/many-items/long-content and right-to-left fixtures, each captured through
internal/tui/ui_screenshots_test.go as .ui-screenshots/<kind>/<Id>/<state>--<direction>--<theme>.txt for every
.ui-quality.yml direction × theme the TUI has (plus no-color), and the asserted accessibility checks at the file's
level. Produce every new exit code on
purpose in the exit-code table. Container-backed tests go in //go:build integration files only. Aim the new tests
at ≥ 80% statement coverage of every file you changed — the group gates on it per file.
Do not run the tests — iru-go-code-one-task-group runs them once per bucket. A new golden file is therefore written
by hand from the exact output the code produces (compact JSON, keys in struct order, trailing newline from
json.Encoder), and the group's test run confirms it; say so in Step 8 so a mismatch is traced back quickly.
Step 5 — Format and compile-check
A broken build in one task blocks every other task in a parallel bucket from validating. From the module root of each touched module:
- Format only the files you touched:
gofmt -w <files>; thengoimports -w <files>whencommand -v goimportsfinds it (with-local <module path>when.golangci.ymlsetsgoimports.local-prefixes), orgolangci-lint fmt <files>when the repository uses golangci-lint v2 formatters and it's installed. go build ./...— every package of the module compiles (this also catches a sibling's in-flight breakage).go vet <touched packages>, e.g.go vet ./internal/cli ./internal/tui— vet compiles the_test.gofiles too, so it is this skill's test-compile check; add-tags integrationwhen you touched anintegration-tagged file.go mod tidyonly when this task added a dependency it was allowed to add (Step 3) — never to "clean up".
No golangci-lint, go test, coverage or govulncheck here — the group runs them once.
Outcomes:
- Failure in code this task wrote or edited: fix it — it's the task's own code, not a finding to defer. Vet
findings (
printfverbs,copylocks, unreachable code, struct tags) count. - Failure outside the task's scope (a sibling task's in-flight edit, a pre-existing error): don't fix unrelated code; re-run once if it looks like a transient race with a concurrently-writing sibling, then note it in Step 8.
- The toolchain can't run (no
go, an installed Go older than thegodirective withGOTOOLCHAIN=local, no network for a first module download): report "compile-check unavailable" with the reason — the group's validation still catches real errors.
Step 6 — When to interrupt the user
Keep interruptions rare. Stop and use AskUserQuestion (or plain text if no real choice is offered) only when:
- The task is ambiguous in a way that changes correctness or scope and can't be safely inferred.
- The task's approach is infeasible against the real code (it names a package/function that doesn't exist, or demands a pattern the framework in use can't express).
- The task needs a new module dependency, a
godirective bump, areplace, a new binary, or a module major-version change (a/vNimport-path change) it didn't ask for. - The task would change an existing exit code number, rename/remove a JSON field, or make a read-only command mutate without saying so — an interface break to confirm, not to slip in.
- The task needs a secret embedded in code, config or
testdata/(reference/security.md) — a design problem to report.
Don't stop merely because the task is nontrivial. Don't report the task done to work around a blocker; report it as
blocked (Step 8). A conflict between reference/ and the surrounding code is not a reason to stop: follow the
surrounding code and report the divergence.
Step 7 — Check off this task's checkbox now
If this task did not stop on a blocker, update implementation_plan.md at the repository root before reporting back.
Re-read it fresh (sibling tasks may be editing it), locate this task's own checkbox line by the exact text passed in,
flip [ ] to [x] for it and every sub-task the work covered, and add a short note naming the packages and files
touched, e.g.:
- [x] Task 3. **Add `list` command with `--limit`/`--cursor`** — internal/cli/list.go, internal/catalog/list.go,
list_test.go + testdata/script/list.txtar + testdata/list.golden.json; group validation pending.
Keep the "group validation pending" wording — iru-go-code-one-task-group replaces it with the final outcome. Edit
only this task's own line(s). If this task stopped on a blocker, leave its checkbox untouched.
Step 8 — Report the outcome
Hand back a short summary:
- The shape detected (module path,
godirective, workspace or not, CLI framework, TUI library and import path, test harness: testscript/teatest/golden) and the packages touched, with the reference files actually read. - The files touched and the tests added/updated per package (not run), including
.txtarscripts and golden files (and whether a golden file was hand-written pending the group's run). - The compile-check result:
go build ./...andgo vetper package — passed, failed on this task's code and fixed, failed outside this task's scope, or unavailable (and why); which files were formatted and with which tool. - For terminal UI work: the
Screen › Region › Componentdecomposition, and the UI tests and captures added (component/screen/flow, states × directions × themes, accessibility rule ids checked; not run). - Interface changes: new/changed commands, flags, exit codes used, JSON fields/
schemaVersion, and whether the generated CLI reference/man pages need regenerating. - Dependency changes (
go.mod/go.sum), only when the task allowed them, with the resolved versions. - Whether this task stopped on a blocker, with enough detail for the caller to surface it.
- Any deliberate deviation from
reference/, and pre-existing violations noticed and left alone (os.Exitin commands, shell-stringexec, unsorted JSON fields, missing--in docs, global command variables).
This skill never captures a quality baseline, adds license headers, runs tests/coverage/golangci-lint/govulncheck, or starts containers.
Verification notes
Exercised on 2026-10-16 in $TMPDIR/iru-verify/cli/go-coding/greeter (outside this repository) on macOS 27 arm64 with
Go 1.27.2, golangci-lint 2.15.0 and govulncheck, against a CLI shaped like the iru-setup-go-cli contract
(cmd/greeter/main.go → cli.Main; internal/cli/{root,greet,schema,browse,output,errors,exitcode,term,signal}.go;
internal/tui Bubble Tea model; internal/version; testdata/script/*.txtar). Module versions resolved live from
proxy.golang.org that day: cobra v1.10.2 (pflag v1.0.9 via cobra; v1.0.10 is latest), charm.land/bubbletea/v2
v2.1.0, charm.land/lipgloss/v2 v2.0.6, github.com/rogpeppe/go-internal v1.16.0, golang.org/x/term v0.47.0,
golang.org/x/sync v0.24.0, github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20261012121918-43427fdbad12.
Fallback if a lookup fails: those versions.
go build ./...,go vet ./...,go test -race ./...(in-process exit-code table, golden JSON, envelope, colour policy, prompt policy, EPIPE, signal-cause and panic-recovery tests; pureUpdatetests and a teatest v2 program test; a testscript suite) andgovulncheck ./...were clean;golangci-lint runwith the v2 config recommended inreference/code-style.mdreported 0 issues after fixing what it found (unformatted struct, an unusednolint, an unwrappedp.Run()error). Every reference snippet was compiled and exercised in asnippets/package of the same module.- Behaviours verified, and recorded in
reference/: Cobra v1.10.2 runsValidateRequiredFlags/ValidateFlagGroupsafterPersistentPreRunE, so a mutually-exclusive-flags error first classified as INTERNAL (exit 1) until the root hook validated them early (now exit 2);EnablePrefixMatchingdefaults to false (gre→ exit 2 with a suggestion);--makes--helpa NAME;cobra/doc.GenManTreedoesn't create its output directory; an unrecovered panic exits 2 (= USAGE), hence the singlerecoverincli.Main; realkill -TERM/-INTgave 143/130;tool | head -n1dies silently by SIGPIPE (141) by default and returnsEPIPE(handled as exit 0) when SIGPIPE is ignored; Lip Gloss v2 has no per-stream renderer (colorprofile.WriterwithNoTTYstrips,Detecton a buffer →NoTTY); testscript v1.16'sMain(m, map[string]func())re-execution counts toward-coverprofile -coverpkg=./...(WithSignalswas covered only through it); teatest'sgoldenpackage registers-update, so declaring another-updatein the same test binary panics (flag redefined: update); gosec G204 fires on the safe argvexec.CommandContext;os.Rootrejects.., absolute paths and an escaping symlink; revivepackage-commentsandexportedfire oninternal/packages. - The scaffold-style testscript wiring (
RequireExplicitExec,UpdateScripts: *update, and a customstatus CODE exec …command built onts.Exec+*exec.ExitError) was added to the same module and pins exact exit codes (a deliberate wrong expectation failed ascodes.txtar:2: exit status 3, want 4). - The verification CLI used the generic
classify/startederror mapping; theiru-setup-go-clitemplates'App/wrapRunE/exitCodestructure thatreference/also describes was read from that skill (written concurrently) but not built here — its own verification covers it. reference/ui-contract.md(verbatim catalog copy) andreference/ui-implementation.mdwere added later for the UI contract without a Go toolchain on the authoring machine: the APIs they use (tea.View.Content,tea.RequestBackgroundColor/BackgroundColorMsg.IsDark,lipgloss.LightDark, Bubbles v2list.New/ItemDelegate,ansi.Strip) were checked against pkg.go.dev, the code sketches were not compiled.- Not verified: Windows and Linux hosts (only macOS arm64), a TTY-attached run of
browse(teatest drives the model without a real terminal), urfave/cli and kong projects (detection only), and the per-task skill's own end-to-end run inside theiru-codepipeline.