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>
Replace fetch_issue.py with four scripts around a flat, greppable cache in
tmp/issues/. Planning stays offline and issues reach Gitea in one push:
- issue_get.py: fetch by key or by filter (--milestone/--label/-q). The list
endpoint carries issue bodies, so a whole milestone costs one request per 50
issues. Gitea silently ignores an unresolvable milestones= filter and returns
the entire backlog, so the milestone is resolved up front and every returned
issue is re-checked locally. --deps walks the dependency graph downwards via
the structured sections plus native dependencies and writes tree-<slug>.md.
- issue_push.py: validate a local draft against the canonical format, create
missing labels with the right colors and exclusivity, POST, delete the draft.
- issue_list.py: discovery to stdout, writes nothing.
- issue_index.py: rebuild INDEX.md from what is on disk.
Files use one metadata field per line with inline lists so plain grep works
without a parser. This is a cache and a drafting area, not a mirror: no drift
tracking, no sync back.
Projects are not fetchable — the projects API is 404 on Gitea 1.26; documented
alongside the milestone caveat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Match the Issues-Workflow wiki (claude-skills/tea#1): types expand to
bug|task|refactor|test|feature|draft (feature becomes a container,
task takes over new functionality), add severity/tech/comp label
namespaces, an optional "Depends on" section, and templates for
test and feature. Milestone/Project containers documented.
Refs claude-skills/tea#1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>