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:
+54
-38
@@ -1,57 +1,73 @@
|
||||
---
|
||||
name: issue
|
||||
description: Create a Gitea issue in the project's canonical format. Run when the user asks to file/create an issue, or types /tea:issue. Ensures exclusive type/* labels exist, composes the body from the type's template, and posts via tea api. Format lives in the use skill's references.
|
||||
description: Create a Gitea issue in the project's canonical format. Run when the user asks to file/create an issue, or types /tea:issue. Writes a local draft during planning, then pushes it with issue_push.py, which validates the format, ensures exclusive type/* labels exist, and posts via tea api.
|
||||
---
|
||||
|
||||
# /tea:issue — create an issue in the canonical format
|
||||
# /tea:issue — draft locally, push when agreed
|
||||
|
||||
Thin procedure on top of the canonical format defined in
|
||||
[`../use/references/issue-format.md`](../use/references/issue-format.md).
|
||||
Read that file first — it is the single source of truth for types, labels,
|
||||
templates, and language rules. Login rules are the same as everywhere:
|
||||
always `--login "$GITEA_LOGIN"`, never a literal name (see `/tea:use`).
|
||||
templates, and language rules.
|
||||
|
||||
## Steps
|
||||
Two phases, deliberately separated: planning writes **local files only** (no
|
||||
network, no `tea`), and one push turns them into real issues. Scripts live in
|
||||
`../use/scripts/` (see `/tea:use` for the full set).
|
||||
|
||||
## Phase 1 — draft (no network)
|
||||
|
||||
1. **Read the format**: load `../use/references/issue-format.md`.
|
||||
2. **Pick the type** — `bug`, `task`, `refactor`, `test`, `feature` (a
|
||||
container for several issues with one business value), or `draft` (for
|
||||
ideas not ready for work). If it is not obvious from the request, ask the
|
||||
user (one question).
|
||||
3. **Ensure labels exist**: `tea labels list --login "$GITEA_LOGIN" -o json`.
|
||||
For each missing **exclusive** label (`type/*`, and `severity/*` when
|
||||
used), create it via `tea api` with `"exclusive": true` exactly as shown
|
||||
in the format doc. Do NOT use `tea labels create` for these — it cannot
|
||||
set exclusivity. Non-exclusive `tech/*` and `comp/*` labels may be created
|
||||
either way; apply them when the technology or component is evident.
|
||||
4. **Compose title and body** per the format: English imperative title without
|
||||
a type prefix; the type's template with all sections present, in order,
|
||||
headers in English, prose in Russian; `## Spec` filled with a repo path,
|
||||
a URL, or the literal `none` — ask the user if you cannot determine which.
|
||||
If the issue depends on others, add a `## Depends on` section right after
|
||||
`## Spec` (one `#N` per line); omit it otherwise.
|
||||
5. **Post via tmp/ + tea api** (the body is always multi-line, so entity
|
||||
commands are off the table — see "Rich payloads" in `/tea:use`):
|
||||
```bash
|
||||
mkdir -p tmp/issue
|
||||
# write {"title": "...", "body": "...", "labels": [<type-label-id>]} as JSON
|
||||
tea api --login "$GITEA_LOGIN" -X POST -d @tmp/issue/<slug>.json \
|
||||
repos/{owner}/{repo}/issues
|
||||
3. **Write `tmp/issues/drafts/<slug>.md`**: a metadata block carrying
|
||||
`labels:` only, then `# Title`, then the type's template.
|
||||
|
||||
```markdown
|
||||
---
|
||||
labels: [type/task, tech/sql, comp/appclick]
|
||||
---
|
||||
# Wire sqlc into the appclick repo layer
|
||||
|
||||
## Summary
|
||||
...
|
||||
```
|
||||
The create endpoint takes label **IDs** (integers), not names — take them
|
||||
from the `tea labels list` output of step 3 (or from the create response).
|
||||
The `labels` array holds every applied label: the `type/*` ID plus any
|
||||
`severity/*`, `tech/*`, `comp/*` IDs. If labels fail to attach on create,
|
||||
fall back to `PUT repos/{owner}/{repo}/issues/{n}/labels` with
|
||||
`{"labels": [<id>]}`.
|
||||
6. **Report**: show the issue URL and the applied labels.
|
||||
|
||||
English imperative title with no type prefix; every section of the
|
||||
template present and in order; headers English, prose Russian; `## Spec`
|
||||
filled with a repo path, a URL, or the literal `none` — ask the user if you
|
||||
cannot determine which. Add `## Depends on` right after `## Spec` when the
|
||||
issue depends on others (one `#N` per line); omit it otherwise.
|
||||
One draft file = one issue. Several related issues = several drafts.
|
||||
4. **Check the format without posting** (optional, free):
|
||||
```bash
|
||||
python3 ../use/scripts/issue_push.py --all --dry-run
|
||||
```
|
||||
|
||||
## Phase 2 — push (once the plan is agreed)
|
||||
|
||||
```bash
|
||||
python3 ../use/scripts/issue_push.py --all
|
||||
```
|
||||
|
||||
The script validates the format (exactly one `type/*`, at most one
|
||||
`severity/*`, English title, `## Summary` / `## Spec` / `## Acceptance
|
||||
criteria` present), creates any missing labels — `exclusive: true` for
|
||||
`type/*` and `severity/*`, canonical colors from the format doc — POSTs each
|
||||
issue, prints `#N <url>`, and **deletes the draft**. The issue lives in Gitea
|
||||
now; the local copy is not a mirror and must not linger.
|
||||
|
||||
Flags: `--keep` writes `tmp/issues/<n>.md` instead of deleting, `--dry-run`
|
||||
validates only, `--force` posts despite format violations (say why).
|
||||
|
||||
Report the issue URLs and the applied labels to the user.
|
||||
|
||||
## Editing an existing issue
|
||||
|
||||
When asked to bring an existing issue to the format: fetch it with the use
|
||||
skill's script (`python3 ../use/scripts/fetch_issue.py <n>` relative to this
|
||||
skill's base dir — writes `tmp/issue/<n>/data` + comments, prints a compact
|
||||
index; no `--login`, it resolves the pin itself), restructure the body into
|
||||
the type's template without losing information, then
|
||||
`PATCH repos/{owner}/{repo}/issues/{n}` with the new title/body and ensure
|
||||
exactly one `type/*` label is set.
|
||||
Drafts only create. To bring an existing issue to the format: fetch it with
|
||||
`python3 ../use/scripts/issue_get.py <n>` (writes `tmp/issues/<n>.md`,
|
||||
prints a compact line), restructure the body into the type's template without
|
||||
losing information, then `PATCH repos/{owner}/{repo}/issues/{n}` via `tea api`
|
||||
with the new title/body and ensure exactly one `type/*` label is set. Login is
|
||||
always the placeholder `--login "$GITEA_LOGIN"` (see `/tea:use`).
|
||||
|
||||
Reference in New Issue
Block a user