Files
marketplace/skills/issue/SKILL.md
T
naudachu 9234d8004f feat: work the sync backlog — comments, labels, refs, closed issues
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>
2026-08-10 00:37:57 +05:00

7.7 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.

Writing a proper description

Issues get filed on the run — "comments aren't pulled", "the guard broke". That is a request, not a statement of work: no reproduction steps, no path/file:line, acceptance criteria nobody can check. Rewriting one into the canonical format is a procedure, not improvisation.

  1. Read the issue whole, and everything it points at — the ids in depends:, the ## Spec target, the files it names.

  2. Determine the type and its template. The type/* label selects one of the templates in references/format.md, and that template's section list is the shape you are aiming at. If the label is missing or wrong, decide it now and fix labels:; promoting a type/draft to a concrete type is this same step.

  3. Locate the anchor points in the code. Grep the repo for every file, symbol, command, and error string the issue mentions, until you can name lines:

    grep -rn 'GITEA_LOGIN' hooks/ skills/
    

    Work that does not exist yet still has anchor points — the files the change will land in, and the ones that will call it.

  4. Gather the missing context. What has to be there when you are done:

    • code references in the path/file.ext:line form, for every place the change lands;
    • reproduction steps — exact commands and their real output (type/bug splits them across ## Steps to reproduce / ## Expected / ## Actual);
    • acceptance criteria that are objectively checkable: a command that exits 0, a file that exists, a section that is present — not aspirations;
    • a real value for ## Spec — a repo path, a URL, or the literal none.

    A missing fact is either found in the repository or becomes a question to the user. Inventing one is forbidden. Ask in one batch, and keep none in ## Spec as the legitimate answer it is — never a plausible-looking link.

  5. Rewrite the sections with Edit: every section of the template, in the template's order, English headers and Russian prose. Replace the body; do not append a second telling of the same issue below the old one.

  6. Check it:

    python3 <skill-base-dir>/scripts/issue_check.py wire-sqlc-appclick
    

    Errors mean malformed, warnings mean the type's template is not fully filled in. Re-run issue_index.py if the labels changed.

The procedure is identical for origin: local and origin: gitea — it works on tmp/issues/<id>.md, and this layer does not know the difference. Getting the rewritten body into the tracker is a separate decision — push.py --update in /tea:sync — and is no part of this.

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.