feat: drop the local copy after a successful push
Gitea becomes the source of truth. Once a push is confirmed, push.py
deletes tmp/issues/<id>.md and <id>.comments.md and prints the number and
URL the issue now lives at; the current state is obtained by pulling
again rather than by reconciling. --update follows the same rule, with no
exception: what is local is what has not left.
This reverses three statements AGENTS.md used to make, and rewriting them
is part of the change:
- "tmp/issues/ is the store, not a cache of Gitea" — it is both, split
by origin:. An origin: local file is the only copy of the work; an
origin: gitea file is a deletable working copy.
- "Pushing is additive: the file is never deleted" — it is deleted.
- "origin: local is a durable state" — complete, but not durable:
pushing ends it.
Slug stability, which the format promises for the life of an issue, can
no longer rest on a file push is about to delete. The slug goes up in the
body as a hidden marker, <!-- tea:id <slug> -->, on the first line:
map.to_payload strips every marker and prepends exactly one, map.from_api
strips every marker on the way down, so the local file never holds one
and a body cannot accumulate them however many round trips it makes. The
marker survives a rename in the web UI, a lost .remote.json, a fresh
clone and another machine — none of which a local index does.
Deletion is the last thing that happens to an issue and only after the
transport returned, the answer carried a positive integer number (and, on
--update, the number that was PATCHed — push.confirmed_number), and
.remote.json was written. A raised transport, a non-2xx, an empty or
mismatched body each leave the file on disk and stop the run.
.remote.json is no longer "only an index over the files": its entries now
deliberately outlive them, so it is the local number -> slug ledger and
rebuild_map merges into it instead of reconstructing it from files that
may be gone. It stays recoverable, from the markers in Gitea rather than
from the files. push.dep_state reads it too, so a blocker whose file an
earlier push dropped still gets its native dependency link.
Also fixes a pre-existing bug the new tests hit: issue.all_ids treated
<id>.comments.md as an issue called "<id>.comments", so a bare push.py in
a store holding pulled threads tried to file a comment thread as a unit
of work. A slug has no dot in it.
tests/test_drop_after_push.py covers the round trip (push -> gone -> pull
-> identical in slug, depends: and body), the marker's algebra, and every
failure path separately. test_push_dependencies.py is updated where it
encoded the old "never deleted" contract. 183 tests, no network.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -92,9 +92,14 @@ radius.
|
||||
|
||||
## Local issue store
|
||||
|
||||
`tmp/issues/` (gitignored) is **the store, not a cache of Gitea**. One flat
|
||||
markdown file per issue, named by its slug, with one metadata field per line so
|
||||
plain grep works without a parser.
|
||||
`tmp/issues/` (gitignored) holds **two kinds of file, and only one of them is a
|
||||
store.** An `origin: local` issue lives here and nowhere else — this file *is*
|
||||
the issue, and losing it loses the work. Anything with `origin: gitea` is a
|
||||
**cache**: the tracker has it, this copy is a working copy, and it is deleted
|
||||
the moment a push confirms the tracker is up to date.
|
||||
|
||||
One flat markdown file per issue, named by its slug, with one metadata field per
|
||||
line so plain grep works without a parser.
|
||||
|
||||
- **The path is `<repo root>/tmp/issues`, resolved from `issue.py`'s own
|
||||
location, not from cwd.** `issue.store_root()` walks up from `__file__` to the
|
||||
@@ -106,10 +111,27 @@ plain grep works without a parser.
|
||||
and they say so on stderr.
|
||||
- Identity is the slug (`wire-sqlc-appclick.md`), never a tracker number.
|
||||
Numbers live in the `gitea:` field.
|
||||
- `origin: local` is a durable state. An issue that never leaves this machine is
|
||||
complete and valid, not a draft.
|
||||
- Pushing is additive: the file is never deleted, it gains `gitea:` / `url:` /
|
||||
`synced:`.
|
||||
- Pulling overwrites the body — a fetch, not a merge.
|
||||
- No drift tracking. `synced:` tells you how old your copy is; re-pull when it
|
||||
matters.
|
||||
- `origin: local` is a complete state, not a draft: an issue that never leaves
|
||||
this machine is valid and finished. It is not a *durable* state, though —
|
||||
pushing ends it, and the local file goes with it.
|
||||
- **A successful push deletes the local file** (`<id>.md` and
|
||||
`<id>.comments.md`), and prints the number and URL the issue now lives at.
|
||||
`--update` too: one rule, no exception. What is in the store is what has not
|
||||
left. Get it back with `pull.py <n>`.
|
||||
- Deletion happens only after a confirmed tracker response and only after
|
||||
`.remote.json` has been written. Network down, non-2xx, an answer that does
|
||||
not carry the right number: the file stays and the run stops. A never-pushed
|
||||
`origin: local` issue is never touched by any of this.
|
||||
- The slug survives the round trip because it goes up in the body as
|
||||
`<!-- tea:id … -->` (`map.with_id_marker`) and is indexed by number in
|
||||
`tmp/issues/.remote.json`. A rename in the web UI, a lost `.remote.json`, a
|
||||
fresh clone, another machine — the file comes back under the same name and
|
||||
every `depends:` that points at it still resolves.
|
||||
- `.remote.json` is therefore no longer "an index over the files": it is the
|
||||
local number → slug ledger, its entries outlive the files they name, and
|
||||
nothing prunes them. It is still recoverable — from the markers in Gitea, not
|
||||
from the files.
|
||||
- Pulling overwrites the body — a fetch, not a merge. It is also how a pushed
|
||||
issue comes back at all.
|
||||
- No drift tracking, and now nothing to track: there is no second copy to
|
||||
diverge from. `synced:` tells you how old your working copy is.
|
||||
|
||||
Reference in New Issue
Block a user