9234d8004f
Five tracker issues, all in the bridge layer except the last. pull.py fetches comments by default (#6). The thread was reachable only through --comments, and only for a single issue, so a bulk pull left every local copy silently incomplete: a missing <id>.comments.md could mean "no comments" or "never asked". Now every written issue gets its thread, in key and filter mode alike; an empty one costs no request (the count rides in the list payload) and writes no file, and a file left over from an earlier pull is deleted. --cached skips the thread along with the body. The --comments flag is gone. labels.py bootstraps the canonical label set (#7). Labels used to appear as a side effect of the first push that happened to use them, so a repo could not be filtered by type/bug until somebody pushed a bug. The set is finite and already described by the domain taxonomy — 6 type/* and 5 severity/* — which makes it a run, not a decision. Names and exclusivity come from issue.TYPES / SEVERITIES / EXCLUSIVE_NS, colors from map.label_specs; no list is duplicated. An exact name is never re-created or patched. Lookalikes (bug, Bug, "type: bug", kind/bug) are reported with their id and left alone — renaming somebody else's label is a decision, not a migration. Color or exclusive drift is printed, and changed only under --fix. branch: carries Gitea's ref (#8). map.to_payload sends ref only when the field is non-empty, since ref="" would clear whatever the server has; from_api reads it back; push fills an empty one from `git rev-parse --abbrev-ref HEAD` and writes it into the issue file. A hand-written value is never overwritten, on create or on --update. Detached HEAD and running outside a repo warn and send no ref. Reading the branch is the only thing these scripts ask of git. The domain needs no change: unknown keys already ride in Issue.extra and render after the domain fields. Bulk pulls no longer store closed issues (#10). Filter mode wrote every payload the server returned, so --state all dragged the closed backlog into a store that gets read whole — INDEX.md, grep over tmp/issues/*.md. They are still enumerated, the number left out goes to stderr, and an issue already on disk is refreshed either way so the local copy learns it was closed instead of staying open forever. --state closed stores them, and key mode is exempt: an address is not a bulk read. /tea:issue gains a "Writing a proper description" procedure (#9). Six steps from reading an issue to issue_check.py, the rule that a missing fact is found in the repository or asked about rather than invented, and the note that the procedure is identical for origin: local and origin: gitea while delivery to the tracker belongs to /tea:sync. No new script. Verified: labels.py run for real against claude-skills/tea (9 created, 2 already present) and idempotent on a second run; pull.py exercised live for the closed-skip, --state closed, key-mode and comment paths; the push write path covered offline with the transport stubbed. skills/issue/scripts/ still imports stdlib only, with no subprocess. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
309 lines
10 KiB
Markdown
309 lines
10 KiB
Markdown
# Issue format
|
|
|
|
Canonical format for every issue in this project, whether it ever reaches a
|
|
tracker or not. Designed to be unambiguous for both humans and LLMs: fixed
|
|
English section headers in a fixed order, verifiable acceptance criteria, one
|
|
issue = one deliverable. Source spec: the project wiki
|
|
([Issues-Workflow](https://git.noodles.cam/claude-skills/tea/wiki/Issues-Workflow)).
|
|
|
|
Nothing here depends on Gitea. How these files are mapped onto a tracker is the
|
|
sync layer's business — see `/tea:sync`.
|
|
|
|
## Identity
|
|
|
|
An issue is one file, `tmp/issues/<id>.md`, and `id` is a slug: lowercase
|
|
ASCII, digits, single dashes, derived from the title. **The slug is the
|
|
identity.** It is stable for the life of the issue — a retitled issue keeps its
|
|
slug, and an issue pushed to a tracker keeps it too. Tracker numbers are a
|
|
foreign key stored in a field, never the name of anything.
|
|
|
|
```
|
|
tmp/issues/wire-sqlc-appclick.md
|
|
```
|
|
|
|
## Metadata block
|
|
|
|
One field per line, lists inline, so plain `grep` works without a parser:
|
|
|
|
```markdown
|
|
---
|
|
id: wire-sqlc-appclick
|
|
state: open
|
|
labels: [type/task, tech/sql]
|
|
assignees: [naudachu]
|
|
milestone: v0.2
|
|
depends: [migrate-schema]
|
|
origin: gitea
|
|
branch: feat/wire-sqlc
|
|
gitea: claude-skills/tea#42
|
|
remote-updated: 2026-08-09T18:24:01Z
|
|
synced: 2026-08-09T18:40:00Z
|
|
url: https://git.noodles.cam/claude-skills/tea/issues/42
|
|
---
|
|
# Wire sqlc into the appclick repo layer
|
|
|
|
## Summary
|
|
…
|
|
```
|
|
|
|
| Field | Owner | Meaning |
|
|
|---|---|---|
|
|
| `id` | domain | slug; equals the file name |
|
|
| `state` | domain | `open` or `closed` |
|
|
| `labels` | domain | see namespaces below; exactly one `type/*` |
|
|
| `assignees` | domain | logins; may be empty |
|
|
| `milestone` | domain | title, or `none` |
|
|
| `depends` | domain | ids this issue depends on — **the authoritative graph** |
|
|
| `origin` | domain | `local`, or the name of a tracker this also lives in |
|
|
| `gitea` | sync | the handle in that tracker: `owner/repo#N` |
|
|
| `branch` | sync | the tracker's branch link (Gitea `ref`); push fills an empty one with the current git branch, and never overwrites a filled one |
|
|
| `url`, `synced`, `remote-updated`, `comments` | sync | bookkeeping |
|
|
|
|
Domain fields render first, in the order above; sync fields follow, sorted.
|
|
|
|
`origin` is domain-owned on purpose: *whether* a piece of work exists anywhere
|
|
but here is a fact about the work. *Where* that is, and how to reach it, is the
|
|
sync layer's business — the domain carries `gitea:` and the rest through
|
|
load/save verbatim and never reads them. That passthrough is why one file can
|
|
represent a local issue and a synced one without a second format.
|
|
|
|
`origin: local` is a **durable state, not a pending one.** An issue that never
|
|
leaves this machine is complete and valid. Pushing is optional and additive.
|
|
|
|
## Language rules
|
|
|
|
- **Issue title**: English, imperative mood, no type prefix — the type lives in
|
|
the label, not the title. Good: `Fix tea-guard crash on empty settings file`.
|
|
Bad: `fix: crash`, `[bug] crash`, `Крашится гвард`.
|
|
- **Section headers**: the exact English literals below, as `##` headings, in
|
|
the given order. Do not translate, rename, or reorder them.
|
|
- **Body prose** (text inside sections): Russian.
|
|
|
|
## Label namespaces
|
|
|
|
Four namespaces classify an issue. Two are exclusive (at most one label from
|
|
the namespace), two are free-form:
|
|
|
|
| Namespace | Exclusive | Purpose |
|
|
|---|---|---|
|
|
| `type/*` | yes | What kind of work; primarily its business value. Mandatory, exactly one. |
|
|
| `severity/*` | yes | Business impact. At most one; apply when the impact is known. |
|
|
| `tech/*` | no | Technology the issue is bound to. Any number. |
|
|
| `comp/*` | no | System component of this repo. Any number; no preset — project-specific. |
|
|
|
|
### `type/*` — mandatory, exactly one
|
|
|
|
| Label | Meaning |
|
|
|---|---|
|
|
| `type/bug` | Something behaves incorrectly in existing code |
|
|
| `type/task` | Implementation of new functionality |
|
|
| `type/refactor` | Internal restructuring: file moves, architecture; behavior must not change |
|
|
| `type/test` | Writing or fixing tests |
|
|
| `type/feature` | Container: several issues delivering one unit of business value |
|
|
| `type/draft` | Idea captured for later; not ready for work |
|
|
|
|
### `severity/*` — at most one
|
|
|
|
`severity/low`, `severity/medium`, `severity/high`, `severity/showstopper`,
|
|
`severity/critical`.
|
|
|
|
### `tech/*` — any number
|
|
|
|
Technology-bound labels, e.g. `tech/sql` (pgx, sqlc, sql-migrate — persistent
|
|
storage), `tech/obs` (grafana, loki, prometheus, alloy — observability),
|
|
`tech/postgres`.
|
|
|
|
### `comp/*` — any number
|
|
|
|
Components of this repo's system, e.g. `comp/appclick`. No preset list —
|
|
derive from the project.
|
|
|
|
> Label **colors** are not part of the format: a hex code is how a tracker
|
|
> paints a chip, not what an issue is. They live in `skills/sync/scripts/map.py`
|
|
> and are applied on push.
|
|
|
|
## Dependencies
|
|
|
|
`depends:` in the metadata block is the graph, and it holds **ids**:
|
|
|
|
```markdown
|
|
depends: [migrate-schema, add-pool-cfg]
|
|
```
|
|
|
|
An optional `## Depends on` section, placed right after `## Spec`, carries the
|
|
human explanation — one reference per line, with a reason where it helps:
|
|
|
|
```markdown
|
|
## Depends on
|
|
- migrate-schema — нужна схема БД из этого issue
|
|
- add-pool-cfg
|
|
```
|
|
|
|
The section is prose and is passed to and from a tracker unchanged; only
|
|
`depends:` is walked when the graph is computed. Keeping them consistent is on
|
|
you — `issue_check.py` warns when the section names an id that `depends:` does
|
|
not list. Omit the section when there are no dependencies; never write an empty
|
|
one.
|
|
|
|
Draw the graph with `issue_tree.py`. The reverse direction is a grep:
|
|
|
|
```bash
|
|
grep -ln 'depends:.*migrate-schema' tmp/issues/*.md
|
|
```
|
|
|
|
## Shared rules
|
|
|
|
- `## Summary` is always the first section; `## Acceptance criteria` is always
|
|
present (exception: `type/draft`). These two are the anchors every reader
|
|
(human or LLM) relies on.
|
|
- `## Spec` is **mandatory in every type**. Its value is a repo path
|
|
(`docs/specs/auth.md`), a URL, or the literal `none` when no spec exists.
|
|
Never omit the section and never invent a link — `none` is an explicit,
|
|
valid answer.
|
|
- Acceptance criteria are `- [ ]` checkboxes; each item is an objectively
|
|
checkable condition, not an aspiration.
|
|
- Code references use the `path/file.ext:line` form; related issues by id.
|
|
- Screenshots are allowed but their content must be duplicated as text — an
|
|
LLM reading these files cannot see images.
|
|
- If acceptance criteria grow past ~5 unrelated items, split the issue (or
|
|
promote it to a `type/feature` container with child issues).
|
|
|
|
## Template: `type/bug`
|
|
|
|
```markdown
|
|
## Summary
|
|
Что сломано и где проявляется, одно-два предложения.
|
|
|
|
## Spec
|
|
`docs/specs/auth.md`, URL — или `none`.
|
|
|
|
## Steps to reproduce
|
|
1. …
|
|
2. …
|
|
|
|
## Expected
|
|
Что должно было произойти.
|
|
|
|
## Actual
|
|
Что происходит на самом деле: вывод команды, лог.
|
|
|
|
## Environment
|
|
Только релевантное: версии, ОС, конфигурация.
|
|
|
|
## Acceptance criteria
|
|
- [ ] баг не воспроизводится по шагам выше
|
|
- [ ] добавлена проверка на регрессию (если применимо)
|
|
```
|
|
|
|
## Template: `type/task`
|
|
|
|
```markdown
|
|
## Summary
|
|
Что нужно сделать, одно-два предложения.
|
|
|
|
## Spec
|
|
Ссылка или `none`.
|
|
|
|
## Motivation
|
|
Какую проблему пользователя/системы это решает.
|
|
|
|
## Acceptance criteria
|
|
- [ ] проверяемое условие
|
|
- [ ] …
|
|
|
|
## Constraints
|
|
Что НЕ входит в объём; технические рамки. (опционально)
|
|
```
|
|
|
|
## Template: `type/refactor`
|
|
|
|
```markdown
|
|
## Summary
|
|
Что перестраиваем и в каких файлах (`path/file:line`).
|
|
|
|
## Spec
|
|
Ссылка или `none`.
|
|
|
|
## Motivation
|
|
Чем плохо текущее состояние: дублирование, связность, читаемость.
|
|
|
|
## Invariants
|
|
Что НЕ должно измениться: поведение, публичные API, форматы данных.
|
|
|
|
## Acceptance criteria
|
|
- [ ] проверяемое условие (тесты зелёные, старый путь удалён, …)
|
|
```
|
|
|
|
## Template: `type/test`
|
|
|
|
```markdown
|
|
## Summary
|
|
Что покрываем тестами и где (`path/file:line`).
|
|
|
|
## Spec
|
|
Ссылка или `none`.
|
|
|
|
## Motivation
|
|
Зачем: регрессия после бага, пробел в покрытии, флаки-тест.
|
|
|
|
## Test cases
|
|
- сценарий → ожидаемый результат
|
|
- …
|
|
|
|
## Acceptance criteria
|
|
- [ ] перечисленные кейсы покрыты и зелёные
|
|
- [ ] тесты проходят в CI
|
|
```
|
|
|
|
## Template: `type/feature`
|
|
|
|
A container: one unit of business value delivered by several child issues.
|
|
Child issues carry their own `type/*` (`task`, `bug`, `test`, …) and link back
|
|
via their `depends:`. Keep implementation detail in the children; the feature
|
|
body stays at business level.
|
|
|
|
```markdown
|
|
## Summary
|
|
Бизнес-ценность одним-двумя предложениями.
|
|
|
|
## Spec
|
|
Ссылка или `none`.
|
|
|
|
## Motivation
|
|
Какую проблему пользователя/системы это решает.
|
|
|
|
## Issues
|
|
- [ ] wire-sqlc-appclick — краткое описание части
|
|
- [ ] …
|
|
|
|
## Acceptance criteria
|
|
- [ ] все дочерние issues закрыты
|
|
- [ ] проверяемое условие уровня фичи (например, e2e-сценарий работает)
|
|
```
|
|
|
|
## Template: `type/draft`
|
|
|
|
A parking spot for ideas that are not fleshed out yet. Minimal structure, no
|
|
acceptance criteria required. Before implementation starts, a draft MUST be
|
|
promoted: relabeled to a concrete type and rewritten into that type's template.
|
|
|
|
```markdown
|
|
## Summary
|
|
Идея одним-двумя предложениями.
|
|
|
|
## Spec
|
|
Ссылка или `none` (для драфтов обычно `none`).
|
|
|
|
## Notes
|
|
Свободные заметки: что известно, открытые вопросы, варианты.
|
|
```
|
|
|
|
## Containers beyond `type/feature`
|
|
|
|
- **Milestone** — a set of issues with an optional time bound. Locally it is
|
|
just the `milestone:` field; a tracker-side milestone must already exist for
|
|
a push to attach the issue to it.
|
|
- **Project** — a set of issues tracked by status columns (Backlog, ToDo,
|
|
InProgress, Ready, Done). Not represented in this format and not reachable
|
|
through the Gitea API — web UI only.
|