- Added core documentation for the Team Management feature, including iteration goals, implementation plans, and design specifications. - Introduced new files for team configuration, IPC channels, and UI components for team listing and Kanban board. - Established a clear definition of done and scope for the first iteration, focusing on visible results and graceful degradation in the UI. - Documented the messaging and task file formats, ensuring robust communication between team members. - Implemented a Kanban design with defined states and transitions for task management.
158 lines
5.5 KiB
Markdown
158 lines
5.5 KiB
Markdown
# Research: Task-файлы Claude Code
|
||
|
||
## Формат
|
||
|
||
**Путь**: `~/.claude/tasks/{teamName}/{id}.json`
|
||
|
||
```json
|
||
{
|
||
"id": "48",
|
||
"subject": "Rename package in pubspec.yaml",
|
||
"description": "Change name: dartdoc to name: dartdoc_vitepress",
|
||
"activeForm": "Updating pubspec.yaml",
|
||
"owner": "senior-1-rename",
|
||
"status": "completed",
|
||
"blocks": [],
|
||
"blockedBy": [],
|
||
"metadata": { "_internal": true }
|
||
}
|
||
```
|
||
|
||
### Поля
|
||
|
||
| Поле | Обязательно | Тип | Описание |
|
||
|------|:-----------:|-----|----------|
|
||
| `id` | YES | string | Числовой ID в виде строки ("1", "48") |
|
||
| `subject` | YES | string | Краткий заголовок задачи |
|
||
| `description` | NO | string | Детальное описание |
|
||
| `activeForm` | NO | string | Present continuous для спиннера ("Updating...") |
|
||
| `owner` | NO | string | Имя агента-владельца |
|
||
| `status` | YES | string | pending / in_progress / completed / deleted |
|
||
| `blocks` | NO | string[] | ID задач, которые зависят от этой |
|
||
| `blockedBy` | NO | string[] | ID задач, от которых зависит эта |
|
||
| `metadata` | NO | object | Произвольный объект |
|
||
|
||
### Статусы
|
||
|
||
```
|
||
pending — задача создана, ждёт исполнителя
|
||
in_progress — агент работает над задачей
|
||
completed — агент завершил
|
||
deleted — задача удалена
|
||
```
|
||
|
||
---
|
||
|
||
## .highwatermark
|
||
|
||
**Путь**: `~/.claude/tasks/{teamName}/.highwatermark`
|
||
**Содержимое**: число (последний выданный ID)
|
||
|
||
### Как используется
|
||
|
||
```
|
||
1. TaskCreate → читает .highwatermark (например, "48")
|
||
2. Новый ID = 49
|
||
3. Создаёт 49.json
|
||
4. Записывает "49" в .highwatermark
|
||
```
|
||
|
||
### Риск при внешней записи
|
||
|
||
Если мы создаём задачу с ID=50, но .highwatermark = 48:
|
||
- Следующий TaskCreate создаст 49.json (ОК)
|
||
- Ещё один — 50.json → **ПЕРЕЗАПИШЕТ нашу задачу**
|
||
|
||
**Решение**: Не создавать задачи напрямую. Мы только ЧИТАЕМ задачи и модифицируем status/metadata.
|
||
|
||
---
|
||
|
||
## .lock файл
|
||
|
||
**Путь**: `~/.claude/tasks/{teamName}/.lock`
|
||
**Содержимое**: пустой файл (0 байт)
|
||
|
||
### Поведение
|
||
|
||
- Обновляется (touch) при обращении к tasks
|
||
- Механизм блокировки НЕДОКУМЕНТИРОВАН
|
||
- Вероятно используется как advisory lock (наличие = кто-то работает)
|
||
|
||
### Наш подход
|
||
|
||
Не трогаем .lock. Он для Claude Code. Мы делаем atomic write (tmp + rename) что безопасно без lock.
|
||
|
||
---
|
||
|
||
## Видимость для агентов
|
||
|
||
| Инструмент | Видит status | Видит metadata | Видит owner | Видит blockedBy |
|
||
|------------|:---:|:---:|:---:|:---:|
|
||
| TaskList | YES | **NO** | YES | YES |
|
||
| TaskGet | YES | YES | YES | YES |
|
||
| TaskUpdate | Меняет | Меняет | Меняет | Меняет |
|
||
|
||
**Следствие**: Агент через TaskList НЕ увидит наш kanbanColumn в metadata. Поэтому хранить kanban-состояние в metadata бессмысленно для агентов, и рискованно (может быть перезаписано).
|
||
|
||
---
|
||
|
||
## metadata: merge vs replace
|
||
|
||
### Проблема
|
||
|
||
**НЕДОКУМЕНТИРОВАНО.** Неизвестно что происходит при:
|
||
```typescript
|
||
// Текущее состояние:
|
||
{ metadata: { kanbanColumn: "REVIEW", _internal: true } }
|
||
|
||
// Агент делает TaskUpdate:
|
||
TaskUpdate({ metadata: { owner_note: "done" } })
|
||
|
||
// Результат ???
|
||
// MERGE: { kanbanColumn: "REVIEW", _internal: true, owner_note: "done" }
|
||
// REPLACE: { owner_note: "done" } ← kanbanColumn ПОТЕРЯНО
|
||
```
|
||
|
||
### Решение
|
||
|
||
Не полагаемся на metadata. Kanban-состояние храним в собственном файле `kanban-state.json`.
|
||
|
||
---
|
||
|
||
## Конкурентный доступ
|
||
|
||
### Сценарий
|
||
|
||
```
|
||
T=0: App читает task.json: { status: "completed", metadata: {} }
|
||
T=1: Agent читает тот же файл: { status: "completed", metadata: {} }
|
||
T=5: App пишет: { status: "in_progress", metadata: {} } (Fix)
|
||
T=6: Agent пишет: { status: "completed", metadata: { note: "done" } }
|
||
→ Наш status: "in_progress" ПОТЕРЯН
|
||
```
|
||
|
||
### Вероятность
|
||
|
||
**СРЕДНЯЯ** — мы пишем в task только при Fix (status → in_progress). Но агент может одновременно обновлять тот же файл.
|
||
|
||
### Митигация
|
||
|
||
1. **Atomic write** (tmp + rename)
|
||
2. **Verify after write**: перечитать файл, проверить что status = наш
|
||
3. **File watcher**: если status изменился обратно → показать warning в UI
|
||
|
||
### Что пишем в task-файлы
|
||
|
||
Минимум — только `status` при Fix:
|
||
```typescript
|
||
// Читаем текущий task
|
||
const task = JSON.parse(fs.readFileSync(taskPath));
|
||
// Меняем только status
|
||
task.status = 'in_progress';
|
||
// Atomic write
|
||
const tmp = taskPath + '.tmp.' + process.pid;
|
||
fs.writeFileSync(tmp, JSON.stringify(task, null, 2));
|
||
fs.renameSync(tmp, taskPath);
|
||
```
|
||
|
||
Всё остальное (kanbanColumn, reviewStatus) — в нашем kanban-state.json.
|