Work has been starting from a chat message and a plan file, so two people could pick up the same thing and neither could see the other. This makes the tracker the source of truth, and moves everything unimplemented out of .planning/ and into it.
What is in it
Commit
chore(scripts): reach the issue tracker from the command line
scripts/issue.sh + issue_fmt.py
docs: make the issue tracker the source of truth
CLAUDE.md, .planning/ reorganisation
Both types release nothing, so this cuts no version.
The workflow
Search before starting; claim before the first edit, not before the commit — the point is that the work is visibly taken while it is being done. A claim is an assignee, Status/In Progress and a comment naming the branch, all three. If no issue covers the work, open one first. Findings get filed, including decisions not to do something.
The conventions were already right and are written down rather than reinvented: the Kind/Area/Priority/Platform/Reviewed/Status taxonomy and its exclusive scopes, #73 as the roadmap, real Gitea dependencies for hard blockers, and this repo's own PR #83 as the body shape.
Unchanged: the commit grammar, which is load-bearing for semantic-release. The issue number lives in the branch name and here, not in the commit subject.
.planning/ stops being a queue
It keeps design documents and measured history — NOTES.md, the audits, the completed plans and the arguments in them. plans/pending/ is gone, because a plan nobody is executing is an issue. Five plans moved to completed/, each stamped with which issue carries its remainder; autotag.md is kept as a historical record, marked stale where the scoring overhaul overtook it.
Filed from it, all with the argument preserved rather than a one-line summary:
Issue
From
#85 warm the release cache for owned, not-known-complete albums
plan 010 (deleted)
#86 six bound-but-uncalled backend methods — wire or remove
plan 012, finding 3
#87 Android: tag writing has never been run on a device
plan 016, B3
#88 run the credit ingest against the real dump and publish it
plan 015 multi-artist
#89 materialise credits into file_artists — depends on #88
plan 015 multi-artist, phase 3
#90 autotag strict auto-accept as a background job
#83's Closes list named ten and exactly half of them fired. #13, #19, #26, #29 and #37 were in main for a fortnight and still open; each is now closed with a comment naming the commit that shipped it. That is why the workflow says closing is a step you take and verify, not a keyword you trust.
The wrapper, and what it pins
list mine search show new claim unclaim comment close label depends labels. Bodies cross to the API as JSON and never as shell, which is why the formatting half is its own Python file — same reasoning that keeps release notes out of gitea-release.sh's argument list.
Four faults found by self-testing it against the live tracker, all fixed and each pinned with its reason:
claim could half-claim — the assignee and label landed, then the comment was rejected as empty, leaving the issue saying it was taken without saying by what work. The comment is resolved before anything mutates.
label accepted names that do not exist. Gitea answers 200 and applies none of them, so a typo reported success and did nothing. Names resolve to ids first.
depends needs a whole IssueMeta, not an index. A body of {"index": 88} answers 404, which reads exactly like a Gitea build without the feature.
close drops Status/In Progress, or a claim outlives the work.
Verified
make skill-check — 47 documented make targets, all present (no new target added)
Work has been starting from a chat message and a plan file, so two people could pick up the same thing and neither could see the other. This makes the tracker the source of truth, and moves everything unimplemented out of `.planning/` and into it.
**What is in it**
| Commit | |
|---|---|
| `chore(scripts): reach the issue tracker from the command line` | `scripts/issue.sh` + `issue_fmt.py` |
| `docs: make the issue tracker the source of truth` | CLAUDE.md, `.planning/` reorganisation |
Both types release nothing, so this cuts no version.
**The workflow**
Search before starting; claim before the first edit, not before the commit — the point is that the work is visibly taken *while it is being done*. A claim is an assignee, `Status/In Progress` and a comment naming the branch, all three. If no issue covers the work, open one first. Findings get filed, including decisions not to do something.
The conventions were already right and are written down rather than reinvented: the `Kind/Area/Priority/Platform/Reviewed/Status` taxonomy and its exclusive scopes, #73 as the roadmap, real Gitea dependencies for hard blockers, and this repo's own PR #83 as the body shape.
Unchanged: the commit grammar, which is load-bearing for semantic-release. The issue number lives in the branch name and here, not in the commit subject.
**`.planning/` stops being a queue**
It keeps design documents and measured history — `NOTES.md`, the audits, the completed plans and the arguments in them. `plans/pending/` is gone, because a plan nobody is executing is an issue. Five plans moved to `completed/`, each stamped with which issue carries its remainder; `autotag.md` is kept as a historical record, marked stale where the scoring overhaul overtook it.
**Filed from it, all with the argument preserved rather than a one-line summary:**
| Issue | From |
|---|---|
| #85 warm the release cache for owned, not-known-complete albums | plan 010 (deleted) |
| #86 six bound-but-uncalled backend methods — wire or remove | plan 012, finding 3 |
| #87 Android: tag writing has never been run on a device | plan 016, B3 |
| #88 run the credit ingest against the real dump and publish it | plan 015 multi-artist |
| #89 materialise credits into `file_artists` — depends on #88 | plan 015 multi-artist, phase 3 |
| #90 autotag strict auto-accept as a background job | autotag 011 |
| #91 autotag settings panel — depends on #90 | autotag 012 |
**Five issues were closed that had already shipped**
#83's `Closes` list named ten and exactly half of them fired. #13, #19, #26, #29 and #37 were in `main` for a fortnight and still open; each is now closed with a comment naming the commit that shipped it. That is why the workflow says closing is a step you take and verify, not a keyword you trust.
**The wrapper, and what it pins**
`list mine search show new claim unclaim comment close label depends labels`. Bodies cross to the API as JSON and never as shell, which is why the formatting half is its own Python file — same reasoning that keeps release notes out of `gitea-release.sh`'s argument list.
Four faults found by self-testing it against the live tracker, all fixed and each pinned with its reason:
- **`claim` could half-claim** — the assignee and label landed, then the comment was rejected as empty, leaving the issue saying it was taken without saying by what work. The comment is resolved before anything mutates.
- **`label` accepted names that do not exist.** Gitea answers 200 and applies none of them, so a typo reported success and did nothing. Names resolve to ids first.
- **`depends` needs a whole `IssueMeta`, not an index.** A body of `{"index": 88}` answers **404**, which reads exactly like a Gitea build without the feature.
- **`close` drops `Status/In Progress`**, or a claim outlives the work.
**Verified**
- `make skill-check` — 47 documented make targets, all present (no new target added)
- `bash -n scripts/issue.sh`, `python3 -m py_compile scripts/issue_fmt.py`
- Every subcommand exercised against the live tracker, destructive ones on a scratch issue that was then deleted
- `commit-check` on both commits via the `commit-msg` hook
No Go, TypeScript or SQL changed, so the app-facing suites are untouched by this branch — CI will confirm.
**Closes** #92
yonlu
self-assigned this 2026-08-18 20:24:41 +00:00
Issues become this project's source of truth for what is wanted and what
is already being worked on, which puts "search the tracker" at the top of
every task rather than occasionally. Fifty-odd open issues make that a
real lookup, and a lookup nobody can remember the shape of is a lookup
that gets skipped -- the same way the CI log endpoint cost two sessions
to a tool that 404s.
Text reaches the API as JSON and never as shell, which is why the
formatting half is its own Python file: an issue body is arbitrary prose
carrying backticks, quotes and $, and every attempt to build that JSON
inside the shell ends in nested quoting nobody can verify. Same reasoning
that keeps release notes out of gitea-release.sh's argument list.
Claiming is an assignment, a label and a comment together, because any
one alone is a claim somebody has to go looking for. It resolves the
comment before it mutates anything -- reading it afterwards is how a
claim ends up half-made, with the issue saying it is taken without saying
by what work -- and refuses outright if somebody else holds it.
Three API shapes are pinned here because each fails quietly:
- Labels are resolved to ids rather than posted as names. Gitea accepts
a list of unknown names with 200 and applies none of them, so a typo
reports success and does nothing.
- The dependency endpoint takes a whole IssueMeta, not an index. A body
of {"index": 88} answers 404, which reads exactly like a Gitea build
without the feature.
- close drops Status/In Progress, or a claim outlives the work.
Refs #92
Work has been starting from a chat message and a plan file, so two
people could pick up the same thing and neither could see the other.
The tracker is where that is visible.
Search before starting, claim before the first edit -- not before the
commit, since the point is that the other person can see the work is
taken while it is being done. If no issue covers it, open one first:
that is what makes the tracker a description of the project rather than
a description of the past.
The conventions were already right and are written down rather than
reinvented -- the Kind/Area/Priority/Platform/Reviewed/Status taxonomy,
its exclusive scopes, #73 as the roadmap, real Gitea dependencies for
hard blockers, and PR #83's body shape.
What #83 also demonstrated is that a Closes list closes nothing
reliably: it listed ten and five of them sat open in main for a
fortnight. So closing is a step you take and verify, not a keyword you
trust.
.planning/ stops being a queue and keeps design documents and measured
history -- NOTES.md, the audits, the completed plans and the arguments
in them. plans/pending/ is gone, because a plan nobody is executing is
an issue; everything unimplemented in it is now #85-#91, and each
completed plan says which issue carries its remainder. autotag.md is
kept as a historical record, marked stale where the scoring overhaul
overtook it.
The commit grammar is unchanged and is load-bearing for a different
reason, so the issue number lives in the branch name and the PR body
rather than the commit subject.
Refs #92
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Work has been starting from a chat message and a plan file, so two people could pick up the same thing and neither could see the other. This makes the tracker the source of truth, and moves everything unimplemented out of
.planning/and into it.What is in it
chore(scripts): reach the issue tracker from the command linescripts/issue.sh+issue_fmt.pydocs: make the issue tracker the source of truth.planning/reorganisationBoth types release nothing, so this cuts no version.
The workflow
Search before starting; claim before the first edit, not before the commit — the point is that the work is visibly taken while it is being done. A claim is an assignee,
Status/In Progressand a comment naming the branch, all three. If no issue covers the work, open one first. Findings get filed, including decisions not to do something.The conventions were already right and are written down rather than reinvented: the
Kind/Area/Priority/Platform/Reviewed/Statustaxonomy and its exclusive scopes, #73 as the roadmap, real Gitea dependencies for hard blockers, and this repo's own PR #83 as the body shape.Unchanged: the commit grammar, which is load-bearing for semantic-release. The issue number lives in the branch name and here, not in the commit subject.
.planning/stops being a queueIt keeps design documents and measured history —
NOTES.md, the audits, the completed plans and the arguments in them.plans/pending/is gone, because a plan nobody is executing is an issue. Five plans moved tocompleted/, each stamped with which issue carries its remainder;autotag.mdis kept as a historical record, marked stale where the scoring overhaul overtook it.Filed from it, all with the argument preserved rather than a one-line summary:
file_artists— depends on #88Five issues were closed that had already shipped
#83's
Closeslist named ten and exactly half of them fired. #13, #19, #26, #29 and #37 were inmainfor a fortnight and still open; each is now closed with a comment naming the commit that shipped it. That is why the workflow says closing is a step you take and verify, not a keyword you trust.The wrapper, and what it pins
list mine search show new claim unclaim comment close label depends labels. Bodies cross to the API as JSON and never as shell, which is why the formatting half is its own Python file — same reasoning that keeps release notes out ofgitea-release.sh's argument list.Four faults found by self-testing it against the live tracker, all fixed and each pinned with its reason:
claimcould half-claim — the assignee and label landed, then the comment was rejected as empty, leaving the issue saying it was taken without saying by what work. The comment is resolved before anything mutates.labelaccepted names that do not exist. Gitea answers 200 and applies none of them, so a typo reported success and did nothing. Names resolve to ids first.dependsneeds a wholeIssueMeta, not an index. A body of{"index": 88}answers 404, which reads exactly like a Gitea build without the feature.closedropsStatus/In Progress, or a claim outlives the work.Verified
make skill-check— 47 documented make targets, all present (no new target added)bash -n scripts/issue.sh,python3 -m py_compile scripts/issue_fmt.pycommit-checkon both commits via thecommit-msghookNo Go, TypeScript or SQL changed, so the app-facing suites are untouched by this branch — CI will confirm.
Closes #92
Issues become this project's source of truth for what is wanted and what is already being worked on, which puts "search the tracker" at the top of every task rather than occasionally. Fifty-odd open issues make that a real lookup, and a lookup nobody can remember the shape of is a lookup that gets skipped -- the same way the CI log endpoint cost two sessions to a tool that 404s. Text reaches the API as JSON and never as shell, which is why the formatting half is its own Python file: an issue body is arbitrary prose carrying backticks, quotes and $, and every attempt to build that JSON inside the shell ends in nested quoting nobody can verify. Same reasoning that keeps release notes out of gitea-release.sh's argument list. Claiming is an assignment, a label and a comment together, because any one alone is a claim somebody has to go looking for. It resolves the comment before it mutates anything -- reading it afterwards is how a claim ends up half-made, with the issue saying it is taken without saying by what work -- and refuses outright if somebody else holds it. Three API shapes are pinned here because each fails quietly: - Labels are resolved to ids rather than posted as names. Gitea accepts a list of unknown names with 200 and applies none of them, so a typo reports success and does nothing. - The dependency endpoint takes a whole IssueMeta, not an index. A body of {"index": 88} answers 404, which reads exactly like a Gitea build without the feature. - close drops Status/In Progress, or a claim outlives the work. Refs #92