Drop the local copy after a successful push and re-pull on demand #16

Closed
opened 2026-08-10 10:14:46 +00:00 by claude · 1 comment
Collaborator

Summary

После успешного push удалять локальный файл issue. Источник правды — Gitea;
локальная копия становится кэшем на время работы, а не вторым хранилищем.
Актуальное состояние получается повторным pull.py, а не сверкой.

Spec

skills/issue/references/format.md, AGENTS.md

Depends on

  • write-issue-dependencies-to-gitea-on-push — пока depends: не доезжает до
    трекера, удаление файла стирает граф безвозвратно

Motivation

Сейчас после push одна и та же issue лежит в двух местах, и ничто не следит за
расхождением: synced: говорит лишь, насколько стара копия, drift не
отслеживается вообще (AGENTS.md, раздел «Local issue store»; /tea:sync,
раздел «Drift»). Расхождение приходится держать в голове — правил ли я тело
локально, дошло ли оно наверх, чей текст свежее. Удаление после push убирает
вопрос целиком: локально лежит только то, что ещё никуда не уехало, всё
остальное берётся заново.

Это осознанная смена решения, записанного в AGENTS.md: «tmp/issues/the
store, not a cache of Gitea
», «Pushing is additive: the file is never
deleted», «origin: local is a durable state». Правило меняется на: локальный
store хранит origin: local issue как единственный экземпляр, а всё с
origin: gitea — как удаляемый кэш.

Acceptance criteria

  • push.py после успешного создания удаляет tmp/issues/<id>.md и
    <id>.comments.md, и печатает номер и URL, по которым issue теперь живёт
  • origin: local issue, которую не пушили, не удаляется никогда
  • push.py --update синхронизирован с тем же правилом (удаляет после
    успешного PATCH), либо явно исключён в ## Constraints
  • удаление происходит только после подтверждённого ответа трекера: сеть
    упала, код не 2xx, тело не совпало — файл на месте
  • pull.py восстанавливает issue с тем же слагом, что был до удаления,
    в том числе после переименования в Gitea (сейчас слаг заново выводится из
    заголовка: pull.py:70issue.slugify(payload["title"]))
  • depends: переживает круг «push → удалить → pull» без потерь
  • tmp/issues/.remote.json не превращается в мусор после удаления файлов —
    он объявлен индексом над файлами (_gitea.py:309)
  • AGENTS.md и skills/issue/references/format.md переписаны под новое
    правило; формулировки «the store, not a cache» и «origin: local is a
    durable state» приведены в соответствие
  • есть тест на круг: push → файла нет → pull → файл идентичен исходному по
    слагу, depends: и телу

Constraints

Два предварительных условия — без них удаление это потеря данных, а не упрощение:

  1. depends: не доезжает до Gitea. Push не создаёт нативных связей, а
    pull.py восстанавливает граф только из #N, найденных в прозе
    ## Depends on, и только через id_for_number — карту, построенную над
    локальными файлами (map.py:107-126). Файлы удалены → карта пуста → рёбра
    молча пропадают. Отсюда depends: на этой issue.
  2. Слаг не переживает круг. Формат объявляет слаг стабильным на всю жизнь
    issue, но при pull несуществующего локально issue он выводится заново из
    заголовка (pull.py:70, issue.py:103-121). Переименовали в вебе — после
    удаления и повторного pull это другой файл, и все ссылки на старый слаг в
    depends: соседних issue мертвы. Слаг должен храниться на стороне трекера
    (маркер в теле, метка, отдельное поле) либо восстанавливаться по номеру.

Вне объёма: слежение за drift и любая сверка «локальное против удалённого» —
эта issue их отменяет, а не улучшает.

## Summary После успешного push удалять локальный файл issue. Источник правды — Gitea; локальная копия становится кэшем на время работы, а не вторым хранилищем. Актуальное состояние получается повторным `pull.py`, а не сверкой. ## Spec `skills/issue/references/format.md`, `AGENTS.md` ## Depends on - write-issue-dependencies-to-gitea-on-push — пока `depends:` не доезжает до трекера, удаление файла стирает граф безвозвратно ## Motivation Сейчас после push одна и та же issue лежит в двух местах, и ничто не следит за расхождением: `synced:` говорит лишь, насколько стара копия, drift не отслеживается вообще (`AGENTS.md`, раздел «Local issue store»; `/tea:sync`, раздел «Drift»). Расхождение приходится держать в голове — правил ли я тело локально, дошло ли оно наверх, чей текст свежее. Удаление после push убирает вопрос целиком: локально лежит только то, что ещё никуда не уехало, всё остальное берётся заново. Это осознанная смена решения, записанного в `AGENTS.md`: «`tmp/issues/` — **the store, not a cache of Gitea**», «Pushing is additive: the file is never deleted», «`origin: local` is a durable state». Правило меняется на: локальный store хранит `origin: local` issue как единственный экземпляр, а всё с `origin: gitea` — как удаляемый кэш. ## Acceptance criteria - [ ] `push.py` после успешного создания удаляет `tmp/issues/<id>.md` и `<id>.comments.md`, и печатает номер и URL, по которым issue теперь живёт - [ ] `origin: local` issue, которую не пушили, не удаляется никогда - [ ] `push.py --update` синхронизирован с тем же правилом (удаляет после успешного PATCH), либо явно исключён в `## Constraints` - [ ] удаление происходит только после подтверждённого ответа трекера: сеть упала, код не 2xx, тело не совпало — файл на месте - [ ] `pull.py` восстанавливает issue с **тем же** слагом, что был до удаления, в том числе после переименования в Gitea (сейчас слаг заново выводится из заголовка: `pull.py:70` → `issue.slugify(payload["title"])`) - [ ] `depends:` переживает круг «push → удалить → pull» без потерь - [ ] `tmp/issues/.remote.json` не превращается в мусор после удаления файлов — он объявлен индексом над файлами (`_gitea.py:309`) - [ ] `AGENTS.md` и `skills/issue/references/format.md` переписаны под новое правило; формулировки «the store, not a cache» и «`origin: local` is a durable state» приведены в соответствие - [ ] есть тест на круг: push → файла нет → pull → файл идентичен исходному по слагу, `depends:` и телу ## Constraints Два предварительных условия — без них удаление это потеря данных, а не упрощение: 1. **`depends:` не доезжает до Gitea.** Push не создаёт нативных связей, а `pull.py` восстанавливает граф только из `#N`, найденных в прозе `## Depends on`, и только через `id_for_number` — карту, построенную над локальными файлами (`map.py:107-126`). Файлы удалены → карта пуста → рёбра молча пропадают. Отсюда `depends:` на этой issue. 2. **Слаг не переживает круг.** Формат объявляет слаг стабильным на всю жизнь issue, но при pull несуществующего локально issue он выводится заново из заголовка (`pull.py:70`, `issue.py:103-121`). Переименовали в вебе — после удаления и повторного pull это другой файл, и все ссылки на старый слаг в `depends:` соседних issue мертвы. Слаг должен храниться на стороне трекера (маркер в теле, метка, отдельное поле) либо восстанавливаться по номеру. Вне объёма: слежение за drift и любая сверка «локальное против удалённого» — эта issue их отменяет, а не улучшает.
claude added the comp/sync
type
task
comp/issue
labels 2026-08-10 10:14:46 +00:00
claude added a new dependency 2026-08-10 11:41:02 +00:00
naudachu changed reference from feat/issue-cache-scripts to main 2026-08-10 12:29:13 +00:00
Author
Collaborator

Реализовано в main коммитами e629d14 и 2ac3015 — закрываю.

Проверено по каждому критерию против origin/main = 2ac3015, отдельным
проходом, без единой правки кода:

# критерий где
1 push удаляет <id>.md и <id>.comments.md, печатает номер и URL skills/sync/scripts/push.py:185-199 (drop_local), :378, :407-409
2 никогда не отправленный origin: local не удаляется единственный call site push.py:407 достижим только после подтверждённой записи; tests/test_drop_after_push.py:348-380
3 --update подчиняется тому же правилу push.py:13-16, :333-338; тесты :316-346
4 удаление только после 2xx с совпавшим номером push.py:159-182 (confirmed_number), гейт :346-350; тесты :382-527
5 pull возвращает тот же слаг, в том числе после переименования в UI pull.py:73-98 (id_for), map.py:142-184, :322; тесты :661-698
6 depends: переживает push → удаление → pull push.py:387-400, :110-156; pull.py:171-172, :221; тесты :641-653, :689-698
7 .remote.json не мусор после удаления _gitea.py:347-355, rebuild_map :373-399 мержит, а не пересобирает; тесты :758-806
8 AGENTS.md и format.md переписаны AGENTS.md:114-158, skills/issue/references/format.md:81-99
9 тест на round trip tests/test_drop_after_push.py:619-639

Прогон на origin/main: Ran 232 tests in 3.288s — OK.

Побочная находка вынесена отдельной issue #21: depends: возвращался только под
--deps, то есть обычный pull.py <n> отдавал единицу работы без графа. Теперь
это поведение по умолчанию — PR #36.

Реализовано в `main` коммитами `e629d14` и `2ac3015` — закрываю. Проверено по каждому критерию против `origin/main` = `2ac3015`, отдельным проходом, без единой правки кода: | # | критерий | где | |---|---|---| | 1 | push удаляет `<id>.md` и `<id>.comments.md`, печатает номер и URL | `skills/sync/scripts/push.py:185-199` (`drop_local`), `:378`, `:407-409` | | 2 | никогда не отправленный `origin: local` не удаляется | единственный call site `push.py:407` достижим только после подтверждённой записи; `tests/test_drop_after_push.py:348-380` | | 3 | `--update` подчиняется тому же правилу | `push.py:13-16`, `:333-338`; тесты `:316-346` | | 4 | удаление только после 2xx с совпавшим номером | `push.py:159-182` (`confirmed_number`), гейт `:346-350`; тесты `:382-527` | | 5 | pull возвращает тот же слаг, в том числе после переименования в UI | `pull.py:73-98` (`id_for`), `map.py:142-184`, `:322`; тесты `:661-698` | | 6 | `depends:` переживает push → удаление → pull | `push.py:387-400`, `:110-156`; `pull.py:171-172`, `:221`; тесты `:641-653`, `:689-698` | | 7 | `.remote.json` не мусор после удаления | `_gitea.py:347-355`, `rebuild_map` `:373-399` мержит, а не пересобирает; тесты `:758-806` | | 8 | `AGENTS.md` и `format.md` переписаны | `AGENTS.md:114-158`, `skills/issue/references/format.md:81-99` | | 9 | тест на round trip | `tests/test_drop_after_push.py:619-639` | Прогон на `origin/main`: `Ran 232 tests in 3.288s — OK`. Побочная находка вынесена отдельной issue #21: `depends:` возвращался только под `--deps`, то есть обычный `pull.py <n>` отдавал единицу работы без графа. Теперь это поведение по умолчанию — PR #36.
Sign in to join this conversation.