Merge remote-tracking branch 'origin/main' into fix/worktree-login-pin

This commit is contained in:
naudachu
2026-08-10 18:24:45 +05:00
9 changed files with 962 additions and 56 deletions
+49 -7
View File
@@ -42,8 +42,8 @@ there is nothing to pin a second time. No pin anywhere → exit with a pointer t
| Script | What it does |
|---|---|
| `remote.py [--state] [--label] [--milestone] [-q TEXT]` | discovery: one line per Gitea issue to stdout, writes nothing |
| `pull.py <key…>` or `pull.py --milestone M \| --label L \| -q TEXT` | Gitea → `tmp/issues/<id>.md`, plus `<id>.comments.md` when the thread is not empty |
| `remote.py [--state] [--label] [--milestone] [-q TEXT] [--limit N]` | discovery: one line per Gitea issue to stdout, writes nothing; `--limit` caps the **listing** (default 30) |
| `pull.py <key…>` or `pull.py --milestone M \| --label L \| -q TEXT [--limit N]` | Gitea → `tmp/issues/<id>.md`, plus `<id>.comments.md` when the thread is not empty; follows dependencies by default (`--no-deps` to stop); `--limit` caps what is **stored** (default 100) |
| `push.py [id…] [--update] [--dry-run]` | local → Gitea; validates first, **deletes the local file on success** and prints where it lives now |
| `comment.py <id> --file F \| --body TEXT [--edit N]` | post or edit a comment, then refetch the thread |
| `labels.py [--dry-run] [--fix]` | bootstrap the canonical `type/*` + `severity/*` set in a repo; exact names left alone, lookalikes reported, drift fixed only with `--fix` |
@@ -93,7 +93,7 @@ python3 <skill-base-dir>/scripts/pull.py 42
python3 <skill-base-dir>/scripts/pull.py --milestone 6 # id or title
python3 <skill-base-dir>/scripts/pull.py --label type/bug --state all
python3 <skill-base-dir>/scripts/pull.py -q sqlc --limit 20
python3 <skill-base-dir>/scripts/pull.py 40 --deps # follow dependencies
python3 <skill-base-dir>/scripts/pull.py 40 --no-deps # this issue only
```
Do not loop over numbers to pull a group — pass the filter. The list endpoint
@@ -119,13 +119,55 @@ already on disk is refreshed either way — the local copy learns it was closed
instead of staying open forever. Key mode is exempt: `pull.py 1` fetches a
closed issue as always, because an address is not a bulk read.
**`--limit N` bounds the write, not the selection.** N is how many issues this
run leaves in the store — written, or left in place by `--cached`. Closed ones
that were enumerated and thrown away do not spend it, so `--limit 20` over a
milestone whose first 30 issues are closed still writes 20, as long as 20 open
ones are there to write. Pagination follows the budget rather than the other way
round:
| | |
|---|---|
| budget full | the next page is never requested |
| pages run out | fewer than N, and that is the honest answer |
| filter matches almost only closed issues | at most 4× the pages N would need if nothing were dropped, then a warning on stderr and a short answer — raising `--limit` raises that ceiling too |
| dependencies | outside the count: a blocker is followed because a stored issue named it, not because the filter selected it — so `--limit 20` can leave more than 20 files behind |
`remote.py --limit` means something else, deliberately: it caps the **listing**,
closed issues included. It writes nothing, so there is no write for a limit to
bound — enumeration is its whole job.
**Comments come with every pull** — there is no flag. An issue that has a
thread gets `tmp/issues/<id>.comments.md` beside it, in key mode and in filter
mode alike, and the issue's output line says how many. An issue with none
costs nothing: the count arrives in the list payload, so no request is made
and no file is written — and a file left over from a thread that has since
been emptied is deleted. `--cached` skips the thread along with the body, so a
skipped issue makes no request at all.
skipped issue makes one request for its links and no other.
**Dependencies come with every pull too, and this one costs.** A pull answers
with the unit of work — the issue and what blocks it — so `depends:` is filled
from Gitea's native graph and every blocker is pulled as well, recursively, down
to `--depth` (default 3). It has to come from the native graph: the body's
`## Depends on` section holds slugs, never `#N`, so there is no edge to recover
from the text. `--no-deps` turns off both halves. `--deps` is still accepted and
does nothing — it names the default.
| | requests |
|---|---|
| every issue that lands in the store | **+1** — `GET …/issues/{n}/dependencies`, fetched once and used twice (fills `depends:`, steers the walk) |
| every blocker the selection did not already carry | **+1** to fetch it, then its own links, until `--depth` |
| a closed issue filter mode drops | 0 — nothing was stored, so there is no unit of work to complete |
| `--milestone X` over 50 open issues | 1 list request + 50, plus a pair per outside blocker — it used to be 1 |
| the same with `--no-deps` | 1 |
**A blocker the filter did not select still lands in the store, deliberately.**
`--milestone X` can leave an issue from milestone Y on disk; `--label` can leave
an unlabelled one. It is there because a stored issue names it, not because it
matched. The exception is a closed blocker: closed is not a unit of work, filter
mode drops it like any other closed issue, and the `depends:` edge to it goes
with it — nothing is left pointing at a file that is not there. Key mode
(`pull.py 42`) has no such rule and stores it.
Two traps this handles for you:
@@ -238,7 +280,7 @@ accumulate them however many round trips it makes, and why a body that somehow
gained two is cleaned on the next pull.
`depends:` survives the same round trip through Gitea's native links (below):
push writes them, `pull.py --deps` reads them back, and the ledger turns the
push writes them, every `pull.py` reads them back, and the ledger turns the
numbers into the slugs they had here.
Before anything is sent, `/tea:issue`'s validator runs (exactly one `type/*`,
@@ -258,7 +300,7 @@ The two directions are symmetric, and they use the same endpoint:
| | direction | endpoint |
|---|---|---|
| `push.py` | `depends:` → native links | `POST …/issues/{n}/dependencies` |
| `pull.py --deps` | native links → `depends:` | `GET …/issues/{n}/dependencies` |
| `pull.py` (default; `--no-deps` off) | native links → `depends:` | `GET …/issues/{n}/dependencies` |
The POST body is Gitea's `IssueMeta``{"index", "owner", "repo"}` naming the
**blocker**, posted to the **blocked** issue's endpoint ("make the issue in the
@@ -334,7 +376,7 @@ is the one thing `branch:` exists to record.
| `labels` | `labels[]` | names both ways; ids only on write |
| `assignees` | `assignees[]` | logins |
| `milestone` | `milestone.title` | resolved to an id on write |
| `depends` | native links | slugs here, `IssueMeta` there; push writes them, `pull --deps` reads them |
| `depends` | native links | slugs here, `IssueMeta` there; push writes them, every pull reads them (`--no-deps` opts out) |
| — | `ref` | lands in `branch:`; sent only when non-empty |
| — | `number`, `html_url` | lands in `gitea:` / `url:` |