@goodandready/dsh-kanban 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GooDAnDReaDY
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,455 @@
1
+ # dsh-kanban
2
+
3
+ Доска задач для [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): задачи из Gitea, колонки по реальному воркфлоу и отдельная сессия агента под каждую задачу.
4
+
5
+ ## Что умеет
6
+
7
+ - **Ставить задачи и следить за ними.** Свои задачи и задачи из Gitea на одной доске, порядок карточек переживает перезагрузку.
8
+ - **Заводить задачу из issue.** Кнопка «+ Задача» показывает открытые issue выбранного репозитория; уже импортированные приглушены. Карточка заполняется заголовком, телом, метками и ссылкой.
9
+ - **Запускать работу.** Выбрали модель, нажали «Начать» — поднимается отдельная сессия агента по этой задаче, карточка уезжает в «В работе».
10
+ - **Выбирать, чем запускать.** Профиль агента и уровень доступа (`read-only`, `workspace-write`, `danger-full-access`) — списки приходят от харнесса, свои слова доска не выдумывает.
11
+ - **Продолжать в том же чате.** У задачи ровно одна сессия на всю её жизнь. Как только сессия есть, кнопка становится «Продолжить» и открывает прежний чат, а не заводит новый. Перед отправкой видно заготовку сообщения — её можно переписать, уйдёт то, что осталось в поле.
12
+ - **Запускать пачку.** Отметили несколько карточек — все уезжают в одну сессию и приходят агенту по очереди: первая в работе, остальные «в очереди» с номером. Отдельную задачу можно доложить в уже идущую сессию.
13
+ - **Держать доску в порядке.** Счётчик ждущих ответа в шапке, сортировка по дате, отбор по автору, групповые действия над выбранными и остановка агента прямо с карточки.
14
+ - **Показывать задачу в чате.** В сессии, поднятой с доски, в шапке виден чип задачи с текущей колонкой.
15
+
16
+ ## Установка
17
+
18
+ ```bash
19
+ dsh plugin --profile web add @goodandready/dsh-kanban
20
+ ```
21
+
22
+ Из локальной рабочей копии:
23
+
24
+ ```bash
25
+ dsh plugin --profile web add file:/путь/к/dsh-kanban
26
+ ```
27
+
28
+ Перезапустите веб-интерфейс и обновите страницу с очисткой кэша.
29
+
30
+ Других плагинов не требует: клиент Gitea у канбана свой. Пока адрес инстанса не задан, доска работает, а импорт честно объясняет, чего не хватает.
31
+
32
+ ## Настройка
33
+
34
+ **Настройки → Плагины**, карточка **Канбан**.
35
+
36
+ | Поле | Что задаёт |
37
+ |---|---|
38
+ | Адрес инстанса | `https://gitea.example.com`. Пусто — импорт недоступен. |
39
+ | Имя учётной записи | ИМЯ учётной записи DSH с токеном, по умолчанию `GITEA_TOKEN`. Сам токен сюда не вводить. |
40
+ | Корень проектов | Где лежат рабочие копии. Пусто — рабочая папка харнесса. |
41
+ | Шаблон первого сообщения | Текст, с которого начинается сессия. Пусто — встроенный. Подстановки: `{repo}`, `{issueNumber}`, `{title}`, `{body}`, `{issueUrl}`. |
42
+ | Пределы колонок | Предел числа карточек. Ноль — без предела. |
43
+
44
+ Токен в настройках **не хранится**: там только ИМЯ учётной записи, а сам секрет лежит в хранилище учётных данных DSH. Наружу значение не отдаётся никогда, включая сообщения об ошибках. Если у вас уже настроен `GITEA_TOKEN` для других плагинов, достаточно вписать адрес инстанса.
45
+
46
+ Все поля настроек скалярные. Это не упрощение, а требование: клиентский API настроек пишет только скаляры, и словарь или массив сделал бы карточку нередактируемой. Доски поэтому живут в хранилище задач, а не в настройках.
47
+
48
+ ## Колонки
49
+
50
+ | Колонка | Признак входа |
51
+ |---|---|
52
+ | Backlog | issue есть, никто не взял |
53
+ | В работе | сессия поднята, в issue есть стартовый комментарий с планом и веткой |
54
+ | Ревью | PR снят с WIP, ревьюер даёт PASS/FAIL |
55
+ | Deploy | PR влит в `main`, ждём согласованный deploy и production-проверку |
56
+ | Cleanup | ветка в Gitea, worktree и локальная ветка ещё не удалены |
57
+ | Done | issue закрыт **и** cleanup записан в итоговом комментарии |
58
+
59
+ Колонка `cleanup` не лишняя: по воркфлоу задача не завершена, пока cleanup не сделан и не записан. Доска, отправляющая карточку в `done` по merge, врёт ровно там, где копятся забытые ветки.
60
+
61
+ WIP PR **не** двигает карточку в «Ревью»: он открывается рано и означает «работа идёт», а не «смотрите».
62
+
63
+ Превышение предела колонки подсвечивается, но перенос не запрещает: предел — ориентир, а не преграда.
64
+
65
+ ## Чего плагин не делает
66
+
67
+ - **Не создаёт ветки и worktree.** По воркфлоу агент сначала делает preflight — смотрит открытые ветки и worktree, проверяет рабочее дерево — и только потом заводит worktree от свежего `origin/main`. Ветка, созданная заранее, обессмысливает preflight.
68
+ - **Не пишет стартовый комментарий в issue.** Его пишет агент после preflight, потому что до preflight ветка и план неизвестны.
69
+ - **Не подсказывает имя ветки** в первом сообщении: его выбирает агент.
70
+ - Ветка и worktree на карточке — **наблюдение**, а не намерение.
71
+ - Не трогает Hermes Kanban. Это другая система.
72
+
73
+ ## Хранилище
74
+
75
+ `<DSH_HOME>/kanban/kanban.db`, где `DSH_HOME` — переменная окружения либо `~/.dsh`. SQLite через встроенный `node:sqlite`; внешних зависимостей нет.
76
+
77
+ Порядок карточек хранится дробным индексом: перетаскивание — всегда один `UPDATE` одной строки, сколько бы карточек ни было в колонке.
78
+
79
+ ## Автоперемещение
80
+
81
+ **Обратно в Gitea доска сообщает одно: закрытие issue**, когда карточка доходит до «Выполнено». Меток колонок она не ставит — стадия и так видна по ветке и pull request, а метка лишь пересказывала бы то, что уже написано рядом. Заодно доска не трогает чужую разметку вовсе.
82
+
83
+ Карточки двигаются сами. Три источника со строгим приоритетом — **Gitea > инструмент агента > жизненный цикл сессии**. Без приоритета «PR влит → deploy» через секунду перебивалось бы «ход завершился → в работе», и карточка прыгала бы.
84
+
85
+ | Наблюдение в Gitea | Колонка |
86
+ |---|---|
87
+ | есть ветка по задаче | В работе |
88
+ | PR открыт и снят с WIP | Ревью |
89
+ | PR влит | Deploy |
90
+ | issue закрыт, ветка ещё есть | Cleanup |
91
+ | issue закрыт, ветки нет | Done |
92
+
93
+ Метки и заголовок доска забирает из Gitea при каждой сверке: переименовали issue или сменили `priority/high` на `priority/critical` — доска это увидит. **Тело не забирает** — его правит человек заметкой из чипа, и сверка, затирающая тело каждые две минуты, съедала бы заметки; тело обновляется по кнопке «обновить».
94
+
95
+ Каждый переход попадает в журнал задачи с источником и пояснением: `Ревью ← gitea · PR #39 снят с WIP`. Когда доска однажды покажет не то, будет видно, кто её подвинул.
96
+
97
+ ### Две доски
98
+
99
+ Досок ровно две, и обе заводятся сами при первом запуске.
100
+
101
+ | Доска | Колонки | Для чего |
102
+ |---|---|---|
103
+ | Проектная | бэклог · в работе · ревью · deploy · cleanup · выполнено | задачи из issue |
104
+ | Простая | бэклог · в работе · проверка · выполнено | свободные задачи |
105
+
106
+ `review`, `deploy` и `cleanup` осмысленны только там, где за задачей стоит issue: у свободной заметки нет ни ветки, ни PR, и Gitea о ней ничего не скажет. Три колонки, которые никогда не заполнятся, — не запас на будущее, а помеха.
107
+
108
+ Импорт issue на простую доску не предлагается и отвергается сервером. Вид доски — её явное свойство, а не вывод из содержимого: доска, меняющая число колонок от того, что на неё положили, ведёт себя непредсказуемо.
109
+
110
+ Прежняя единственная доска СТАНОВИТСЯ проектной вместе со всеми задачами — вторая заводится рядом, а не вместо неё. Смены вида у доски с задачами пока нет: карточки из `deploy` и `cleanup` на простой доске девать некуда.
111
+
112
+ ### Чип задачи в чате
113
+
114
+ В шапке чата, поднятого с доски, виден чип задачи с текущей колонкой. Он умеет три вещи:
115
+
116
+ - **перенести в колонку** — то же, что перенос на доске, со всеми последствиями;
117
+ - **перейти к карточке** — открывает доску и сразу окно этой задачи, переключая доску, если карточка лежит на другой;
118
+ - **оставить заметку** — дописывает текст в тело задачи.
119
+
120
+ Заметка меняет тело, а тело уходит агенту первым сообщением при запуске. Значит заметка, оставленная до запуска, попадёт в работу, а после запуска — нет; это написано подсказкой рядом с полем, иначе поведение выглядит случайным.
121
+
122
+ Дописывает заметку сервер, а не браузер: чип держит копию задачи, и склейка у него потеряла бы всё, что тело нажило с момента её получения.
123
+
124
+ ### Создание задачи: проект или не проект
125
+
126
+ ```
127
+ Новая задача
128
+ ○ Проект → репозиторий: выбрать из Gitea либо ввести имя нового
129
+ ○ Не проект → уходит на простую доску
130
+ ```
131
+
132
+ **Проект** заводит issue в Gitea, и карточка сразу к нему привязана. Репозиторий выбирается из существующих либо создаётся: приватный и пустой, без README и заготовок. Создание репозитория необратимо, и окно говорит об этом до нажатия, а не после.
133
+
134
+ **Не проект** — заметка, не относящаяся ни к какому репозиторию: живёт на простой доске и в Gitea не попадает.
135
+
136
+ Правило без исключений: **есть issue — проектная доска, нет — простая.** Помнить, где вы её завели, не нужно.
137
+
138
+ Порядок при создании неслучаен: сперва репозиторий, потом issue, потом карточка. Обрыв на середине не оставляет карточку привязанной к репозиторию без issue — лучше не завести её вовсе и сказать об этом. Созданный репозиторий при этом не удаляется: убирать чужое добро из-за своей неудачи хуже, чем оставить лишний пустой репозиторий.
139
+
140
+ **Инструмента заведения issue у агента нет и не будет.** У соседнего плагина `dsh-gitea` уже есть `gitea_issue_create`, и второй способ делать то же самое разошёлся бы с первым. Агент зовёт свой инструмент, а карточка приезжает подхватом.
141
+
142
+ ### Задачи приезжают сами
143
+
144
+ Доска сама забирает открытые задачи **из всех репозиториев организации** и заводит карточки в бэклоге. Настраивать для этого ничего не нужно.
145
+
146
+ Опрашиваются только репозитории, где открытые задачи есть: их число приходит в общем списке, и отсев бесплатен — полсотни запросов впустую каждые две минуты это расточительство, а не осторожность. Заброшенные репозитории пропускаются.
147
+
148
+ Организация определяется сама, пока у токена она одна; иначе её называют настройкой. Настройка «Сузить подхват до репозиториев» позволяет смотреть только на часть — пустая означает «все».
149
+
150
+ Не берутся: закрытые issue (это уже не работа), pull request-ы (это ход работы, а не сама работа), issue с уже заведённой карточкой и те, чью карточку вы **удалили** — удаление значит «видеть её здесь не хочу», и возвращать её каждый проход было бы спором с человеком каждые две минуты.
151
+
152
+ Вебхук зовёт подхват для того репозитория, о котором пришло событие: новый issue становится карточкой за секунды, а не ждёт таймера. Только по репозиторию события — полный обход при каждом чихе в любом из отслеживаемых означал бы обращение ко всем.
153
+
154
+ Недоступный репозиторий не роняет остальные: он пропускается до следующего прохода, а причина попадает в состояние сверки в шапке.
155
+
156
+ ### Архив
157
+
158
+ Карточки, отстоявшие свой срок в «Выполнено», уезжают в архив — свой экран, а не колонка: колонка это стадия работы, а архив стадией не является. Срок в настройках, по умолчанию неделя, ноль отключает.
159
+
160
+ Возраст считается с попадания карточки в «Выполнено», а не по последнему изменению: сверка трогает задачу и после завершения, и по «изменено» архив откладывался бы бесконечно.
161
+
162
+ **Архивируется только `done`.** Задача, застрявшая в ревью на месяц, — это сигнал, а не мусор, и прятать её нельзя.
163
+
164
+ Ничего не удаляется: возвращённая карточка встаёт в ту же колонку, откуда ушла. Архив обязан быть обратимым, иначе им не станут пользоваться из страха.
165
+
166
+ ### Перенос без мыши
167
+
168
+ На карточке — «перенести» со списком колонок её доски. Окно подтверждения и последствия те же, что при перетаскивании: путь другой, а результат один, и разойтись им нельзя. Карточка стала точкой табуляции — доска была мышиной целиком.
169
+
170
+ Перетаскивание никуда не делось: речь о втором пути, а не о замене.
171
+
172
+ ### Видно, как идёт сверка
173
+
174
+ В шапке доски — состояние сверки с Gitea:
175
+
176
+ | Что видно | Что значит |
177
+ |---|---|
178
+ | `сверено 2 мин назад` | всё в порядке; точное время в подсказке |
179
+ | `сверяемся с Gitea…` | проход идёт прямо сейчас |
180
+ | `сверки с Gitea ещё не было` | харнесс только поднялся |
181
+ | **`сверка не проходит`** | выделено; в подсказке место, причина и время последней удачной сверки |
182
+
183
+ Причина называется словами — «репозиторий не найден», «токен не подошёл», — а не прячется в журнал харнесса. Доска, переставшая обновляться, не имеет права молчать: отличить «ничего не изменилось» от «мы ослепли» иначе нечем.
184
+
185
+ **Неудача не затирает время последнего успеха.** Иначе теряется ответ на главный вопрос: когда доска в последний раз видела правду.
186
+
187
+ Состояние живёт в памяти процесса, а не в базе: это положение текущего запуска. После перезапуска честнее сказать «сверки ещё не было», чем показать вчерашний успех как сегодняшний.
188
+
189
+ Повторных попыток и починки здесь нет — задача показать, а не вылечить.
190
+
191
+ ### Свои названия колонок
192
+
193
+ Настройка «Свои названия колонок» — пары `идентификатор=Название` через запятую, например `backlog=Идеи, review=Проверка`. Пусто — обычные названия.
194
+
195
+ Одно поле на все колонки, а не шесть отдельных: переименовывают их раз в жизни, а шесть полей засорили бы карточку настроек навсегда. Своё название приходит с сервера — там конфиг; перевод остаётся в браузере — там языки.
196
+
197
+ ### Как разложена колонка
198
+
199
+ Внутри колонки задачи сгруппированы по проектам: заголовок со счётчиком, щелчок сворачивает группу. Крупные проекты идут первыми — с двадцатью задачами проект не должен теряться под однозадачными. Заголовки появляются, только когда проектов в колонке больше одного.
200
+
201
+ Внутри группы наверх поднимается идущая работа: **сперва ждущие человека, затем идущие, затем остановившиеся**, дальше всё прочее в прежнем порядке. Ждущие впереди идущих намеренно: идущая работа идёт сама, а ждущая стоит из-за вас.
202
+
203
+ Ручной порядок при этом не теряется — подъём работает поверх него, внутри каждой ступени порядок прежний.
204
+
205
+ Порядок задаёт сервер, а браузер рисует заголовок при смене проекта: повторять правила во второй раз значило бы завести вторую их редакцию.
206
+
207
+ ### Языки
208
+
209
+ Плагин несёт два словаря — английский и китайский: это встроенные локали ядра. Русский приезжает языковым пакетом `@goodandready/dsh-russian-lang`, который регистрирует `ru` для пространства `dsh-kanban` извне. Свой `ru` внутри плагина мешал бы пакету: повторная регистрация локали падает, и пакет такие пространства пропускает.
210
+
211
+ Без службы локализации (например, до её появления в сборке) доска говорит по-английски — это запасной перевод, а не выбор.
212
+
213
+ ### Отбор видно
214
+
215
+ Когда что-нибудь отобрано или введён поиск, рядом с отборами появляется «показано 12 из 81». На нетронутой доске этой строки нет — она была бы шумом. Без неё при восьмидесяти карточках легко решить, что отбор сломан, когда он просто строгий.
216
+
217
+ ### Что на карточке
218
+
219
+ Шапка карточки отвечает на два вопроса раньше заголовка — чей это проект и что за работа:
220
+
221
+ ```
222
+ memory-brain#1369 [type/feature]
223
+ BudgetTracker concurrent reservations: детерминированный max-cost…
224
+ 3 из 7 · пишу тесты
225
+ перенести · в работе · здесь 2 дн
226
+ ```
227
+
228
+ Имя проекта и номер задачи разделены: имя плашкой, номер отдельно и приглушённее. Это две разные вещи — принадлежность и адрес, — и слипшимися они читаются как один непонятный идентификатор. **Номер ведёт на issue**: чаще всего ссылка нужна именно с карточки, а раньше пряталась в окне задачи. Щелчок по номеру карточку не открывает.
229
+
230
+ Метки покрашены **цветами из Gitea** — теми же, что вы видите в самом issue. Цвет текста поверх выбирается по яркости фона, как это делает Gitea: иначе бледная метка на светлом становится нечитаемой.
231
+
232
+ На карточке видна **дата заведения задачи**, а не время в колонке: «здесь 34 минуты» у задачи, приехавшей подхватом полчаса назад, не значит ничего — это возраст карточки на доске, а спрашивают про возраст задачи. Год показывается, только если он не нынешний.
233
+
234
+ У задачи с сессией рядом с отметкой «в работе» стоит **переход в чат**: «идёт» без пути к тому, что идёт, — сведение, которым нельзя воспользоваться.
235
+
236
+ Счётчик колонки — залитая плашка цвета своей колонки, а при превышении предела красная. Серым на сером он не читался.
237
+
238
+ Слева на карточке — полоса цвета `priority`. Критичное видно с другого конца экрана, не читая. Задача без срочности остаётся без полосы: пустая полоса читалась бы как «низкий приоритет», а это неправда.
239
+
240
+ «Перенести» показывается при наведении и при фокусе с клавиатуры — прятать ради компактности то, что делалось ради доступности, нельзя. Полный заголовок — в подсказке: на карточке он обрезан двумя строками.
241
+
242
+ Метка без цвета остаётся нейтральной. Цвета приезжают вместе с именами при импорте, подхвате и каждой сверке.
243
+
244
+ ### Отборы
245
+
246
+ В шапке доски — по одному списку на репозиторий и на каждое пространство меток:
247
+
248
+ ```
249
+ [ репозиторий ▾ ] [ type ▾ ] [ priority ▾ ] [ status ▾ ] [ scope ▾ ] ... [ поиск ]
250
+ ```
251
+
252
+ | Уровень | Правило |
253
+ |---|---|
254
+ | между пространствами | **и** — «срочные баги» это `priority/high` и `type/bug` |
255
+ | внутри пространства | **или** — срочность у задачи одна, и «и» всегда давало бы пусто |
256
+
257
+ Пространства собираются из меток, которые реально лежат на карточках: всё до первой `/` — пространство, всё после — значение. Список не зашит, поэтому заведённое в Gitea новое пространство появится само, без правки кода и без релиза. Порядок задан для известных, незнакомые дописываются в конец по алфавиту.
258
+
259
+ Метки без пространства (`epic`, `hotfix`) своего отбора не получают — список из двух значений ради двух меток был бы мебелью. На карточках они видны и находятся поиском.
260
+
261
+ **Счётчик колонки считает отобранное, а поиск — нет.** Разница осмысленная: поиск временный, и предел «3 из 3» при одной найденной карточке врал бы; отбор постоянный, и на отборе по одному проекту «в работе 3 из 3» должно значить «по этому проекту».
262
+
263
+ Отборы держатся, пока открыт харнесс, и сбрасываются при смене доски: на другой доске те же значения могут не встречаться вовсе.
264
+
265
+ ### Возврат в чат
266
+
267
+ В шапке доски слева — кнопка «В чат». Доска открывается на месте разговора, и до неё закрыть её можно было тремя неочевидными способами: клавишей Escape, повторным щелчком по строке в боковом списке и переходом в чужую сессию. Кнопка не заводит четвёртого механизма, а зовёт тот же самый: разойтись им негде.
268
+
269
+ ### Окно задачи
270
+
271
+ ```
272
+ [memory-brain#1368] [type/feature] [Закрыть]
273
+ Same-turn memory write-back contract
274
+ заведена 2 дня назад · изменена 5 мин назад · issue ↗
275
+
276
+ <описание, размеченное>
277
+ <план агента, если есть>
278
+ <журнал, если есть>
279
+ <запуск: провайдер, модель, сообщение>
280
+ ```
281
+
282
+ **Описание разбирается разметкой**: заголовки, списки, выделение, `код`, блоки кода, цитаты и ссылки. Всё непонятое остаётся текстом как есть — полноценный markdown доска не тянет и не притворяется.
283
+
284
+ Разбор идёт на сервере и приезжает **деревом**, а браузер собирает его элементами. Тело задачи пишет кто угодно, и вставлять его строкой HTML значило бы дать issue выполнить что угодно в вашем интерфейсе; при сборке элементами подстановка невозможна по построению, а не по бдительности. Ссылки открываются только `http` и `https` — `javascript:` в теле остаётся видимым текстом, а не превращается в ссылку.
285
+
286
+ Пустые разделы не рисуются вовсе.
287
+
288
+ ### Карточка открывается окном
289
+
290
+ Панель справа в 420 пикселей вмещала описание и журнал, но не план с отметками. Теперь карточка открывается окном поверх доски: описание, метки, ссылка на issue, план агента, журнал переходов, время, пусковая полоса. Закрывается крестиком, щелчком по затемнению и клавишей Escape.
291
+
292
+ Окно живёт внутри экрана доски, а не поверх всего интерфейса: доска сама лежит в колонке разговора, и выход за её пределы наложился бы на чужие части оболочки. Снизу оставлен запас — поверх интерфейса висят плавающие виджеты соседних плагинов.
293
+
294
+ ### Время на доске
295
+
296
+ | Где | Что видно |
297
+ |---|---|
298
+ | карточка | сколько стоит в этой колонке — `здесь 3 дн` |
299
+ | карточка с сессией | когда её последний раз трогали; после порога молчания отметка становится тревожной |
300
+ | окно задачи | когда заведена и когда менялась в последний раз |
301
+
302
+ Формат относительный, потому что «третий день» читается мгновенно, а точная дата требует сравнения с сегодняшним числом; сама дата — в подсказке при наведении. Время в колонке считается по отметке, которую ставит перенос: перестановка внутри колонки её не сбрасывает, задача в ревью третий день там и остаётся.
303
+
304
+ Порог молчания настраивается (`staleAfterMin`, по умолчанию час) и считается на хосте: отдавать браузеру ещё и порог значило бы отдавать ему решение. Отдельного опроса живости сессий нет — это был бы второй источник правды и лишняя нагрузка.
305
+
306
+ **Открыть чат.** У задачи с сессией в окне есть кнопка возврата в её чат. Без неё задача, идущая вторые сутки, ищется в общем списке по имени вида `kanban-9e84cae6-…`.
307
+
308
+ ### Сворачивание колонок
309
+
310
+ Шесть колонок по 220 пикселей не помещаются на ноутбуке. Поэтому пустая колонка сама сжимается до вертикальной полоски с названием, а щелчок по названию сворачивает и разворачивает любую, включая непустую. Решение человека сильнее автоматики в обе стороны: развёрнутая им пустая колонка не схлопнется у него на глазах.
311
+
312
+ Свёрнутая показывает число карточек — колонка с работой, выглядящая пустой, хуже любой тесноты. Целью переноса она остаётся: карточку можно бросить в неё, не разворачивая.
313
+
314
+ Колонки не прячутся совсем. `cleanup` пуст ровно до того момента, когда в нём появляется забытая ветка; спрятав его, мы спрятали бы тот самый сигнал, ради которого его завели.
315
+
316
+ ### Ручной перенос — это команда агенту
317
+
318
+ Карточку двигают не ради учёта. Человек перетащил — значит просит запустить тот этап, куда перетащил, и команда уходит в чат задачи отдельным сообщением. При отпускании карточки доска показывает окно с описанием последствий; отмена оставляет карточку на месте.
319
+
320
+ | Куда перенесли | Что происходит |
321
+ |---|---|
322
+ | Backlog | работа останавливается: идущий ход агента прерывается |
323
+ | В работе | агент начинает или продолжает реализацию |
324
+ | Ревью | агент снимает с PR пометку черновика и запрашивает проверку |
325
+ | Deploy | агент вливает PR и выкатывает |
326
+ | Cleanup | агент удаляет ветку, worktree и локальную ветку |
327
+ | Done | завершение; сюда переносит только человек |
328
+
329
+ **Перенос в Deploy и есть явное «ок» на выкатку.** Воркфлоу требует разрешения человека перед deploy, и это разрешение дано движением, а не словом в чате — второй раз агент не спрашивает.
330
+
331
+ Инструменту агента переход в `done` закрыт и даже не предлагается в перечислении. Его путь к завершению — закрыть issue и удалить ветку; сверка с Gitea сама увидит закрытый issue без ветки. Карточка двигается фактом, а не заявлением.
332
+
333
+ ### План агента на карточке
334
+
335
+ Агент публикует план своим инструментом `board_plan` — доска не разбирает его сообщения, потому что разбор это гадание, и не опирается на чужой `todo_list`, потому что это была бы зависимость от соседнего плагина.
336
+
337
+ Список пунктов шлётся один раз, дальше агент двигает только номер текущего: гонять весь план ради одного числа незачем. Всё, что до текущего пункта, считается сделанным.
338
+
339
+ На карточке видно `3 из 7 · пишу тесты`, в окне задачи — весь план: сделанное зачёркнуто, идущий пункт выделен. Плана из одного пункта и плана из двадцати это касается одинаково — на карточку едут только числа и текущий пункт.
340
+
341
+ | Состояние | Откуда известно |
342
+ |---|---|
343
+ | в работе | у живого агента идёт ход |
344
+ | ждёт пользователя | запрос разрешения или вопрос |
345
+ | остановился | сессия у задачи записана, а живого агента за ней нет |
346
+
347
+ «Остановился» — не догадка по тишине, а факт: так выглядит работа, прерванная перезапуском харнесса. Порога тишины в настройках нет и не будет: он был бы догадкой там, где есть точный признак.
348
+
349
+ **Признак «нужен человек».** Колонки «Блокировано» в наборе нет, поэтому запрос разрешения или вопрос агента не двигают карточку, а поднимают на ней оранжевую точку. Признак снимается сам, когда ход возобновляется.
350
+
351
+ **Инструмент агента** `board_move` выключен по умолчанию: скилл `gitea-project-workflow` пока запрещает этому CLI трогать канбан.
352
+
353
+ Определение инструмента живёт в `lib/board-tool.js` отдельно от обвязки cordis и
354
+ следует контракту `defineTool` из `@deepseek-ai/dsh-tools` **не ниже 0.1.0-rc.8**:
355
+ `output` обязателен, а `parameters` — карта «имя -> схема значения», а не JSON
356
+ Schema с корнем `type: 'object'`. Пропущенный `output` роняет не инструмент, а
357
+ загрузку всего профиля, поэтому форма закреплена тестами `test/board-tool.test.mjs`.
358
+
359
+ ### Пачка задач одной сессией
360
+
361
+ Флажок на карточке отмечает задачу, в шапке появляется «Выбрано N» и «Запустить выбранные». Вся пачка уезжает в **одну** сессию: провайдер и модель спрашиваются один раз, рабочая папка берётся у первой задачи и называется в окне подтверждения.
362
+
363
+ Задачи приходят агенту **по очереди** — по сообщению на каждую, а не одним списком. Выбора «отдать разом» нет намеренно: правило выпуска релизов всё равно не даёт делать десять задач сразу, а десять карточек в «В работе», из которых делается одна, лгут о состоянии доски и лишают предел колонки смысла.
364
+
365
+ В «В работе» переезжает только первая. Остальные остаются на месте с отметкой «в очереди · 2», «в очереди · 3» и так далее — номер считается по порядку постановки внутри сессии.
366
+
367
+ Каждое сообщение называет свой номер и общее число: «Задача 2 из 5». Без этого агент, получив первую из пяти, объявит работу законченной и приберёт за собой — удалит ветку и закроет issue.
368
+
369
+ **Инструменты при нескольких задачах в сессии.** `board_move` обязан назвать, какую карточку двигает; не назвал — отказ с перечислением, а не догадка: подвинутую не ту карточку заметят нескоро. При одной задаче называть нечего, поведение прежнее. `board_plan` пишет один план на сессию — это план работы над пачкой, — и кладёт его всем её задачам.
370
+
371
+ ### Очередь к идущей работе
372
+
373
+ В меню карточки — «в очередь…»: выбор из **живых** сессий доски. Задача привязывается к выбранной сессии, сообщение уходит туда же, и агент возьмёт его, закончив текущий ход.
374
+
375
+ Своей очереди плагин не заводит. Она уже есть у ядра: `followup` кладёт сообщение, агент забирает его на границе шага. Вторая очередь рядом с настоящей разошлась бы с ней при первой же заминке, и разбираться, какая права, пришлось бы человеку.
376
+
377
+ Сессия без живого агента в список не попадает: предлагать её значило бы предлагать очередь, из которой никто ничего не возьмёт.
378
+
379
+ «Снять с очереди» отвязывает задачу и пишет в её журнал, что **отозвать уже отданное сообщение нечем** — агент может дойти до задачи и увидеть карточку, которая его не ждёт.
380
+
381
+ ### Чем запускать: профиль агента и уровень доступа
382
+
383
+ В окне задачи рядом с провайдером и моделью — **профиль агента** и **уровень доступа**. Оба списка приходят от харнесса: профили из `agentPresets`, уровни из `permissionPresets` (обычно `read-only`, `workspace-write`, `danger-full-access`). Свой словарь уровней доска не заводит и пояснения к ним берёт у ядра: собственные слова разошлись бы с тем, что уровень значит на самом деле.
384
+
385
+ Профиль едет в создание сессии (`meta.agentPreset`) — это факт создания, у идущей сессии его не сменить. Поэтому при «Продолжить» поле профиля не показывается: показывать поле, которое ничего не меняет, значит врать.
386
+
387
+ **Выбрали уровень доступа, а поставить его нечем — задача не запускается.** Сессия иначе пошла бы с правами по умолчанию, то есть, возможно, бо́льшими, чем просили; поднятая сессия при этом убирается за собой. Молчаливое расширение прав хуже отказа.
388
+
389
+ Обе службы у ядра необязательны: если развёртывание их не собирает, списки приходят пустыми и поля не рисуются вовсе. Сломанный профиль в список не попадает — сессия на нём не соберётся, а отказ прилетел бы уже после запуска.
390
+
391
+ При групповом запуске поля те же и стоят в том же окне: пачка идёт одной сессией, значит профиль и доступ у неё один.
392
+
393
+ ### Что видно в шапке доски
394
+
395
+ **«Ждут ответа: 2»** — задачи, где агент задал вопрос или просит разрешения. Щелчок оставляет на доске только их, повторный снимает. Когда таких задач нет, счётчика нет вовсе: ноль в шапке — мебель, за которой перестают следить.
396
+
397
+ **Порядок задач** — «как разложено» (ручной порядок плюс живое вперёд), «сначала старые», «сначала новые». Сортировка идёт в браузере поверх присланного порядка и ничего не записывает: это взгляд на доску, а не свойство задач. Группировку по проектам она не ломает — карточки переставляются внутри своего проекта, а идущая работа остаётся наверху группы.
398
+
399
+ **Отбор по автору** стоит в одном ряду с метками. Задачи без автора — свои, заведённые на доске — собираются под значением `—`: «никто» тоже ответ на вопрос «кто завёл», и по нему тоже отбирают.
400
+
401
+ ### Остановить, не двигая карточку
402
+
403
+ Раньше прервать агента можно было только перетащив карточку в «Бэклог», то есть отменив заодно и этап. В меню карточки есть «остановить»: прерывается ход, колонка остаётся. Пункт виден только у идущей задачи — останавливать нечего у того, кто не идёт.
404
+
405
+ «Агент не работал» и «остановлено» — разные ответы, и доска не выдаёт второй за первый.
406
+
407
+ ### Пачку видно на карточках
408
+
409
+ У карточки, чья сессия несёт больше одной задачи, стоит «пачка 2 из 5» — тот же порядок, в котором агент их берёт. Наведение подсвечивает остальные карточки той же сессии. Одиночная задача отметки не получает: «1 из 1» ничего не сообщает.
410
+
411
+ ### Групповые действия
412
+
413
+ В полосе выбора кроме «Запустить выбранные» — «В архив», «Снять с очереди», «Перенести…». Отдельного группового маршрута нет: это те же действия, что для одной задачи, применённые по очереди.
414
+
415
+ **Итог называется числом.** Часть задач может отказать — архивировать можно только из «Выполнено», снять с очереди можно только стоящую в ней, — и доска говорит «сделано у 7 из 10, остальные отказали». Молчаливо съеденный отказ означал бы карточку, про которую человек уверен, что она в архиве.
416
+
417
+ ### Доска помнит, чем запускали
418
+
419
+ Провайдер, модель, профиль агента и уровень доступа подставляются те же, что в прошлый раз на этой доске. Память живёт в браузере у человека, а не в настройках плагина: это привычка одного, а не свойство доски.
420
+
421
+ Подставляется только то, что ядро всё ещё предлагает: исчезнувшая модель или уровень доступа заменяются умолчанием. Тихо подставить несуществующий уровень значило бы запустить задачу не с теми правами.
422
+
423
+ ## Синхронизация
424
+
425
+ **Gitea → канбан.** Опрос с настраиваемым промежутком плюс вебхук для мгновенной сверки. Вебхук — ускорение, а не замена: он может быть не настроен, недоступен снаружи или потерян, и опрос остаётся всегда.
426
+
427
+ Маршрут вебхука `/dsh-kanban/webhook`, подпись проверяется общим секретом. **Без секрета вебхук не принимается вовсе** — иначе карточки двигал бы кто угодно снаружи.
428
+
429
+ **Канбан → Gitea.** Доска сообщает Gitea ровно одно: **закрытие issue**, когда карточка доходит до «Выполнено». Меток колонок она не ставит — стадия и так видна по ветке и pull request, а метка лишь пересказывала бы то, что уже написано рядом. Заодно доска не трогает чужую разметку вовсе: `bug`, `feat`, `hotfix` принадлежат процессу, и стирать их доска права не имеет.
430
+
431
+ Отправка идёт очередью: перенос карточки не ждёт сети, доска работает и при недоступном Gitea. Неудачная отправка повторяется, а исчерпав попытки — попадает в журнал задачи, а не исчезает.
432
+
433
+ **Конфликты.** Если обе стороны изменились с последней сверки, побеждает более позднее изменение, а проигравшее **называется в журнале задачи**. Синхронизация, тихо отменяющая правку человека, хуже отсутствующей: расхождение остаётся, а узнать о нём неоткуда.
434
+
435
+ До первой сверки «изменилось с прошлого раза» смысла не имеет — прошлого раза не было, — поэтому первая сверка принимает положение дел, а не спорит с ним.
436
+
437
+ ## Ограничения
438
+
439
+ - Точного «работа идёт прямо сейчас» инстанс не знает. Порядок репозиториев и вывод колонок — приближение по открытым задачам, веткам и времени правки.
440
+ - Клиент Gitea покрывает только нужное доске: issue, PR, ветки, метки, поиск репозиториев.
441
+ - Неполное наблюдение считается отсутствующим: репозиторий, который не прочитался целиком, пропускается до следующей сверки. Иначе непрочитанные PR выглядели бы как их отсутствие и утаскивали карточку назад.
442
+
443
+ ## Разработка
444
+
445
+ ```bash
446
+ npm test
447
+ ```
448
+
449
+ Тесты идут без харнесса, без сети и без установленных зависимостей: чистая логика вынесена в модули без импортов, а браузерная половина проверяется через `node:vm` с заглушкой загрузчика.
450
+
451
+ Тестовая матрица — в [index.md](index.md). План выпуска — в [docs/plans](docs/plans).
452
+
453
+ ## Лицензия
454
+
455
+ MIT
@@ -0,0 +1,4 @@
1
+ - insert:
2
+ - id: dsh-kanban
3
+ name: '@goodandready/dsh-kanban'
4
+ config: {}