596cf853e81f5ed47fd7cd99835f1663156456f5
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
596cf853e8 |
fix: keep request payloads out of the issue store
`labels.py` handed `_gitea.api` the issue store as a place to put the request file, and on a checkout without a store that quietly created `tmp/issues/.payload/`. Bootstrapping a repository's labels touches no issue at all, so the one rule the store has — nothing materializes it as a side effect of a write — was broken by an operation that has no business knowing the store exists. Where a request body goes was never the caller's decision to make. It is now the transport's: `tmp/payload/`, resolved from `_gitea.py`'s own location the way both domains resolve theirs, so every caller — sync and wiki alike — writes to one directory whatever it was invoked from, and `out_root` is gone from `api`, `add_dependency` and all six call sites. The directory is created by the first write of a run and not before: a `--dry-run` leaves nothing behind. `tmp/` is already gitignored. The name carries the distinction the old path lost. A store holds the only copy of something; this holds debris kept for a retry or a post-mortem, and deleting it costs nothing. A dotdir sitting among an issue's files claimed otherwise, and `ls tmp/issues` started lying about what existed. tests/test_payload_root.py runs the real `labels.py` in a throwaway repo against a fake `tea` on PATH: no store appears, the payloads land in tmp/payload/, a dry run writes nothing, and a run from a subdirectory still resolves to the repo root. Two source checks keep the callers from drifting apart again. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e629d14585 |
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>
|
||
|
|
0cf4baa429 |
fix: write issue dependencies to Gitea on push
The local `depends:` graph never reached the tracker. push.py sent dependent
issues in topological order but created no native links, so `native_deps` in
_gitea.py was a reader with no writer and the slugs in `## Depends on` stayed
dead prose for anyone reading the issue in Gitea.
Once an issue has its number, every `depends:` entry that also has one now
becomes a real link: POST /repos/{owner}/{repo}/issues/{index}/dependencies
with the blocker's IssueMeta. Topological order means the blocker is already
numbered, so no second pass is needed. Existing links are read back first, so
a repeat push is a no-op and never 409s; a link that fails anyway warns rather
than aborting a run that has already created issues. `--dry-run` prints the
links it would make and touches nothing.
The `## Depends on` prose is still passed through verbatim — the edge the
tracker acts on is the native link, not the text, which is exactly why the
text can be left alone. Removing a link that disappeared from `depends:` is
out of scope and now says so in push.py's docstring.
Establishes tests/: stdlib unittest, the transport stubbed at _gitea.api, no
network. Run with `python3 -m unittest discover -s tests`.
The POST body shape was confirmed against the instance's own swagger.v1.json
(Gitea 1.26.1), not assumed from upstream docs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
091dceec1d |
refactor: split issue domain from Gitea transport
An issue was a Gitea row that happened to be cached locally: its identity was the tracker's number (42.md), its dependencies were tracker numbers (depends: [#12]), and a local issue existed only as a draft that push deleted on success. Nothing could be planned or tracked without a tracker. Split into layers, with knowledge flowing one way: skills/issue DOMAIN what an issue is: format, validation, dep graph ^ offline; stdlib imports only, no subprocess | imports skills/sync BRIDGE map.py md <-> Gitea JSON, pure, no I/O _gitea.py login pin, api, pagination, filters skills/use REFERENCE tea CLI docs for non-issue entities skills/issue never imports skills/sync. Delete the sync layer and the domain keeps working. Identity is now a slug derived from the title (wire-sqlc-appclick.md) and is stable across retitles and pushes. Tracker numbers live in a `gitea:` field, never in a file name and never in `depends:`; the pair is indexed in .remote.json, which is a cache over the files, not a second source of truth. Behavior changes: - Pushing is additive. The file is never deleted; it gains gitea:/url:/ synced: and origin: flips from local to gitea. `origin: local` is a durable state, not a pending one. - Pushes go in topological order so dependencies get numbers first. - The dependency graph is computed offline from `depends:` metadata; body prose is passed through unchanged in both directions rather than being rewritten between slugs and #N. - `origin` is domain-owned (whether work exists elsewhere is a fact about the work); the handle and how to reach it stay with sync. Script moves: issue_get.py -> sync/pull.py issue_push.py -> sync/push.py issue_list.py -> sync/remote.py issue_index.py -> issue/issue_index.py _tea.py -> split into issue/issue.py, sync/map.py, sync/_gitea.py New: issue/issue_new.py, issue/issue_check.py, issue/issue_tree.py, and sync/comment.py — comment posting was the last issue operation still hand-rolled through raw `tea api`. references/issue-format.md moves to skills/issue/references/format.md; label hex colors move out of it into map.py, since a color is how a tracker paints a chip, not what an issue is. Verified: offline path end to end (new, check, tree, index, push --dry-run) and read-only against Gitea (remote listing, pull with mapping, comment guard). Write paths of push.py and comment.py are not exercised here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |