--- name: issue description: 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`](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 `/scripts/`. | Script | What it does | |---|---| | `issue_new.py --type T --title "…"` | create `tmp/issues/.md` from the type's template | | `issue_check.py [id…]` | validate against the canonical format; exit 1 on errors | | `issue_ac.py [--check N\|TEXT]` | list the body's checkboxes; tick or untick one | | `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-.md saved graph (issue_tree.py --write) ``` ## Where the store is `/tmp/issues` — **not** `tmp/issues` relative to wherever you are standing. The scripts resolve it by walking up from their own file to the nearest `.git` or `AGENTS.md`, so they all see one store no matter which directory you run them from, and a `cd` earlier in the session changes nothing. `--out` overrides that and is taken **literally**: an absolute path is used as given, a relative one stays relative to the current directory. Nothing rewrites what you typed. Two things follow, and both are deliberate: - A store that is not there reports `does not exist`; a store with no issues in it reports `is empty`. They are different problems. - No script conjures a store as a side effect of writing. Only `issue_new.py` creates one — the first issue in a fresh checkout — and it says so on stderr. ## 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: ```bash 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`](references/format.md). 2. **Pick the type** — `bug`, `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:** ```bash python3 /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:** ```bash python3 /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:`, add ids to `depends:`. Re-run `issue_check.py` afterwards, and `issue_index.py` to refresh the table. Checkboxes are the exception — use `issue_ac.py`, below. If the issue is synced (`origin: gitea`), the file is a working copy: your edit is local until you run `push.py --update` from `/tea:sync`, and that push **deletes the file** once Gitea has it. Nothing tracks drift, and with one copy at a time there is little to track — a file that is still here has not been pushed. Get it back with `pull.py `; the slug does not change. ## Ticking checkboxes A checkbox is the one part of a body that is **state** and not prose, so it has a command of its own. Never rewrite a body just to tick a box: the rewrite re-flows lines and re-words sentences, and the issue's diff swells around a change that means one character. ```bash python3 /scripts/issue_ac.py wire-sqlc-appclick python3 /scripts/issue_ac.py wire-sqlc-appclick --check 3 python3 /scripts/issue_ac.py wire-sqlc-appclick --check "регресс" python3 /scripts/issue_ac.py wire-sqlc-appclick --uncheck 3 ``` With no flag it prints the numbered list with each item's state, grouped by the heading the item sits under. `--check` / `--uncheck` take that number or a substring of the item's text (case-insensitive). - **Every checkbox in the body counts, not just `## Acceptance criteria`.** A `type/feature` keeps its children as checkboxes under `## Issues`, and they are numbered in the same list. The script is named after the section most boxes live in, nothing more. - **A substring must match exactly one item.** Two matches is an error that lists them; pick by number instead. It never guesses. - **Exactly one character of the file changes.** Metadata, wording, wrapping and trailing whitespace all come back byte for byte, so `git diff` and the tracker's diff show the tick and nothing else. - Examples inside a ``` fence are markup, not state — they are skipped. - `INDEX.md` gains a `progress` column (`3/7`, blank when the issue has no boxes), recomputed from the body on every build and stored in no field. `issue_ac.py` rebuilds the index after a successful tick. Getting the tick to the tracker is a separate step — `push.py --update` in `/tea:sync`. ## 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`](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: ```bash 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:** ```bash python3 /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/.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. ```bash python3 /scripts/issue_tree.py # all roots python3 /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: ```bash 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`.