refactor: split issue domain from Gitea transport

An issue was a Gitea row that happened to be cached locally: its identity
was the tracker's number (42.md), its dependencies were tracker numbers
(depends: [#12]), and a local issue existed only as a draft that push
deleted on success. Nothing could be planned or tracked without a tracker.

Split into layers, with knowledge flowing one way:

  skills/issue  DOMAIN  what an issue is: format, validation, dep graph
        ^               offline; stdlib imports only, no subprocess
        | imports
  skills/sync   BRIDGE  map.py    md <-> Gitea JSON, pure, no I/O
                        _gitea.py login pin, api, pagination, filters
  skills/use    REFERENCE  tea CLI docs for non-issue entities

skills/issue never imports skills/sync. Delete the sync layer and the
domain keeps working.

Identity is now a slug derived from the title (wire-sqlc-appclick.md) and
is stable across retitles and pushes. Tracker numbers live in a `gitea:`
field, never in a file name and never in `depends:`; the pair is indexed
in .remote.json, which is a cache over the files, not a second source of
truth.

Behavior changes:

- Pushing is additive. The file is never deleted; it gains gitea:/url:/
  synced: and origin: flips from local to gitea. `origin: local` is a
  durable state, not a pending one.
- Pushes go in topological order so dependencies get numbers first.
- The dependency graph is computed offline from `depends:` metadata; body
  prose is passed through unchanged in both directions rather than being
  rewritten between slugs and #N.
- `origin` is domain-owned (whether work exists elsewhere is a fact about
  the work); the handle and how to reach it stay with sync.

Script moves:

  issue_get.py   -> sync/pull.py
  issue_push.py  -> sync/push.py
  issue_list.py  -> sync/remote.py
  issue_index.py -> issue/issue_index.py
  _tea.py        -> split into issue/issue.py, sync/map.py, sync/_gitea.py

New: issue/issue_new.py, issue/issue_check.py, issue/issue_tree.py, and
sync/comment.py — comment posting was the last issue operation still
hand-rolled through raw `tea api`.

references/issue-format.md moves to skills/issue/references/format.md;
label hex colors move out of it into map.py, since a color is how a
tracker paints a chip, not what an issue is.

Verified: offline path end to end (new, check, tree, index, push
--dry-run) and read-only against Gitea (remote listing, pull with
mapping, comment guard). Write paths of push.py and comment.py are not
exercised here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
naudachu
2026-08-09 23:37:32 +05:00
parent 335b0bbd54
commit 091dceec1d
24 changed files with 2504 additions and 1284 deletions
-245
View File
@@ -1,245 +0,0 @@
# Issue format
Canonical format for every issue created or edited via `tea`. 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)).
## 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 (Gitea enforces at most
one label from the scope), 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 | Color | Meaning |
|---|---|---|
| `type/bug` | `#ee0701` | Something behaves incorrectly in existing code |
| `type/task` | `#0e8a16` | Implementation of new functionality |
| `type/refactor` | `#1d76db` | Internal restructuring: file moves, architecture; behavior must not change |
| `type/test` | `#fbca04` | Writing or fixing tests |
| `type/feature` | `#5319e7` | Container: several issues delivering one unit of business value |
| `type/draft` | `#cccccc` | Idea captured for later; not ready for work |
### `severity/*` — at most one
| Label | Color |
|---|---|
| `severity/low` | `#c2e0c6` |
| `severity/medium` | `#fbca04` |
| `severity/high` | `#eb6420` |
| `severity/showstopper` | `#ee0701` |
| `severity/critical` | `#b60205` |
### `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.
### Creating exclusive labels
Gitea enforces exclusivity only if the label was created with
`exclusive: true`. The `tea labels create` command (as of tea 0.14.2) cannot
set that field, so missing `type/*` and `severity/*` labels MUST be created
via `tea api`:
```bash
tea api --login "$GITEA_LOGIN" -X POST \
-d '{"name":"type/bug","color":"#ee0701","exclusive":true,"description":"Something behaves incorrectly in existing code"}' \
repos/{owner}/{repo}/labels
```
`tech/*` and `comp/*` are non-exclusive; either `tea labels create` or
`tea api` works for them.
## Dependencies
An issue may explicitly depend on others. Declare that in an optional
`## Depends on` section placed right after `## Spec`, one `#N` reference per
line:
```markdown
## Depends on
- #12 — нужна схема БД из этого issue
- #15
```
Omit the section when there are no dependencies — never write an empty one.
## 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 as `#N`.
- Screenshots are allowed but their content must be duplicated as text — an
LLM posting through `tea api` cannot read 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 `## Depends on` or the `## Issues` list here. Keep implementation
detail in the children; the feature body stays at business level.
```markdown
## Summary
Бизнес-ценность одним-двумя предложениями.
## Spec
Ссылка или `none`.
## Motivation
Какую проблему пользователя/системы это решает.
## Issues
- [ ] #N — краткое описание части
- [ ]
## 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. Manage via
`tea milestones` / `tea milestones issues`.
- **Project** — a set of issues describing one project, tracked by status
columns. Standard statuses: Backlog, ToDo, InProgress, Ready, Done. The
Gitea projects API is not exposed via `tea` subcommands — use the web UI.