Files
marketplace/skills/issue/SKILL.md
T
naudachu 091dceec1d 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>
2026-08-09 23:37:32 +05:00

5.1 KiB

name, description
name description
issue Work with this project's issues as units of work — create, read, grep, validate, and walk their dependency graph. Entirely offline; issues are local markdown files and need no tracker. Load when the user asks to file/create an issue, read or find issues, check an issue against the format, or see what depends on what. For pushing to or pulling from Gitea, load /tea:sync instead.

/tea:issue — issues as units of work

An issue is a markdown file in tmp/issues/. This skill covers everything you do with an issue: writing one, reading one, checking it against the canonical format, and walking the dependency graph.

Nothing here touches the network. No tea, no Gitea, no login. An issue that lives only on this machine is a first-class issue, not a draft waiting to be uploaded. Synchronizing with a tracker is a separate, optional layer — /tea:sync.

Read references/format.md before creating or editing an issue. It is the single source of truth for identity, metadata, types, labels, templates, and language rules.

Identity: the slug

The file name is the id and the id is a slug — tmp/issues/wire-sqlc-appclick.md. It never changes, not when the title changes and not when the issue is pushed somewhere. Tracker numbers live in a metadata field (gitea: owner/repo#42), never in a file name and never in depends:.

Consequence worth internalizing: #42 means nothing in this layer. Refer to issues by id.

Scripts

All offline, all in <skill-base-dir>/scripts/.

Script What it does
issue_new.py --type T --title "…" create tmp/issues/<slug>.md from the type's template
issue_check.py [id…] validate against the canonical format; exit 1 on errors
issue_tree.py [id…] draw the dependency graph from depends:
issue_index.py rebuild tmp/issues/INDEX.md
issue.py the domain module the others import — not a command
tmp/issues/INDEX.md                 table of every issue — read this first
tmp/issues/wire-sqlc-appclick.md    metadata block + `# Title` + body
tmp/issues/wire-sqlc.comments.md    comment thread (written by /tea:sync only)
tmp/issues/tree-<id>.md             saved graph (issue_tree.py --write)

Reading: grep, don't parse

Metadata is one field per line with inline lists precisely so plain grep works. INDEX.md first, then the files:

grep -l  'labels:.*type/bug' tmp/issues/*.md      # all bugs
grep -l  'origin: local' tmp/issues/*.md          # never pushed anywhere
grep -ln 'depends:.*migrate-schema' tmp/issues/*.md   # who depends on it
grep -A3 '## Acceptance criteria' tmp/issues/wire-*.md
grep -c  '^- \[ \]' tmp/issues/wire-sqlc-appclick.md  # open checkboxes

Read whole files only for the issues the task actually needs.

Creating an issue

  1. Read the format: references/format.md.
  2. Pick the typebug, task, refactor, test, feature (a container for several issues with one business value), or draft (an idea not ready for work). If it is not obvious from the request, ask the user (one question).
  3. Scaffold it:
    python3 <skill-base-dir>/scripts/issue_new.py \
      --type task --title "Wire sqlc into the appclick repo layer" \
      --label tech/sql --label comp/appclick --depends migrate-schema
    
    English imperative title with no type prefix; --depends takes ids.
  4. Fill the sections with Edit — every section of the template present and in order, headers English, prose Russian. ## Spec gets a repo path, a URL, or the literal none; ask the user if you cannot determine which.
  5. Check it:
    python3 <skill-base-dir>/scripts/issue_check.py wire-sqlc-appclick
    

One file = one issue. Several related issues = several files, linked through depends:.

The issue is now real and complete. Publishing it to Gitea is a separate decision — /tea:sync — and does not change the file's status here.

Editing an issue

Edit the file. Change state: to close it, edit labels:, tick checkboxes in ## Acceptance criteria, add ids to depends:. Re-run issue_check.py afterwards, and issue_index.py to refresh the table.

If the issue is synced (origin: gitea), your edit is local until you run push.py --update from /tea:sync. Nothing tracks that drift automatically.

Dependency graph

depends: is the authoritative edge list; the body's ## Depends on section is prose for humans. issue_check.py warns when they disagree.

python3 <skill-base-dir>/scripts/issue_tree.py                 # all roots
python3 <skill-base-dir>/scripts/issue_tree.py wire-sqlc-appclick --write

A type/feature plus its children read as one document: draw the tree once for the shape, then grep the files.

Layering rule

This skill must keep working with skills/sync/ deleted. Every import under scripts/ is stdlib, and subprocess is not among them:

grep -rhn '^import\|^from' skills/issue/scripts/ | sort -u

If you find yourself wanting a tracker concept here — an issue number, a login, an HTTP call — it belongs in /tea:sync.