# Issue tracker: Gitea (`git.unom.io`)

Issues and specs for this repo live as **Gitea issues** on the self-hosted instance at
`git.unom.io`, in the repo **`unom/punktfunk`** (owner `unom`, repo `punktfunk`).

Confirm with `git remote -v` if in doubt — but note that a worktree of this repo has the same
remote, so `unom/punktfunk` holds regardless of which checkout you are in.

## Use the `gitea` MCP server — not `gh`, `glab`, or `tea`

This is **not** GitHub and **not** GitLab. `gh` is installed on this machine but is bound to
github.com and will not see these issues; `glab` and `tea` are not installed at all. Every issue
operation goes through the connected **`gitea` MCP server**.

Its tools are *deferred* — the names are visible but the schemas are not loaded, so calling one
straight away fails with `InputValidationError`. Load what you need first:

```
ToolSearch("select:mcp__gitea__issue_write,mcp__gitea__issue_read,mcp__gitea__list_issues,mcp__gitea__label_read")
```

## Conventions

Every call takes `owner: "unom"`, `repo: "punktfunk"`.

- **Create an issue**: `mcp__gitea__issue_write` with `method: "create"`, `title`, `body`.
- **Read an issue**: `mcp__gitea__issue_read` with `method: "get"` (details), `"get_comments"`
  (discussion), or `"get_labels"`. Read all three when triaging — Gitea returns them separately.
- **List issues**: `mcp__gitea__list_issues` with `state` (`open`/`closed`/`all`), optional
  `labels` (an array of label **names** here), `since`/`before`, `page`/`per_page`.
- **Search across repos**: `mcp__gitea__search_issues` with `query`, optional `owner`, `labels`
  (comma-separated string), `state`, `type`.
- **Comment**: `issue_write` with `method: "add_comment"`, `issue_number`, `body`.
- **Apply labels**: `issue_write` with `method: "add_labels"` / `"replace_labels"` /
  `"remove_label"` / `"clear_labels"`.
- **Close**: `issue_write` with `method: "update"`, `issue_number`, `state: "closed"` — comment
  first if you have something to say, since `update` takes no comment.

### Trap: labels are written by numeric ID, read by name

`issue_write` takes `labels` as an **array of numeric label IDs**, and `remove_label` takes a
single `label_id`. It will not accept label names. `list_issues`, by contrast, filters on label
**names**. So before applying a label, resolve the name to its ID:

```
mcp__gitea__label_read { method: "list_repo_labels", owner: "unom", repo: "punktfunk", per_page: 100 }
```

and match on `.name` to get `.id`. If the label does not come back, it does not exist yet — see
`triage-labels.md`; the repo currently has **no labels defined at all**, on the repo or the org.

## Ask before writing — this tracker is outward-facing

Reads (`issue_read`, `list_issues`, `search_issues`, `label_read`) are free; run them whenever you
need context.

**Writes are outward-facing and require the user's go-ahead each time.** Creating an issue,
commenting, applying labels, closing, and creating labels all publish to a shared instance other
people watch, and Gitea emails on activity. Draft the full text, show it to the user, and file it
only once they say to. This applies to subagents too — a subagent may not file on your behalf.

## Pull requests as a triage surface

**PRs as a request surface: no.** _(Set to `yes` if this repo should treat external PRs as feature
requests; `/triage` reads this flag.)_

If it is ever set to `yes`, the PR equivalents are `mcp__gitea__list_issues` with
`type: "pulls"`, plus `mcp__gitea__pull_request_read` and `mcp__gitea__pull_request_write`. Gitea
shares one number space across issues and PRs, so a bare `#42` may be either — resolve with
`pull_request_read` and fall back to `issue_read`.

## When a skill says "publish to the issue tracker"

Create a Gitea issue in `unom/punktfunk` — after asking (see above).

## When a skill says "fetch the relevant ticket"

`issue_read` with `method: "get"`, then `method: "get_comments"`.

## Wayfinding operations

Used by `/wayfinder`. The **map** is a single issue; **child** issues are the tickets.

- **Map**: an issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body.
- **Child ticket**: Gitea has no sub-issue relationship over this MCP surface. Add each child to a
  task list in the map body (`- [ ] #<child>`) and put `Part of #<map>` at the top of the child
  body. Label with `wayfinder:<type>` (`research`/`prototype`/`grilling`/`task`). Once claimed,
  set `assignees` to the driving dev.
- **Blocking**: Gitea has native issue dependencies in its web UI, but no MCP method reaches them.
  Use a `Blocked by: #<n>, #<n>` line at the top of the child body instead. A ticket is unblocked
  when every issue named there is closed — check with `issue_read`.
- **Frontier query**: `list_issues` with `state: "open"`, narrowed to the map's task-list children;
  drop any with an open blocker or an assignee; first in map order wins.
- **Claim**: `issue_write` with `method: "update"` and `assignees` — the session's first write, so
  ask first.
- **Resolve**: `add_comment` with the answer, then `update` to `state: "closed"`, then append a
  context pointer to the map's Decisions-so-far.
