feat: local issue cache and draft-then-push workflow

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>
This commit is contained in:
naudachu
2026-08-07 19:35:38 +05:00
parent 3549f37ebf
commit 335b0bbd54
12 changed files with 1190 additions and 249 deletions
+104 -22
View File
@@ -46,38 +46,116 @@ The pin takes effect immediately — no restart. Only `tea logins list` and
per-project by the operator (see `/tea:auth`) and injected by the guard.
Config lives in `$XDG_CONFIG_HOME/tea`.
## Reading an issue: use the fetch script, not raw tea calls
## Issues: work on local files, not on live `tea` calls
To read an existing issue (its body, its discussion), do NOT run
`tea issues <n> -o json` or `tea api .../issues/<n>` directly — the full JSON
payload (avatars, nested user objects, every comment body) lands in your
context whether you need it or not. Instead run the bundled script; the only
input it needs is the issue key:
Never run `tea issues <n> -o json` or `tea api .../issues/<n>` to read an
issue — the full JSON payload (avatars, nested user objects, every comment
body) lands in your context whether you need it or not. Use the scripts in
`<skill-base-dir>/scripts/`. They pull issues into a flat, greppable cache
under `$PWD/tmp/issues/` and print only a compact index.
This is a **cache, not a mirror**: nothing tracks drift, nothing syncs back.
Refetch when you need current data; the `fetched:` field tells you the age.
| Script | Network | What it does |
|---|---|---|
| `issue_get.py <key…>` or `issue_get.py --milestone M \| --label L \| -q TEXT` | yes | fetch issue(s) → `tmp/issues/<n>.md` (+ `<n>.comments.md`, `tree-<slug>.md`) |
| `issue_list.py [--state] [--label] [--milestone] [-q TEXT]` | yes | discovery: one line per issue to stdout, writes nothing |
| `issue_push.py <draft…>\|--all [--keep] [--dry-run]` | yes | validate a draft, create labels, POST the issue, delete the draft |
| `issue_index.py` | no | rebuild `tmp/issues/INDEX.md` (auto after get/push) |
```
tmp/issues/INDEX.md table of everything cached — read this first
tmp/issues/42.md metadata block + `# Title` + body
tmp/issues/42.comments.md comments (only with --comments)
tmp/issues/tree-40.md dependency map (only with --deps)
tmp/issues/drafts/<slug>.md issues not yet created in Gitea
```
Key forms for `<key>`: `42`, `#42`, `owner/repo#42`, or a full issue URL. Repo
defaults to the current directory's git remote (add `--repo owner/repo`
outside one). No `--login` on any script call: they resolve the operator's pin
from `.claude/settings.local.json` themselves — same source as the tea-guard
hook. No pin → exit with a pointer to `/tea:auth`.
### Fetching a whole set: milestone, label, search
Do not loop `issue_get.py` over numbers to pull a group — pass the filter. The
list endpoint carries the issue bodies, so a milestone costs **one request per
50 issues**, not one per issue:
```bash
python3 <skill-base-dir>/scripts/fetch_issue.py 42
python3 <skill-base-dir>/scripts/issue_get.py --milestone 6 # id or title
python3 <skill-base-dir>/scripts/issue_get.py --milestone v0.2 --deps
python3 <skill-base-dir>/scripts/issue_get.py --label type/bug --label comp/hooks --state all
python3 <skill-base-dir>/scripts/issue_get.py -q sqlc --limit 20
```
Key forms: `42`, `#42`, `owner/repo#42`, or a full issue URL. Repo defaults
to the current directory's git remote (add `--repo owner/repo` outside one).
Filters AND together; `--state` defaults to `open`; `--limit` defaults to 100.
Keys and filters are mutually exclusive. `--comments` stays single-issue —
loop over the numbers when a whole thread set is needed.
It writes trimmed markdown files locally and prints only a compact index:
Two traps this handles for you:
```
tmp/issue/42/data issue: metadata header + body
tmp/issue/42/comments/ one file per comment: NNN-<comment-id>.md
- **Gitea silently ignores an unresolvable milestone filter** and returns the
whole backlog. The script resolves the milestone first (exits listing the
real ones if it does not exist) and re-checks every returned issue locally.
Never trust a raw `tea api ...issues?milestones=X` call for this.
- **Projects are not fetchable.** The projects API is not exposed (404 on
Gitea 1.26 for `repos/…/projects`, `orgs/…/projects`, `projects/{id}`).
Use milestones or labels; project columns live in the web UI only.
After a filtered fetch, `INDEX.md` carries a `milestone` column, and the cache
is greppable by it: `grep -l 'milestone: v0.2' tmp/issues/*.md`.
### Working a feature as one document
`--deps` walks the dependency graph **downwards** — the structured
`## Depends on` and `## Issues` sections plus Gitea's native dependencies.
Prose `#N` mentions are ignored on purpose, or the walk would drag in half the
backlog. Comments are not fetched during a walk (loop over the numbers if you
need them).
```bash
python3 <skill-base-dir>/scripts/issue_get.py 40 --deps # feature + children
```
Then Read just the files the task needs — often the `data` file alone, or a
single comment picked from the index (author + date per line). Each comment
file carries its `comment-id`, ready for a `PATCH` via `tea api`.
Read `tree-40.md` once for the shape (a filtered fetch writes one forest,
`tree-<slug>.md`), then grep the files as one document:
Notes:
- No `--login` on the script call: the script resolves the operator's pinned
login itself from `.claude/settings.local.json` — same source as the
tea-guard hook. No pin → it exits with a pointer to `/tea:auth`.
- Every run refetches fresh and wipes the issue's `comments/` dir, so stale
files never survive.
```bash
grep -ln 'depends:.*#42' tmp/issues/*.md # who depends on #42 (upwards)
grep -l 'labels:.*type/bug' tmp/issues/*.md # all cached bugs
grep -A3 '## Acceptance criteria' tmp/issues/4*.md
grep -c '^- \[ \]' tmp/issues/42.md # open checkboxes
```
Metadata is written one field per line with inline lists (`labels: [a, b]`)
precisely so plain grep works without a parser.
### Creating issues: draft locally, push once
During planning write drafts to `tmp/issues/drafts/<slug>.md` — no network, no
`tea` call. A draft is the metadata block with `labels:` only, plus the
canonical body:
```markdown
---
labels: [type/task, tech/sql]
---
# Wire sqlc into the appclick repo layer
## Summary
...
```
When the plan is agreed, `issue_push.py` validates the format (exactly one
`type/*`, English title without a type prefix, `## Summary` / `## Spec` /
`## Acceptance criteria` present), creates missing labels with the right
colors and exclusivity, POSTs, prints the URL and **deletes the draft** — the
issue lives in Gitea now. `--keep` writes `tmp/issues/<n>.md` instead;
`--dry-run` validates without touching the network. Guided procedure:
`/tea:issue`.
## Index
@@ -104,6 +182,10 @@ kills the process (e.g. exit 144 = 128 + SIGURG on macOS).
fences / backticks / pipes / tables), bypass entity commands. Save the full
request payload to `$PWD/tmp/` first, then POST via `tea api`.
Issue creation is already wrapped: use `issue_push.py` (above) instead of
hand-rolling the JSON. The procedure below covers everything else — comments,
pulls, releases, and PATCHes to existing issues.
### Procedure
1. Ensure the target dir exists: `mkdir -p tmp/{kind}` where `{kind}` is