Files
Skills/issue/SKILL.md
T
claude 22abd37458 feat(issue): work-start signal is assignee, not label
Retire the in-progress label. Work-start on a tracker issue is now
signalled by assigning the issue to the dedicated tracker user
claude — one write, no removal; the assignee survives the close as
provenance. The live worktree branch (issue-N-<slug>, from the
worktree convention) is the matching git-side signal, and it
disappears with the ratified merge on its own.

Rationale (user decision 2026-07-14): a set-and-remove label is
administrative overhead with a lying-label failure mode when the
removal is forgotten; an assignee needs no removal and stays
meaningful after close.

issue/SKILL.md documents the mechanic (PATCH .../issues/<n> with
{"assignees": ["claude"]} — the call replaces the assignee set;
Gitea has no additive endpoint, verified against the live swagger).
boss/SKILL.md Step 2 assigns on a clean pick (chat-seeded runs
assign when the reference issue is created). conventions.md § Issue
tracker carries the cross-project statement. The label itself was
deleted from all Gitea repos that had it (AILang, Aura, Skills,
doctate); the label vocabulary in ~/dev/CLAUDE.md no longer lists
it.
2026-07-14 12:42:50 +02:00

369 lines
22 KiB
Markdown

---
name: issue
description: Use when creating, editing, commenting on, or closing a Gitea issue or milestone — filing a bug, feature, or idea into the tracker, or turning a finding into a tracker item. Binds every actor that creates or edits an issue or milestone, human or skill, not just an explicit invocation. Covers the issue-writing conventions and the tea-CLI mechanics for the operation.
---
# issue — write and edit Gitea issues
> **Violating the letter of these rules is violating the spirit.**
## Overview
An issue is a durable artefact in the forward-queue, read later by
humans and by other agents — usually without the conversation that
produced it, and a human reads it linearly, holding only what is on the
page. It must stand on its own. This skill unifies how issue text is
written so every issue reads the same way, and records the `tea`
mechanics for creating and editing them.
An issue states facts and clearly-flagged claims about a concern. It
is not a chat message.
## When to use
| Situation | Use this? |
|---|---|
| "Open an issue for X" / "file a bug about Y" | Yes |
| "Edit / re-label / comment on issue #N" | Yes |
| "Close #N" (outside a `closes #N` commit) | Yes |
| Create or edit a **milestone** (the container for a big chunk) | Yes |
| Turning a finding or deferred work into a tracker item | Yes |
| A note relevant only to the current conversation | No |
**This binds every author, not just an explicit invocation.** Any actor
that creates or edits an issue or a milestone — a person at the keyboard or
any skill or agent doing it as a side-effect — writes it to these
conventions; there is no second, looser standard for tracker text written
in passing. Other skills reach the tracker through them constantly:
`brainstorm` parks a deferred spec as a backlog issue, `audit` files
regressions, and `boss` files skill-system deficiencies (with an
autonomous-provenance body block — see `../boss/SKILL.md` § Skill-system
feedback) and logs each fork decision on the run's reference issue (§ The
reference issue). This skill is the shared convention they lean on.
## The four writing rules
These apply to every issue, on create and on edit — and equally to
every comment: a comment is tracker text like any other, and rule 4 in
particular is written around a reader who lands on a bare comment anchor.
The one carve-out is rule 3 — a comment has no title.
1. **Impersonal and tool-neutral.** Declarative voice — no "you", no
imperative aimed at the reader, and equally no first-person narration of
the loop's own acts ("I'll now run audit", "I'll hand this off next"):
an issue is nobody's monologue, neither addressed to a reader nor spoken
by the loop. State the situation, not what an actor does about it. The
issue is also a durable, tool-agnostic artefact, so it never carries the
orchestration loop's own next move — any direction of *what the loop runs
next*: most blatantly naming a skill or pipeline step ("enter `specify`",
"dispatch `implement`"), but equally a role-periphrasis for the same
hand-off that names nothing ("hand it to the spec writer next", "kick off
the next pipeline stage"). That is session control-flow, meaningless to a
tracker reader — a human months later, a different tool — who is not the
loop; rephrasing it in declarative voice does not save it, because the
*content* is harness-native.
- The discriminator is **status vs. dispatch**, not whether a skill is
named. A sentence stating *what is true of the work* — the next
**work** itself ("next: a breakout-style trend entry") or a durable
readiness **status** ("design settled — ready for spec production";
"implementation done — ready for audit", fine even when the phase-noun
is also a skill name; "blocked on #N") — is legitimate. A sentence
directing *who runs what next* is out **whether or not it names a
skill**. Apply it sentence by sentence: does it describe the work's
condition (keep) or an actor's next move (cut)? "What's next" otherwise
lives in the issue's open/labelled state, not a prose dispatch of the
next skill in the body.
- Avoid: "You need to fix the parser because you broke escaping." /
"Next step: enter `specify` from this comment." / "Ready to hand off to
the spec writer next."
- Use: "The parser drops backslash escapes in quoted strings." / "The
design is settled here — no open load-bearing fork remains; ready for
spec production."
2. **Validated or flagged.** Every factual statement is either
verified — carrying its evidence (command output, log line,
`file:line`, a reproduction) — or explicitly marked as an
unverified claim, so a reader never mistakes a guess for a fact.
- Verified: "`parse()` returns `None` for `a\"b` — see
`parser.rs:84` and the repro below."
- Flagged: "Claim (unverified): the regression likely landed in
0b96983; not yet bisected."
- Where it sharpens the point, drop in a concrete code example —
the snippet that triggers the failure, or the call as it
*should* behave. A fenced block beats a prose paraphrase:
````
```rust
parse("a\"b") // => None, want Some("a\"b")
```
````
3. **Imperative, concise title.** Verb-first, one line, no trailing
period — scannable in the list, mirroring commit-message style.
Checkboxes (`- [ ]`) are allowed in the body for sub-points or a
small acceptance checklist. A comment carries no title, so this rule
alone has no comment analogue — the other three bind it unchanged.
- Avoid: "parser bug" / "There is a problem with the parser."
- Use: "Preserve backslash escapes in quoted strings"
4. **Self-contained — reachable AND comprehensible.** The issue stands on
its own, without the conversation that produced it. Two tests apply to
every reference, and a pointer can pass one while failing the other.
- **(1) Reachability.** *Starting from this entry alone — a reader may
have arrived by a direct comment-anchor link, no chat and no sibling
comments loaded — can they follow it to its target?* If not, it is a
dangling pointer: reproduce the content inline, or swap in a
resolvable locator.
- **(2) Comprehension.** Does the issue make its point *without* the
reader absorbing what the target says? A human reads linearly and
holds no context-wide view; unlike an LLM — which usually has the
referenced sources already in its context — a human has only what is
on the page. So where the issue's substance leans on what a reference
*contains*, state that substance inline beside the pointer: the
reference then corroborates or lets the reader go deeper, it does not
carry the meaning the issue omits. A pointer can pass (1) and fail
(2): `#42` resolves, yet a linear reader still does not know what
`#42` says. A *pure further-reading* pointer — one the issue's point
does not lean on — needs only to resolve.
**Reachable — necessary, not sufficient.** Resolving satisfies test (1)
only; of the forms below, only *content stated or quoted inline* also
passes test (2) unaided.
**No absolute URL carrying a server name or port** — it rots when the
host moves and leaks the deployment; every reference below has a
host-free form:
- content stated or quoted inline;
- an issue/PR number (`#42`; a cross-repo ref needs the `owner/repo#N`
form with no space — `Brummel/Skills#11`, not `Skills #11`, which
binds `#11` to the current repo) or a commit SHA (`a1b2c3d`, resolves
on push) — Gitea auto-links these against the current host;
- a **relative Markdown link** — Gitea resolves it against the repo
root, so it carries no host in the source or the rendered link: the
way to make a file section, a line range, or a specific comment
clickable. The base is the repo root, so a file link needs the `src/`
segment; without it (`commit/<sha>/…`, `branch/…`) it does not
resolve:
- a file at a pinned commit —
`[what it says](src/commit/<sha>/docs/design.md#user-content-<heading-slug>)`,
or a line range `…/parser.rs#L84-L90`; prefer `src/commit/<sha>/…`
(immutable) over `src/branch/<branch>/…` (drifts);
- a specific comment — `[what it says](issues/N#issuecomment-M)`
(a plain `issues/…` path, no `src/`; the bare `#issuecomment-M`
fragment alone only resolves in-page on that comment's own issue);
- a repo-relative path, optionally with a line range (`parser.rs:84-90`),
as a plain locator where a clickable link is not worth building;
- a same-entry section marker (`§5`); a convention-resolvable in-repo
slug (a `docs/specs/` slug, a cycle number).
**Unreachable — rewrite or inline; these never resolve from the
tracker:**
- **The producing chat by reference, in any phrasing** — "the
in-context discussion", "settled before the run", "the session /
cycle transcript", an in-context juror/panel verdict (`D3`). No
anchor can ever reach it; the irrecoverable case. (State the
decision's intention inline in English — see the carve-out below.)
- **A weak prose pointer** — "above", "below", "earlier", "the prior
comment", "logged above", "as discussed" — with no resolvable
locator: a relative link to the target comment
(`issues/N#issuecomment-M`), a `#N`, or a *same-entry* `§` marker. A
`§` pointing into another comment or file is itself unreachable —
link that comment with `issues/N#issuecomment-M`.
- **An artifact named by role, not locator** — "the OOS harness
issue", "see the ledger", a `Depends on` target — carrying no `#N` /
SHA / relative link / repo path.
- **A path outside this repo** — `/mnt/...`, `~/.claude/...`, a Claude
auto-memory slug, another project's `BLOCKED.md`. Only a path in
*this* repo is reachable.
- **A bare code or ordinal whose defining set the entry does not
reproduce** — a plan-item / ledger / lens code (`I7`, `C16`), an
index ("decision #5"). Gloss it in place, or restate the set.
- **A locator-shaped token that resolves to nothing** — a template
placeholder (`#(A)`), or a `#`-number in the wrong format (`#0071` —
issue refs are unpadded integers like `#71`; a leading zero matches
nothing).
A chat decision becomes referenceable only by being **written to the
tracker first** — log it, then cite that comment; a chat is reachable
by being written down, never by being pointed at (the `boss`
reference-issue mechanic, `../boss/SKILL.md` § The reference issue).
**These stay self-contained** (don't strip them chasing the rule): a
dated inline statement of a chat decision's *intention*, in English
("dissolve the old into the new, but only once the new carries" —
2026-07-03) — the reader gets the substance, and only a surplus
"settled in-context" sentence *beside* it is the violation; an
enumerated fork `(A)`/`(B)` whose options are restated here; a
glossary code glossed inline; a provenance sentence whose fact is also
stated inline, so long as it embeds no unresolvable pointer (keep
"decided", drop or anchor "logged above"). Flag the *pointer* a
tracker-only reader can't resolve, never the content it reproduces.
**State the intent, don't paste the chat.** The self-containment
vehicle is the decision's intention rendered in English, not the chat's
verbatim words. The chat is typically not English (chat language per
settings) while an issue is an English repo artefact ("would it be
committed → English"), so a pasted quote imports a foreign language and
the chat's register into the artefact — and it usually adds nothing the
intent statement doesn't already carry. Quote the exact words only
where the precise wording is itself load-bearing, and even then render
them in English (translate the original); never import a non-English
chat quote verbatim.
- Avoid: "Approach B (the mechanism refinement decided + logged
above)." — "logged above" points to a sibling comment with no
resolvable locator, so a reader who landed on this comment can't reach
the refinement; the violation is the unresolvable pointer, and it
stands even when the mechanism is restated inline. The bare label
"Approach B" compounds it — it indexes an A/B fork this entry never
reproduces, so the letter means nothing to a reader who didn't see
the fork laid out.
- Use — drop the chat-native label, name the option on its own terms,
then inline or link the refinement: "The honest per-held-cycle bleed
(accrue each held cycle, dump at close) — refined in
[issue 148's comment](issues/148#issuecomment-2142)."
**Language:** title and body are English — an issue is a repo
artefact mirrored to Gitea ("would it be committed → English", per
`~/dev/CLAUDE.md`).
**Milestones** obey the same conventions, with one adjustment. A milestone
is a container — a big chunk, usually carrying a spec — so its title
*names* the container ("Cycle 7 — streaming rewrite") rather than taking
rule 3's imperative verb form. Its description obeys rules 1 (impersonal
and tool-neutral), 2 (validated or flagged) and 4 (reachable and
comprehensible) exactly as an issue body does.
## tea mechanics
`tea` is pre-authenticated via its own config (`~/.config/tea/`).
Never read or echo that file — it holds a token. `tea` auto-detects
the repository from the git remote; override with `--repo owner/name`
when running outside the repo (the project's issue tracker — its
CLAUDE.md project facts name the repo slug and the list/show commands).
### Actor identity
Agent-driven tracker writes — create, edit, comment, close, re-label,
milestone changes — run under the dedicated Gitea user `claude`, not
the repository owner, so tracker history distinguishes agent actions
from the owner's own. The `claude` credentials live in
`~/.claude/gitea-claude.env` (`GITEA_URL`, `GITEA_TOKEN`) and are used
via the Gitea REST API. tea's own config remains the owner's login:
reads may run under either identity, but an agent performing a write
authenticates with the `claude` token. The account needs collaborator
(write) access on a repository before it can act there.
| Operation | Command |
|---|---|
| List open | `tea issues ls --state open` |
| View one | `tea issues <idx>` (body only) — add `--comments` to include the discussion thread: `tea issues --comments <idx>` |
| Create | `tea issues create -t "<title>" -d "<body>" -L "<labels>"` |
| Edit body/title | `tea issues edit <idx> -t "<title>" -d "<body>"` |
| Re-label | `tea issues edit <idx> --add-labels a --remove-labels b` |
| Comment | `tea comment <idx> "<body>"` |
| Close / reopen | `tea issues close <idx>` / `tea issues reopen <idx>` |
| List milestones | `tea milestones ls --state open` |
| Create milestone | `tea milestones create -t "<title>" -d "<description>"` |
| Close / reopen milestone | `tea milestones close "<name>"` / `tea milestones reopen "<name>"` |
Notes:
- The body flag is `--description`/`-d`, **not** `--body`. For a
multi-line body pass a heredoc via command substitution:
`tea issues create -t "..." -d "$(cat <<'EOF'` … `EOF`\n`)"`.
- Labels are matched by name (`-L feature,bug`); they must already
exist in the repo. The project's label vocabulary lives in
`~/dev/CLAUDE.md` (`BLOCKER`, `feature`, `bug`, `idea`). There is
**no in-progress label**: work-start is signalled by assigning the
issue to the `claude` tracker user (§ Actor identity) — one write,
no removal; the assignee survives the close as provenance. Assign
via the API (a write, so the `claude` token):
`PATCH /api/v1/repos/<owner>/<repo>/issues/<n>` with body
`{"assignees": ["claude"]}` — the call **replaces** the assignee
set (Gitea has no additive endpoint), so when the issue already
carries assignees, include them in the array. The live worktree branch
(`issue-N-<slug>`) is the matching git-side signal.
- Before editing, view the issue first (`tea issues <idx>`) — do not
edit blind. Status/discussion updates go in a `tea comment`, not by
overwriting the body. Note `tea issues <idx>` prints the **body
only**; pass `--comments` to read the existing thread first (the bare
form would otherwise prompt for it interactively, which hangs a
non-interactive agent).
- Routine closing is done by a `closes #N` line in the commit body on
push (per `~/dev/CLAUDE.md`); `tea issues close` is for closing
without an accompanying commit.
- `tea milestones` has no `edit` subcommand (create / list / close /
reopen / delete only). To change a milestone's title or description,
use the Gitea API (`PATCH /api/v1/repos/<owner>/<repo>/milestones/<id>`,
the numeric `<id>` from `tea milestones ls --fields title,id`); the
reachable-and-comprehensible conventions apply to the new description.
## Common mistakes
| Mistake | Why it's bad | Instead |
|---|---|---|
| "You should…" / reader-directed imperative | Breaks rule 1, ages badly | State the situation declaratively |
| Orchestration next-step in title or body — directing who runs what next, whether a skill is named ("enter `specify`", "dispatch `implement`") or periphrased ("hand it to the spec writer next") | Harness session control-flow, invisible to a tracker reader who isn't the loop; declarative phrasing doesn't save it | Drop it — a tool-neutral readiness status ("ready for spec production"); the issue's open/labelled state carries "what's next" |
| A guess written as fact | Misleads future actors | Flag it: "Claim (unverified): …" |
| Title as a noun blob ("parser bug") | Not scannable | Imperative verb phrase |
| `-d` body with a guessed flag (`--body`) | tea rejects it | `--description`/`-d` |
| Editing without viewing first | Clobbers others' edits | `tea issues <idx>`, then edit |
| Reading/echoing tea's config | Token leak | tea is already authenticated |
| Pointer to the chat that produced the entry ("in-context", "the cycle transcript", a juror code) | The chat is invisible to a tracker reader — never reachable | State the decision's intent inline in English, or log the decision to the tracker and cite that comment with a relative link (`issues/N#issuecomment-M`) |
| A verbatim chat quote pasted inline, especially not in English | Imports a foreign language and the chat register into an English artefact; usually just restates the intent | State the decision's intent in English; quote verbatim only where the exact wording is load-bearing, and then translated |
| "above" / "below" / "the prior comment" with no anchor | A reader on a direct comment link can't tell which entry | Add a relative link to the comment (`issues/N#issuecomment-M`), a `#N`, or a *same-entry* `§` marker |
| Artifact or path named without a locator — "the harness issue", "see the ledger", `/mnt/...`, `~/.claude/...` | Nothing to navigate to from the tracker | Supply a `#N`, SHA, in-repo path, or relative link — or reproduce the fact inline |
| Bare un-glossed code or ordinal (`I7`, `C16`, "decision #5") | Its defining set isn't in the entry | Gloss it inline, or restate the set |
| A reachable ref the point leans on, left un-glossed (`Blocked on #42`; "see `parser.rs:84` for the mechanism") | Resolves (test 1) but a linear reader learns nothing (test 2 fails) | Gloss the substance inline; attach the link as the go-deeper pointer |
| An absolute URL with a server name or port (`http://host:port/…`) | Rots when the host moves; leaks the deployment | Host-free form — `#N`, a relative `issues/N#issuecomment-M`, or `src/commit/<sha>/path#…` |
## Red flags — STOP
- The word "you" anywhere in title or body
- An orchestration next-step in title or body — directing who runs what
next, whether a skill is named ("enter `specify`", "dispatch
`implement`") or periphrased ("hand it to the spec writer next") — in
place of a tool-neutral status (a tool-neutral statement of the next
*work*, or a readiness status, is not what this flags)
- A factual statement with neither evidence nor a "claim" flag
- A title that is a noun phrase or ends with a period
- A reference with no resolvable locator (`#N`, a commit SHA, a host-free
relative link like `issues/N#issuecomment-M` or `src/commit/<sha>/path`,
an existing repo path, a `docs/specs/` slug or cycle number, a
same-entry `§`, or the fact reproduced inline), or a bare never-glossed
code whose defining set isn't in the entry — in particular:
- a pointer to the chat that produced the entry ("in-context", "the
cycle transcript", a juror code); the chat is never tracker-reachable
- "above" / "below" / "the prior comment" / "as discussed" with no
resolvable locator — a relative link to the comment
(`issues/N#issuecomment-M`), a `#N`, or a same-entry `§`
- an out-of-repo path (`/mnt`, `~/.claude`, a `memory <slug>`,
another project's `.md`) cited as the basis
- a `#`-token that resolves to nothing — a `#(A)` placeholder or a
wrong-format `#0071`
- An absolute URL carrying a server name or port anywhere in title or body
— use the host-free relative form (`issues/N#issuecomment-M`,
`src/commit/<sha>/path#…`)
- A reachable reference the entry's point leans on, its substance not
glossed inline — a linear reader can't grasp the entry without navigating
away (the comprehension test, rule 4)
- A verbatim chat quote pasted inline, especially one not in English —
the chat is typically non-English and the issue is an English artefact,
and the quote usually only restates the intent; state the decision's
intent in English (quote the exact words only where the wording itself
is load-bearing, and then translated)
- About to `tea issues edit` without having viewed the issue
- About to print or `cat` the tea config
## Cross-references
- `~/dev/CLAUDE.md` — repo-English rule, label vocabulary, the
`closes #N` commit convention (the tracker is the forward-queue).
- `../brainstorm/SKILL.md`, `../audit/SKILL.md`, `../boss/SKILL.md` —
callers that file issues through these conventions. `boss` files
skill-system feedback with an autonomous-provenance body block
(§ Skill-system feedback), and its § The reference issue is the
constructive escape hatch for rule 4: log a chat decision to the
tracker first, then cite that comment instead of pointing at the chat.