| name | github-tasks |
| layer | method |
| description | Track work as GitHub issues in this monorepo โ create epics + child/sub-issues, label them consistently by package/app, and tie them into the commit workflow. Use when planning a feature, breaking work into tasks, or asked to "track this in GitHub", "make issues", "set up tracking". |
| allowed-tools | mcp__github__issue_write, mcp__github__sub_issue_write, mcp__github__issue_read, mcp__github__list_issues, mcp__github__search_issues, mcp__github__get_label, Bash, Read, Grep |
GitHub Task Tracking
The canonical task tracker for this repo is GitHub Issues (FriendlyInternet/nuxt-crouton). This skill defines how to create and label them consistently so every task maps to a real part of the monorepo.
Write it as a hypothesis (assumption-first โ the default for every epic & issue)
Frame work as an assumption, not a task list. A task says "do X". A hypothesis says "we think if we do X, then Y will happen โ and Y is what we want", so later we can look back and honestly say whether we were right. It's more human, it forces the why and the how-we'll-know up front, and it's what the daily digest surfaces. Use it for every epic and issue as much as possible; only trivial chores (a typo, a dep bump) may fall back to a plain task description.
The hypothesis has 4 parts:
- We think that โ if we [do/change X], then [outcome Y] will happen โ and Y is what we want. (the hypothesis)
- We'll do that by โ [this, this, and that]. (the work)
- We'll be right if โ [these things turn out to be true]. (success conditions)
- We'll know by โ [measuring / checking these signals]. (measurement)
It's a lens over the sections below, not a new competing heading. Don't bolt a 5th section on โ map the hypothesis onto what's already required:
| Hypothesis part | Lives in |
|---|
| 1โ2 ยท the hypothesis + the work (plain) | the ๐ค For humans lead (open the body with "We think thatโฆ") |
| 2 ยท the work (precise) | the ๐ค For agents block |
| 3โ4 ยท we'll be right if / we'll know by | the ๐งช How to test section (retitle it ## ๐งช We'll be right if / We'll know by when it reads more naturally as the hypothesis's check) |
A good epic body therefore opens with a ## Hypothesis block, then the ๐ค/๐ค sections expand it, then the check closes it. See epic #359 and #357 / issues #358, #360, #361 for worked examples.
Writing for two audiences (REQUIRED โ applies to issues, PRs, and commits)
Everything that lands in GitHub is written for two readers, in this order:
๐ค For humans (first โ and it must be genuinely easy to read)
Lead with this. Plain language a busy person skims in seconds: what changed, why it matters, what to expect โ impact over mechanics. Short sentences, no unexplained jargon, no raw file paths unless they're the point. Use a diagram only when it makes the change easier to understand (a flow, a before/after, an architecture or state change). Mermaid renders in issue/PR bodies โ use it there. Never add a diagram for decoration; if it doesn't earn its space, leave it out.
๐ค For agents
A precise, structured block an AI can act on without guessing: scope, exact files/paths and symbols, behaviour changes, acceptance criteria, follow-ups, links to issues/docs.
Use explicit headings (## ๐ค For humans / ## ๐ค For agents) so both are obvious. Scale to the change โ a one-line human summary is fine for something small โ but always include both.
Record what you didn't do (Considered & rejected)
A "why not" is as load-bearing as a "why" โ it's the evidence the chosen path beat something, with reasons that may later expire. Whenever a decision had genuine alternatives, write them down, so future-us (and agents doing archaeology) stop re-litigating settled questions and understand the shape of a decision, not just its outcome.
- Add a short
Considered & rejected note โ one line per option: option โ โ why not. It lives in the ๐ค For agents block by default (or its own small block, or a comment on the issue/epic).
- Required only when alternatives were actually weighed โ trivial chores opt out (same as the hypothesis framing).
- The epic is the natural home for a cross-cutting "why not" โ record it in the epic's Decisions log (below), when the decision is made, while the reasoning is fresh.
Worked example (from epic #392):
Considered & rejected โ E2E perf
- Bump Playwright workers โ โ the harness shares one dev server + SQLite per job; parallel workers race on mutated state โ flaky.
- Shared "build packages once" prebuild job โ โ serialises a parallel matrix; saves CI minutes, not wall-clock.
- Cache the built packages โ โ a stale cache makes the regression smoke pass falsely โ defeating its purpose.
The epic's Decisions log (capture the call when it's made)
Considered & rejected catches the alternatives you weighed when you write the issue. But the decisions that leak worst are the ones made halfway through the work โ "team-scope at the query layer", "layout composition is deterministic, not LLM", "fake the payment in the POC". At authoring time they don't exist yet, so no up-front section can catch them; by close they're lost to chat.
So every epic carries a running ## ๐งญ Decisions log โ in the epic body, or a maintained pinned comment on the epic โ that you append to the moment a real design call is made, mid-work, while the reasoning is fresh. It's the same reflex as updating a POC's HANDOFF.md / spec.json at a sign-off (the spec skill), pointed at the epic: one concept, two homes.
The format โ newest at the bottom, one line per decision:
[YYYY-MM-DD] Decision โ why โ (Rejected: X โ why not)
Append-only โ reversals are kept, never edited away. This is the one deliberate difference from the POC spec.json, which supersedes (newest wins, prune the stale entry โ "this is how it behaves now"). A decision log does the opposite: when a decision is later reversed, append a new line recording the reversal and the rule learned โ don't touch the original. The reversal is the lesson, exactly as AGENTS.md demands ("negative results are first-class data โ record the reversal and extract the rule, don't delete it"). A ledger that erases its own reversals is the re-litigation trap wearing a fresh coat.
Keep it a habit, not a gate. Like the hypothesis framing, this is a strongly-modeled convention with a worked example โ not a required section and not hook-enforced. Decision capture dies the instant it's a box you tick to unblock a merge (you get "N/A" and "see chat"). Trivial epics opt out.
Worked example (a running log on an epic):
๐งญ Decisions
- [2026-06-14] Team-scope at the query layer (drizzle
where team_id), not per-route middleware โ one choke-point you can't forget to add on a new endpoint โ (Rejected: per-route guard โ trivially omitted on the next route added; Rejected: DB row-level security โ D1/SQLite has none).
- [2026-06-20] POC default layout is deterministic (
layout-compose.ts rules), not LLM-generated โ reproducible, reviewable, no token cost on every boot โ (Rejected: LLM "place the blocks" โ non-deterministic and slow, can't diff two runs).
- [2026-06-28] Reversed the 06-14 call for the reporting endpoints only: they team-scope in middleware after all โ their cross-collection joins made per-query scoping duplicative and error-prone โ rule learned: query-layer scoping for single-collection reads, middleware for joins.
How to test (REQUIRED on every closeable issue/PR โ written for a human)
Every issue that changes observable behaviour, and every PR, MUST carry a ## ๐งช How to test section written for someone who knows the app concept but not the code. It is not "run the unit tests" โ it is where a person clicks and what they should see. Treat it as the acceptance check: if a non-developer can't follow it to confirm the change, it's not done.
Write it as:
- What changed โ one or two plain sentences: what's different now vs. before.
- Where you'll see it โ the concrete surface: the URL/page, the button, the screen. Name it the way a user would ("the top-right log-out button", "the access-code screen"), not by file or component.
- Steps โ a short numbered walk-through, each step an action + the expected result. Include the before/after contrast where it matters ("previously the kassa still showed; now you land on the access-code screen").
- Test data โ any PINs, logins, or seed URLs needed to reproduce (e.g.
/test1/nl/vlaamsekermis, helper PIN 1234).
Keep it tight and skimmable. A mermaid flow is welcome when the steps branch or the state change is the point โ never for decoration.
Epic acceptance rollup (the epic is the verification unit)
Sub-issues are the work unit; the epic is the verification unit. When all of an epic's sub-issues have merged (the feature has "landed in the app"), post a single ## ๐งช Verify the whole thing comment on the epic before closing it โ so the owner does one QA pass instead of hunting across sub-issues:
- What landed โ one plain line per merged sub-issue/PR (what's now different).
- Where to test โ the one link: the deployed preview or production URL, plus any test data (PINs/logins/seed URLs) needed.
- Walkthrough โ the per-issue "How to test" steps stitched into one ordered pass a human runs end-to-end, with the before/after where it matters.
Close the epic only after that pass passes (or the owner confirms). This turns "a bunch of merged PRs" into a single "now go click these and confirm it all works" checklist for a non-technical owner. If a sub-issue couldn't be auto-verified (e.g. needs a device), say so explicitly in the rollup rather than implying it's confirmed.
Human-action tasks: assign them, and close them when answered (REQUIRED)
Not every task ends in a PR. When something needs the owner to act โ a visual sign-off, a decision, an approval, a manual run/deploy โ capture it as a discrete issue assigned to them, never as a bare comment buried on another issue. The owner's "assigned to me" filter is their entire to-do list; an action that lives only in a comment is invisible to it. Trackers/epics stay unassigned โ only actionable tasks carry an assignee, so "assigned" always means "you, now".
These tasks are resolved by a comment/confirmation, not a merge, so they need their own closing path โ and it's the agent's job, not the owner's:
- Close it the instant the action is confirmed done โ the owner says "looks good", the deploy goes green, the question is answered โ in that same turn, and drop its
status:* label. Then run the parent/epic walk-up below.
- An action answered in a comment but left open is a tracking leak: the assignee filter fills with done-but-open noise and stops being trustworthy. Closing travels with the answer, exactly as closing a leaf travels with its merge.
So an issue closes by either path โ Closes #NN on a merged PR, or a confirmed human-action task the agent closes on the spot โ and both then walk up the tree:
Closing a child? Always check the parent (REQUIRED โ do it the moment the PR merges or the task is answered)
A merged PR auto-closes the issues in its Closes #NN lines โ but a parent epic has no Closes line of its own, so it never auto-closes, and a fully-delivered epic left open is the most common stale-tracking bug. So the instant you close an issue โ or a PR merges that auto-closes one โ walk up the tree, unprompted. Don't wait to be asked and don't defer it to a later task: closing the leaf is not "done" until you've checked the branch above it. Closing the epic travels with the merge that closes its last child โ same logical step, not a follow-up someone has to remember.
- Resolve the parent:
mcp__github__issue_read (method: get) โ parent_issue_url, or read the epic's get_sub_issues.
- If the issue has a parent, check whether all of the parent's children are now
closed (get_sub_issues โ every child's state).
- If they are, the epic is ready to close โ but the epic is the verification unit, so never silently close it. Post the
## ๐งช Verify the whole thing rollup (above) as a comment on the epic, then explicitly ask the owner to close it (e.g. "All N sub-issues are merged โ close the epic?") and close it as completed only on their confirmation (or after the end-to-end pass passes). If a sub-issue couldn't be auto-verified (needs a device, a manual check), say so in the rollup rather than implying it's confirmed.
- Postmortem before closing (verify = does it work?; postmortem = how did it go?). After the verify rollup and before the epic is closed, run the
postmortem skill on the epic โ it posts a retro (what went well / what was hard, evidence-backed / 1โ3 improvement proposals) and offers to mint accepted, not-already-tracked proposals as workflow issues, and ends with a ๐ญ Next handoff โ the next epic to start + a paste-ready next-session prompt โ so closing one epic opens the next (#615). This is how the loop tightens over time (epic #403). Skip only for a trivial epic. Once the postmortem has run the epic carries status:ready-to-close, so the owner can close it in one gesture by commenting /close-epic on the epic (close-epic-on-comment.yml, gated on that label, #856) โ the label is the precondition, so an epic whose postmortem hasn't run can't be closed this way.
- Recurse: a parent can itself be a child of a grander epic โ keep walking up until you hit one with open siblings or no parent.
Don't stop at the issue you were asked about; closing the leaf without checking the branch above it leaves the epic falsely "in progress".
Recurring/standing chores are standalone, never epic sub-issues. A ticket that's re-armed on a cadence and intentionally never permanently closed (e.g. the quarterly dependency sweep) must not be a sub-issue of a deliverable epic. A future-dated child that legitimately never closes silently defeats the "are all children closed?" check above, so a fully-delivered epic is pinned open forever and never reaches its verify + postmortem close-out โ the most common stale-tracking bug, just slower to spot (the #233 โ #244 case: the epic sat done-but-open for days behind the next quarterly sweep). File the recurring chore standalone (it may link the originating epic for context, not parent to it), and when it re-arms, open the next occurrence standalone too. (#422)
Titles are human-first too. Issue/PR titles read like plain English that anyone grasps at a glance ("Run the whole app on a Raspberry Pi and print directly"), not jargon ("node-server preset + in-process TCP drainer"). Keep the technical specifics in the ๐ค body, never the title.
Core rules
- Every issue maps to a package or an app โ never "root". If it feels like root-level work (CI, deploy, ops), label it with the app it serves (e.g. CI that builds fanfare โ
app:fanfare). Harness / method work that serves the whole monorepo rather than one app โ .claude/**, AGENTS.md, skill/agent/hook changes โ maps to meta:agents (see rule 3). There is deliberately no root label.
- Exactly one
type:* label per issue โ except an epic, which carries epic instead of a type.
- Component label = where the source actually changes (mirrors the commit-scope convention):
pkg:<name> for package source (packages/*, e.g. pkg:crouton-sales)
app:<name> for app/deployment/ops work (apps/*, e.g. app:fanfare)
worker:<name> for workers/*
meta:agents for harness / method work with no single app or package owner โ .claude/** (skills, agents, hooks), AGENTS.md, and cross-cutting method changes. This is the component for that work: it serves the whole monorepo, so it satisfies rule 1's "never root" rather than being an exception to it.
- Package work that also lands a schema/config change in an app gets both (e.g.
pkg:crouton-sales + app:fanfare).
- Use meta labels where they apply:
epic, spike, needs-triage.
- Link issues & PRs when talking to the user. Any time you mention an issue/PR in a chat reply, render it as a clickable link to the full URL (
[#303](https://github.com/FriendlyInternet/nuxt-crouton/issues/303), [#376](https://github.com/FriendlyInternet/nuxt-crouton/pull/376)) so the owner can open it in one click. This is a chat-reply convention only โ commit messages keep bare (#NN), and PR bodies use Closes #NN (not a URL) so GitHub auto-closes the issue on merge.
Structure
- Epic โ one tracking issue per initiative. Body: goals, a checklist of workstreams, links to design docs. Labels:
epic + the primary pkg:/app: it spans.
- Child issues โ one per workstream, each linked as a sub-issue of the epic so GitHub shows a progress bar.
- Keep issue bodies tight: scope, acceptance criteria, links to
docs/, and the required ## ๐งช How to test section (above).
How to create them (tools)
Issues, sub-issues, and labels are managed through the GitHub MCP tools:
- Create / update:
mcp__github__issue_write (method: create|update, pass labels: [...]).
- Link a child under a parent:
mcp__github__sub_issue_write (method: add, issue_number = parent, sub_issue_id = the child's id from its create response โ not its number).
- Read / list:
mcp__github__issue_read, mcp__github__list_issues, mcp__github__search_issues.
Scan without overflowing the context. A broad list_issues / search_issues (e.g. labels: ["epic"], or a keyword search with no in:title) can return a 90kโ140k-char blob that overflows the tool result and has to be sliced out-of-band โ pure wasted turns. For any repo-wide scan (the dedup and epic walk-up steps are where this bites): pass minimal_output: true, keep perPage small (5โ10), and prefer in:title filters over broad body matches. Only widen when a narrow query genuinely misses.
Labels must already exist before you can apply them โ applying an unknown label errors. New labels are added via labels-as-code (below), not by the API.
Projects v2 boards can't be created or managed via these tools (UI-only). Tell the user to create the board in the web/iOS app; if they enable the project's "Auto-add" workflow, issues you create land on the board automatically.
Labels-as-code
The label taxonomy lives in .github/labels.yml and mirrors the workspace: pkg:* for every packages/*, app:* for every apps/*, worker:* for workers/*, plus type:* and meta labels. It's synced non-destructively by .github/workflows/labels.yml (skip_delete) on changes to main or via workflow_dispatch.
To add or change a label: edit .github/labels.yml, commit, and let the workflow sync it on merge to main. When a new package or app is added, add its pkg:/app: label here too.
Fit into the task workflow
GitHub issues slot into the repo's task-execution flow (see CLAUDE.md):
- Check for existing work FIRST, then pick / open an issue โ the issue is the unit of work. Because sessions are ephemeral and a teammate (or a past you) may already have opened the epic/tasks, always search before creating. This is no longer a soft step: run the
issue-dedup skill (it searches open and recently-closed work by epic label + keywords, surfaces matches, and forces a reuse / replace / new decision), and the require-issue-dedup PreToolUse hook blocks any issue_write create whose body lacks a Dedup-checked: attestation line. If a matching epic or task already exists, continue that one (assign yourself, set status:in-progress) instead of opening a duplicate. Only when nothing matches do you open a new epic + sub-issues for a multi-step initiative. (Then, when you pick up an issue, the sibling issue-sanity-check skill is your pessimistic go/no-go โ see CLAUDE.md step 1.)
- Mark in progress โ do this the moment you START, not after. Apply the
status:in-progress label (swap to status:blocked when waiting; remove the status label on close). The label is the signal that moves the board: .github/workflows/project-status.yml listens for the status:in-progress/status:blocked label and writes the Project's Status field (โ In progress / Blocked) via a PAT, since these MCP tools can't write Projects v2 fields directly (list_issue_fields is empty). PR opened โ In review and merged/closed โ Done are handled by the same workflow + the Project's built-in workflows. Prerequisite: the PROJECTS_TOKEN repo secret must be set โ without it the workflow is a green no-op and nothing moves, so the label is the only signal and the board won't reflect it.
- Branch + do the work โ work on a feature branch; follow
CLAUDE.md patterns; run pnpm typecheck.
- Commit โ use the
/commit skill, referencing the issue in the body (e.g. (#NN)).
- Open a PR โ early is fine. Put
Closes #NN in the body so the issue auto-closes on merge. Let CI run and fix failures (the PR can be watched/autofixed).
- Merge preserving commits (merge/rebase โ don't squash by default; squash only a noisy
wip/oops history, per AGENTS.md ยง Commits โ Merge policy) โ the issue closes automatically and the branch is deleted. Don't push feature work straight to main.
- Walk up the epic tree (REQUIRED โ part of the merge, not an afterthought). The moment the merge auto-closes the leaf issue, run the parent check in "Closing a child? Always check the parent" above. If that merge closed the epic's last open child, post the
## ๐งช Verify the whole thing rollup on the epic, run the postmortem skill (retro + improvement proposals โ see step 4 there), and ask the owner to close it (close on confirmation). A merge is not "done" until the parent epic is either closed or explicitly handed off for the verify + postmortem pass. When watching/auto-merging a PR, do this walk-up as soon as the merge lands.
Work lands via PRs, not direct pushes to main. Issues are the source of truth for what to do; docs/PROGRESS_TRACKER.md (if used) becomes an optional phase-level rollup, not the per-task tracker.
Quick reference
| Want | Label(s) |
|---|
| New feature in a package | type:feat pkg:<name> |
| Feature touching a package + app schema/config | type:feat pkg:<name> app:<name> |
| App/deployment/ops/CI work | type:chore/type:docs app:<name> |
| Cross-cutting initiative | epic pkg:<name> (+ app:<name>) |
Harness / skill / agent / method change (.claude/**, AGENTS.md) | type:docs/type:chore meta:agents (+ epic if an epic) |
| Time-boxed proof-of-concept | spike type:feat <component> |