Files
pi-auto-reinject/TODO.md
T

262 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TODO: pi-skill-reinject
План реализации extension для Pi Coding Agent. ТЗ — [`SPEC.md`](./SPEC.md). Workflow агента — [`AGENTS.md`](./AGENTS.md).
---
## Правила ведения `TODO.md`
### Роль файла
- **Единственный** источник фаз и чеклистов для цикла «работай по фазе».
- `AGENTS.md` — как исполнять пункты; `TODO.md`**что** делать и в каком порядке.
- Не дублировать полное ТЗ из `SPEC.md`; в пунктах — ссылки на разделы SPEC при необходимости.
### Структура фазы
Каждая фаза — заголовок `### Фаза N — …` и чеклист `- [ ]` / `- [x]`.
| Элемент | Правило |
|---------|---------|
| Нумерация | `0`, `1`, `2`, … — монотонно; не переиспользовать номера |
| Пункт | Одна атомарная мысль = один коммит кода (см. AGENTS.md) |
| Крупный пункт | Разбить на подпункты `- [ ]` до размера «один коммит» |
| Статус фазы | В конце секции или в таблице в `AGENTS.md` при необходимости |
| Зависимости | Явно: «после фазы N», «блокируется …» |
### Формат пункта чеклиста
```markdown
- [ ] **Краткое имя** — что сделать; зачем в одной фразе; ссылка на SPEC §X при нужде
```
Хорошо: «**state.ts** — load/save через `appendEntry`, dedupe skills by name (SPEC §6.1)».
Плохо: «сделать state» (неатомарно, непонятен scope коммита).
### Коммиты и отметки `[x]`
| Действие | Когда |
|----------|--------|
| Код по пункту | Коммит `Phase N: …`**без** `TODO.md` в том же коммите |
| `[x]` на пункте | В коммите **конца фазы** `TODO: …` (или отдельном `TODO:`), не вместе с кодом пункта |
| Правки workflow | `TODO:` / `AGENTS:` — отдельный коммит; оба файла можно в одном |
| Черновик плана | Можно править `TODO.md` по ходу; финальные галочки — с концом фазы |
### Добавление и изменение плана
- Новая фаза — в конец раздела «Фазы реализации», не вставлять между завершёнными без причины.
- Отменённый пункт — `[x]` с пометкой «отменено» в тексте или удалить с записью в коммите `TODO:`.
- Перенос между фазами — обновить оба файла (`TODO.md` + при необходимости таблицу статуса в `AGENTS.md`).
### Чего не писать в `TODO.md`
- Длинные спецификации (они в `SPEC.md`).
- Журнал багов при ручном тесте — в [`BACKLOG.md`](./BACKLOG.md).
- Секреты, пути к личным `~/.pi/…` с токенами.
### Старт работы агентом
1. Прочитать `AGENTS.md` и актуальную фазу в этом файле.
2. Найти первый `- [ ]` в запрошенной фазе (или текущей незавершённой).
3. Выполнить цикл: правки → проверка → review → исправления → коммит → следующий пункт.
4. В конце фазы — коммит `TODO:`/`AGENTS:`, пауза для пользователя (если не сказано иначе).
---
## Контекст
| Сейчас | Цель |
|--------|------|
| Только `SPEC.md` + `README.md` | Рабочий extension `src/index.ts` + тесты |
| Default off | `/skill-reinject on` / `global on` |
| Нет re-inject после compaction | Auto compaction → re-inject tracked skills (SPEC §56) |
---
## Решения (зафиксировано в SPEC)
Ключевые решения не дублировать здесь — см. [`SPEC.md`](./SPEC.md) §5–8, §16. При расхождении при реализации — сначала обновить SPEC или зафиксировать в «Решения» ниже.
| Тема | Где |
|------|-----|
| Триггер re-inject | `session_compact`, auto only (§5.2, §8) |
| Доставка с pi-auto-compact | `defer` + `before_agent_start` (§6.5, §16) |
| Персистенция | `pi.appendEntry("skill-reinject:state", …)` (§6.1) |
| Команды | `/skill-reinject` (§7) |
---
## Фазы реализации
| Фаза | Название | Зависимости | Критерии SPEC |
|------|----------|-------------|---------------|
| 0 | Каркас репозитория | — | §9, §10 |
| 1 | Состояние и персистенция | 0 | §6.1 |
| 2 | Настройки | 0 | §7.3 |
| 3 | Детекция skills | 1 | §6.2, §12.1 |
| 4 | Expand skill-блоков | 0 | §5.3, §12.1 |
| 5 | Kept window | 3 | §6.4, §12.1 |
| 6 | pi-auto-compact | 2 | §16 |
| 7 | Re-inject оркестрация | 1, 4, 5, 6 | §5.2, §6.5 |
| 8 | Источник compaction | 1 | §8 |
| 9 | Хуки отслеживания | 1, 2, 3 | §6.2 |
| 10 | Восстановление сессии | 1, 3, 9 | §6.3 |
| 11 | Команды и UI | 1, 2, 6, 7 | §7 |
| 12 | Edge cases и полировка | 711 | §11 |
| 13 | Приёмка и документация | 0–12 | §1213 |
**Порядок:** фазы 0→13; внутри фазы — сверху вниз. Параллельно после фазы 0 можно вести 1 и 2; фазы 3 и 4 — независимы друг от друга.
---
### Фаза 0 — Каркас репозитория
- [x] **package.json** — manifest с `pi.extensions`, devDependencies (`@earendil-works/pi-coding-agent`, `typescript`); зачем: загрузка extension через `pi -e` (SPEC §9.1, §10)
- [x] **tsconfig.json** — strict TS, module resolution под Pi extension runtime; зачем: `tsc --noEmit` в цикле AGENTS
- [x] **npm scripts**`typecheck`, `test`, `build` (минимально); зачем: единая проверка в каждом пункте
- [x] **src/index.ts shell**`export default function(pi: ExtensionAPI)`, пустой `session_start`; зачем: smoke `pi -e ./src/index.ts` без логики
---
### Фаза 1 — Состояние и персистенция
- [x] **state.ts types**`TrackedSkill`, `ExtensionState` (version 1), `RuntimeFlags`; зачем: единый контракт §6.1
- [x] **state.ts initial**`createInitialState()`, `createRuntimeFlags()`; зачем: предсказуемый старт сессии
- [x] **state.ts persist**`saveState(pi, state)` через `appendEntry("skill-reinject:state", …)`; зачем: пережить `/resume` (§6.1)
- [x] **state.ts load**`loadStateFromBranch(branch)` из последнего custom entry; зачем: восстановление без полного rescan
- [x] **state.ts trackSkill** — upsert по `name`, merge `sources`, preserve insertion order; зачем: дедуп §6.1
---
### Фаза 2 — Настройки
- [x] **settings.ts types**`SkillReinjectSettings` + defaults из §7.3 (`enabled`, `trackReadPaths`, `triggerTurn`, `reinjectOnManualCompaction`, `autoCompactIntegration`, `suffix`)
- [x] **settings.ts read** — merge global + project из `ctx` / Pi settings API; зачем: не читать файл вручную, если API даёт merged view
- [x] **settings.ts writeGlobal** — merge в `~/.pi/agent/settings.json` без затирания чужих ключей; зачем: `/skill-reinject global on`
- [x] **settings.ts effective**`effectiveEnabled(sessionOverride, global)`, `effectiveIntegration(...)`; зачем: три слоя §5.1
- [x] **test/settings.test.ts** — defaults, merge write (mock/temp file); зачем: §12.1
---
### Фаза 3 — Детекция skills
- [ ] **detect.ts slash**`detectSlashSkill(text)``/^\/skill:([a-z0-9-]+)/`; зачем: источник `slash` §6.2
- [ ] **detect.ts skill-block**`parseSkillBlocksFromText(text)` (regex как `parseSkillBlock`); зачем: источник `skill-block` §6.2
- [ ] **detect.ts read-path**`matchReadPathToSkill(path, skills)` по `filePath` из resourceLoader; зачем: источник `read` §6.2
- [ ] **detect.ts trackReadPaths gate** — пропуск read-детекции при `trackReadPaths: false`; зачем: §6.2, §3
- [ ] **test/detect.test.ts** — slash, blocks, read match, trackReadPaths off; зачем: §12.1
---
### Фаза 4 — Expand skill-блоков
- [ ] **expand.ts readBody**`readSkillBody(filePath)` + strip YAML frontmatter; комментарий «mirror agent-session»; зачем: §5.3, §10
- [ ] **expand.ts formatBlock** — XML `<skill name location>…</skill>` с `baseDir`; зачем: повтор `_expandSkillCommand` §5.3
- [ ] **expand.ts suffix** — опциональный суффикс из `settings.suffix`; зачем: §5.3
- [ ] **expand.ts expandSkill** — публичная функция: skill meta → готовый user text; зачем: reinject + `/skill-reinject now`
- [ ] **test/expand.test.ts** — frontmatter strip, paths, suffix; зачем: §12.1
---
### Фаза 5 — Kept window
- [ ] **kept.ts slice**`getKeptEntries(branch, firstKeptEntryId)` от compaction entry до хвоста; зачем: §6.4
- [ ] **kept.ts present**`skillsPresentInKeptWindow(keptEntries, skillNames)` по `<skill name="…"`; зачем: не дублировать блоки §6.4, критерий §13
- [ ] **kept.ts filter**`filterSkillsNeedingReinject(tracked, kept, registeredNames)`; зачем: вход для `pendingReinject` §5.2
- [ ] **test/kept-window.test.ts** — in / not in kept, пустой kept; зачем: §12.1
---
### Фаза 6 — pi-auto-compact
- [ ] **auto-compact.ts detect**`detectPiAutoCompact(pi)` через `getCommands()``auto-compact`; кэш в `RuntimeFlags`; зачем: §16.4
- [ ] **auto-compact.ts deliveryMode**`resolveDeliveryMode(settings, runtime, sessionIntegrationOverride)` по таблице §6.5.3; зачем: defer vs immediate
- [ ] **auto-compact.ts constants**`PI_AUTO_COMPACT_FOLLOW_UP_PREFIXES` (документация/тесты, не runtime match); зачем: §16.9
- [ ] **auto-compact.ts hint** — одноразовый `ui.notify` при detect Pi default compaction + pi-auto-compact; зачем: §16.7
---
### Фаза 7 — Re-inject оркестрация
- [ ] **reinject.ts plan**`planReinject(state, settings, ctx, compactionEvent)` → имена skills с учётом kept + registration; зачем: §5.2 п.45
- [ ] **reinject.ts defer enqueue** — на `session_compact`: `pendingReinject := plan`, без `sendUserMessage`; зачем: §6.5.1, §16.2
- [ ] **reinject.ts defer inject** — на `before_agent_start`: объединённое message со всеми блоками, clear queue; зачем: §5.3, §6.5.1
- [ ] **reinject.ts immediate idle** — первый skill обычный, остальные `followUp`; зачем: §6.5.2
- [ ] **reinject.ts immediate streaming**`willRetry` / streaming → все `deliverAs: "followUp"`; зачем: §5.2, §6.5.2
- [ ] **reinject.ts skip missing** — skill удалён с диска → skip + `ui.notify` warning; зачем: §11
- [ ] **reinject.ts force now**`reinjectNow(pi, state, settings)` для `/skill-reinject now`; зачем: §7.1 debug
---
### Фаза 8 — Источник compaction
- [ ] **compaction.ts state machine**`pendingCompactionSource: "auto" \| "manual" \| null`; зачем: §8
- [ ] **compaction.ts input hook**`text.startsWith("/compact")` → manual; зачем: §8
- [ ] **compaction.ts before_compact** — если не manual → auto; зачем: §8
- [ ] **compaction.ts shouldReinject** — gate: enabled + source + `reinjectOnManualCompaction`; reset после `session_compact`; зачем: §5.2, §8, критерий §13
---
### Фаза 9 — Хуки отслеживания
- [ ] **index.ts input track** — на `input`: slash `/skill:name``trackSkill`; зачем: §6.2 #1
- [ ] **index.ts message_end** — user messages → skill-block scan; зачем: §6.2 #2
- [ ] **index.ts tool read**`tool_call`/`tool_result` с `read` на `SKILL.md`; зачем: §6.2 #3
- [ ] **index.ts persist on track**`saveState` после изменения skills / session override; зачем: §6.1
- [ ] **index.ts session_compact wire** — связать §78: plan → defer/immediate; зачем: end-to-end trigger
---
### Фаза 10 — Восстановление сессии
- [ ] **index.ts session_start load** — load state entry + read global settings + `detectPiAutoCompact`; зачем: §5.1, §16.4
- [ ] **index.ts branch rescan** — если нет state entry: full rescan user messages в `getBranch()`; зачем: §6.3
- [ ] **index.ts resume reload**`reason: "reload" \| "resume" \| "switch"` — тот же путь; зачем: §6.3, `/tree` §11
- [ ] **index.ts session_shutdown** — flush pending `saveState`; зачем: §11
---
### Фаза 11 — Команды и UI
- [ ] **commands.ts register**`pi.registerCommand("skill-reinject", handler)`; зачем: §7
- [ ] **commands.ts status** — вывод без аргументов по формату §7.2 (enabled layer, delivery, tracked, pending, last compaction)
- [ ] **commands.ts session toggle**`on` / `off` / `reset` → session override + persist; зачем: §5.1, §7.1
- [ ] **commands.ts global toggle**`global on` / `global off` → settings.json; зачем: §7.1, критерий §13
- [ ] **commands.ts list clear**`list` tracked skills; `clear` без сброса toggle; зачем: §7.1
- [ ] **commands.ts integration**`integration auto|defer|immediate|off` session override в config entry; зачем: §7.1, §16.4
- [ ] **commands.ts now** — делегат в `reinjectNow`; зачем: §7.1
- [ ] **commands.ts aliases** — опционально `/sr`, `/skills-reinject`; зачем: §7.1
- [ ] **commands.ts status line**`ctx.ui.setStatus("skill-reinject", "on·N")` на изменениях; зачем: §7.2, критерий §13
---
### Фаза 12 — Edge cases и полировка
- [ ] **reinject.ts manual defer clear** — на manual compaction: не enqueue (или clear `pendingReinject` на следующем user prompt при default); зачем: §16.5, §12.3 п.6
- [ ] **reinject.ts name collision** — два skill с одним name → первый из resourceLoader + warn; зачем: §11
- [ ] **reinject.ts maxSkills warn** — soft warn при >3 (если `maxSkills` не задан — unlimited); зачем: §15
- [ ] **commands.ts no-ui** — RPC / `hasUI === false`: команды без падения, notify no-op; зачем: §11
- [ ] **index.ts double compact** — каждый `session_compact` пересчитывает `pendingReinject`; зачем: §16.6
---
### Фаза 13 — Приёмка и документация
- [ ] **README.md** — статус «реализовано», установка `pi -e`, ссылка на `/skill-reinject`, coexistence с pi-auto-compact; зачем: §9.1, §16.7
- [ ] **Manual E2E standalone** — прогон чеклиста §12.2 (записать результат в коммит / BACKLOG при сбоях); зачем: §12.2
- [ ] **Manual E2E pi-auto-compact** — прогон §12.3 (defer, нет гонки, manual `/compact`); зачем: критерии §13
- [ ] **Критерии §13** — сверка всех 10 пунктов; расхождения → BACKLOG или правка SPEC
---
## После v1 (не блокирует фазы 0–13)
Зафиксировано в SPEC §14 — не включать в чеклист v1, только при отдельном запросе:
- `reinjectOnManualCompaction: true` как осознанный default-path
- custom summary в `session_before_compact`
- `pi.events` протокол с pi-auto-compact
- npm package (`keywords: ["pi-package"]`)
- re-inject только последнего активного skill