@goodandready/dsh-kanban 0.2.19 → 0.2.20

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/lib/commands.js CHANGED
@@ -1,21 +1,14 @@
1
- // Перенос карточки — это КОМАНДА агенту, а не запись о состоянии.
2
- //
3
- // Владелец не двигает карточки вручную ради учёта: если он двинул, значит
4
- // просит запустить тот этап, куда двинул. Поэтому таблица ниже описывает не
5
- // «что стало», а «что сделать», и текст уходит в чат задачи.
6
- //
7
- // Тексты последствий для окна подтверждения живут в клиентской половине:
8
- // они переводятся, а инструкция агенту — нет.
1
+ // Moving a card is a COMMAND to the agent, not merely a state update.
2
+ // If user moved a card, it instructs the agent to run that lifecycle phase.
3
+ // Confirmation dialog texts reside in client bundles where they are localized.
9
4
 
10
- /** Виды досок. Простая доска появляется в #63; таблица под неё уже здесь. */
5
+ /** Board kinds. Simple board introduced in #63; table supports it here. */
11
6
  export const BOARD_KINDS = ['project', 'simple']
12
7
 
13
8
  /**
14
- * Колонка `done` для инструмента агента закрыта.
15
- *
16
- * Путь агента к завершению — получить согласие, закрыть issue и удалить ветку;
17
- * сверка с Gitea сама увидит закрытый issue без ветки и передвинет карточку.
18
- * Карточка двигается фактом, а не заявлением, и подделать это нечем.
9
+ * `done` column is inaccessible to agent tool.
10
+ * Agent finishes task by obtaining acceptance, closing issue and deleting branch.
11
+ * Sync detects closed issue without branch and advances the card.
19
12
  */
20
13
  export const TOOL_FORBIDDEN_COLUMNS = ['done']
21
14
 
@@ -45,10 +38,10 @@ const TABLE = {
45
38
  }
46
39
 
47
40
  /**
48
- * Что означает перенос в колонку.
41
+ * Semantics of moving card to column.
49
42
  *
50
43
  * @returns {{stops: boolean, humanOnly: boolean, instruction: string}|undefined}
51
- * `undefined` — такой колонки на этой доске нет.
44
+ * `undefined` - column absent on this board.
52
45
  */
53
46
  export function commandFor(column, kind = 'project') {
54
47
  if (!columnsOfKind(kind).includes(column)) return undefined
@@ -63,18 +56,15 @@ export function commandFor(column, kind = 'project') {
63
56
  }
64
57
  }
65
58
 
66
- /** Колонки доски в том виде, в каком их понимает таблица команд. */
59
+ /** Board columns as understood by commands table. */
67
60
  export function columnsOfKind(kind = 'project') {
68
61
  return Object.keys(TABLE[kind] ?? {})
69
62
  }
70
63
 
71
64
  /**
72
- * Исполнить команду, стоящую за переносом карточки.
73
- *
74
- * Ничего не двигает: карточку уже передвинул `applyMove`. Здесь только
75
- * последствие переноса — остановка идущего хода либо сообщение в чат задачи.
76
- *
77
- * Отсутствие сессии не ошибка: карточку без чата двигают как обычную пометку.
65
+ * Execute command triggered by moving a card.
66
+ * Does not move card: move already applied. Handles side effects:
67
+ * interrupting active turn or posting instruction to task chat.
78
68
  *
79
69
  * @returns {{acted: 'stopped'|'sent'|'idle'|'no-session'|'human'|'unknown'}}
80
70
  */
@@ -91,8 +81,7 @@ export function dispatchMove({ agents, task, column, kind = 'project', createMes
91
81
  if (agent === undefined) return { acted: 'no-session' }
92
82
 
93
83
  if (command.stops) {
94
- // Карточка в бэклоге при работающем агенте — это ложь: работа идёт, а
95
- // доска говорит, что нет. Останавливаем по-настоящему.
84
+ // Card in backlog with running agent is inconsistent: interrupt active turn.
96
85
  if (agent.status !== 'running') return { acted: 'idle' }
97
86
  try {
98
87
  agent.cancel({ kind: 'user' })
@@ -104,7 +93,7 @@ export function dispatchMove({ agents, task, column, kind = 'project', createMes
104
93
  }
105
94
 
106
95
  agent.followup(createMessage({
107
- // Содержимое — МАССИВ блоков, а не строка: ядро перебирает его как список.
96
+ // Content is array of blocks: core processes as list.
108
97
  content: [{ type: 'text', text: command.instruction }],
109
98
  source: { kind: 'dsh-kanban', plugin: 'dsh-kanban', form: 'board-command' },
110
99
  }))
@@ -112,11 +101,8 @@ export function dispatchMove({ agents, task, column, kind = 'project', createMes
112
101
  }
113
102
 
114
103
  /**
115
- * Остановить ход агента, не двигая карточку.
116
- *
117
- * Прежде остановка была возможна только переносом в бэклог, то есть вместе с
118
- * отменой этапа. Здесь останавливается только ход: колонка — отдельное
119
- * решение человека, и доска не вправе принимать его за него.
104
+ * Stop agent turn without moving card.
105
+ * Interrupts execution while preserving column position.
120
106
  *
121
107
  * @returns {{acted: 'stopped'|'idle'|'no-session'}}
122
108
  */
@@ -127,8 +113,7 @@ export function stopWork({ agents, task, logger }) {
127
113
  let agent
128
114
  try { agent = agents.get(sessionId) } catch { agent = undefined }
129
115
  if (agent === undefined) return { acted: 'no-session' }
130
- // Остановить не идущего нечем, и говорить об этом надо прямо: молчаливое
131
- // «готово» на месте «он и так стоял» — ложь о том, что случилось.
116
+ // Agent was already idle, report explicitly.
132
117
  if (agent.status !== 'running') return { acted: 'idle' }
133
118
 
134
119
  try {
@@ -140,7 +125,7 @@ export function stopWork({ agents, task, logger }) {
140
125
  return { acted: 'stopped' }
141
126
  }
142
127
 
143
- /** Пояснения к остановке в журнале задачи. */
128
+ /** Stop details recorded in task transitions. */
144
129
  export const STOP_DETAIL = {
145
130
  stopped: 'user stopped agent turn',
146
131
  idle: 'agent was idle, nothing to stop',
@@ -148,10 +133,8 @@ export const STOP_DETAIL = {
148
133
  }
149
134
 
150
135
  /**
151
- * Пояснение к переходу в журнале задачи.
152
- *
153
- * Одна строка на один перенос: писать вторым рядом «а ещё мы остановили ход»
154
- * значило бы задваивать событие, которое было одним.
136
+ * Transition detail recorded in task history.
137
+ * Single event entry per transition.
155
138
  */
156
139
  export const MOVE_DETAIL = {
157
140
  stopped: 'move interrupted running agent turn',
package/lib/config.js CHANGED
@@ -1,35 +1,25 @@
1
- // Чистая часть настроек: значения по умолчанию, порядок колонок и доступ к
2
- // полям. Ни одной зависимости — поэтому проверяется без харнесса, без сети и
3
- // без установленных пакетов ядра. Обвязка cordis живёт в `lib/index.js`.
1
+ // Pure configuration logic: defaults, column ordering, and field accessors.
2
+ // Zero dependencies: tested without harness, network, or core packages.
3
+ // Cordis bindings reside in `lib/index.js`.
4
4
  //
5
- // Клиентский API настроек пишет ТОЛЬКО скалярные поля, поэтому здесь нет ни
6
- // массивов, ни словарей: метки колонок и пределы разложены по отдельным
7
- // скалярным полям. Доски живут в хранилище задач, а не в настройках.
5
+ // Client settings API writes scalar values only; complex options are decomposed
6
+ // into individual scalar settings.
8
7
 
9
8
  /**
10
- * Порядок колонок повторяет реальный воркфлоу, а не абстрактное
11
- * «в работе → готово». Колонка `cleanup` существует потому, что задача не
12
- * завершена, пока ветка, worktree и локальная ветка не удалены и это не
13
- * записано в итоговом комментарии issue. Доска, отправляющая карточку в
14
- * `done` по merge, врёт ровно там, где копятся забытые ветки.
9
+ * Column order mirrors the actual workflow.
10
+ * `cleanup` column exists because a task is not done until remote branch,
11
+ * worktree, and local branch are cleaned up and recorded in issue comment.
15
12
  */
16
13
  export const COLUMN_ORDER = ['backlog', 'in-progress', 'review', 'deploy', 'cleanup', 'done']
17
14
 
18
15
  /**
19
- * Наборы колонок по виду доски.
16
+ * Column sets per board kind.
20
17
  *
21
- * `review`, `deploy` и `cleanup` осмысленны только там, где за задачей стоит
22
- * issue: у свободной заметки нет ни ветки, ни PR, и Gitea о ней ничего не
23
- * скажет. Три колонки, которые никогда не заполнятся, не «на будущее», а
24
- * помеха.
25
18
  *
26
- * Вид — свойство доски, заданное явно. Выводить его из содержимого нельзя:
27
- * доска, меняющая число колонок от того, что на неё положили, ведёт себя
28
- * непредсказуемо.
29
19
  */
30
20
  export const BOARD_KINDS = ['project', 'simple']
31
21
 
32
- /** Вид доски по умолчанию: неизвестный вид откатывается сюда, а не в пустоту. */
22
+ /** Default board kind fallback. */
33
23
  const DEFAULT_KIND = 'project'
34
24
 
35
25
  const COLUMNS_BY_KIND = {
@@ -37,23 +27,23 @@ const COLUMNS_BY_KIND = {
37
27
  simple: ['backlog', 'in-progress', 'review', 'done'],
38
28
  }
39
29
 
40
- /** Колонки доски. Неизвестный вид — проектная доска, а не пустой набор. */
30
+ /** Board columns. Unknown kind defaults to project board. */
41
31
  export function columnsOf(kind) {
42
32
  return COLUMNS_BY_KIND[kind] ?? COLUMNS_BY_KIND[DEFAULT_KIND]
43
33
  }
44
34
 
45
- /** Приведение вида к известному значению. */
35
+ /** Coerce board kind to supported value. */
46
36
  export function normalizeKind(kind) {
47
37
  return BOARD_KINDS.includes(kind) ? kind : DEFAULT_KIND
48
38
  }
49
39
 
50
- /** Заводимые при первом запуске доски. Их ровно две, и обе нужны сразу. */
40
+ /** Initial boards seeded on first run. */
51
41
  export const DEFAULT_BOARDS = [
52
42
  { id: 'main', title: 'Project Board', kind: 'project' },
53
43
  { id: 'simple', title: 'Simple Board', kind: 'simple' },
54
44
  ]
55
45
 
56
- /** Значения по умолчанию. Из них же собирается схема в `lib/index.js`. */
46
+ /** Default settings values used in schema. */
57
47
  export const CONFIG_DEFAULTS = {
58
48
  gitProvider: 'auto',
59
49
  giteaUrl: '',
@@ -88,7 +78,7 @@ export const CONFIG_DEFAULTS = {
88
78
  autoSyncSessions: false,
89
79
  }
90
80
 
91
- /** Пояснения к полям; повторно используются схемой и карточкой настроек. */
81
+ /** Field descriptions reused in schema and settings card. */
92
82
  export const CONFIG_HINTS = {
93
83
  gitProvider: 'Active Git forge: "auto" (detect by credentials), "gitea", or "github".',
94
84
  giteaUrl: 'Gitea or Forgejo instance URL, e.g. https://gitea.example.com. Empty disables import.',
@@ -123,22 +113,17 @@ export const CONFIG_HINTS = {
123
113
  autoSyncSessions: 'Automatically sync and attach live background agent sessions to cards.',
124
114
  }
125
115
 
126
- /** Поле настроек с пределом числа карточек; задан не у всех колонок. */
116
+ /** Setting key for column WIP limit; not all columns have limits. */
127
117
  const WIP_FIELD = {
128
118
  'in-progress': 'wipInProgress',
129
119
  'review': 'wipReview',
130
120
  }
131
121
 
132
122
  /**
133
- * Фактический корень проектов: настройка либо рабочая папка харнесса.
134
- *
135
- * Единственное место, где считается «что будет, если корень не задан».
136
- * Браузер показывает это значение в окне запуска, чтобы сессия не
137
- * поднималась молча в неожиданном каталоге.
138
- *
139
- * @param {object} config эффективные настройки
140
- * @param {() => string} [cwdOf] рабочая папка (в тестах подменяется)
141
- * @returns {{path: string, set: boolean}} путь и признак «задан настройкой»
123
+ * Effective project root: configured value or harness working directory.
124
+ * @param {object} config effective configuration
125
+ * @param {() => string} [cwdOf] working directory getter
126
+ * @returns {{path: string, set: boolean}} path and whether explicitly configured
142
127
  */
143
128
  export function rootOf(config, cwdOf = () => process.cwd()) {
144
129
  const named = String(config?.defaultProjectRoot ?? '').trim()
@@ -146,11 +131,9 @@ export function rootOf(config, cwdOf = () => process.cwd()) {
146
131
  }
147
132
 
148
133
  /**
149
- * Наложить значения по умолчанию на сырые настройки. Поля неверного типа
150
- * отбрасываются в пользу умолчания: настройки приходят из файла и из
151
- * браузера, и одно испорченное поле не должно ронять плагин целиком.
152
- * @param {object} [raw] сырые значения
153
- * @returns {object} полный набор скалярных полей
134
+ * Apply defaults to raw settings. Invalid types fallback to default.
135
+ * @param {object} [raw] raw configuration
136
+ * @returns {object} validated scalar settings
154
137
  */
155
138
  export function withDefaults(raw) {
156
139
  const out = {}
@@ -162,12 +145,10 @@ export function withDefaults(raw) {
162
145
  }
163
146
 
164
147
  /**
165
- * Предел числа карточек в колонке. Ноль и отсутствие поля означают «без
166
- * предела» и одинаково дают `undefined`, чтобы вызывающему не приходилось
167
- * различать два способа сказать одно и то же.
168
- * @param {object} config эффективные настройки
169
- * @param {string} column идентификатор колонки
170
- * @returns {number|undefined} предел либо undefined
148
+ * Column card limit. Zero and absent setting both return undefined.
149
+ * @param {object} config effective configuration
150
+ * @param {string} column column identifier
151
+ * @returns {number|undefined} limit or undefined
171
152
  */
172
153
  export function wipLimitField(config, column) {
173
154
  const field = WIP_FIELD[column]
@@ -178,14 +159,8 @@ export function wipLimitField(config, column) {
178
159
  }
179
160
 
180
161
  /**
181
- * Свои названия колонок из настройки.
182
- *
183
- * Пары `идентификатор=Название` через запятую. Одно поле на все колонки, а не
184
- * шесть отдельных: переименовывают их раз в жизни, а карточку настроек шесть
185
- * полей засорили бы навсегда.
186
- *
187
- * Название неизвестной колонки не отбрасываем молча — она может появиться
188
- * позже, и тогда настройка сработает сама.
162
+ * Custom column labels from settings.
163
+ * Format: comma-separated `id=Name` pairs.
189
164
  */
190
165
  export function columnNamesOf(config) {
191
166
  const out = {}
package/lib/cron.js CHANGED
@@ -1,12 +1,12 @@
1
- // dsh-kanban: парсер 5-позиционного cron и вычисление следующего запуска.
2
- // Без внешних зависимостей: чистые функции над датами и числами.
1
+ // dsh-kanban: 5-position cron parser and next run calculator.
2
+ // Zero dependencies: pure functions over dates and numbers.
3
3
 
4
4
  const FIELD_RANGES = [
5
- [0, 59], // минуты
6
- [0, 23], // часы
7
- [1, 31], // дни месяца
8
- [1, 12], // месяцы
9
- [0, 7], // дни недели (0 и 7 — воскресенье)
5
+ [0, 59], // minutes
6
+ [0, 23], // hours
7
+ [1, 31], // days of month
8
+ [1, 12], // months
9
+ [0, 7], // days of week (0 and 7 are Sunday)
10
10
  ]
11
11
 
12
12
  function parseField(token, min, max, targetSet) {
@@ -62,9 +62,9 @@ function parseField(token, min, max, targetSet) {
62
62
  }
63
63
 
64
64
  /**
65
- * Разобрать 5-позиционное выражение cron: "минута час день месяц день_недели".
65
+ * Parse 5-position cron expression: "minute hour day month day_of_week".
66
66
  * @param {string} expr
67
- * @returns {object|null} распарсенное расписание или null, если выражение невалидно
67
+ * @returns {object|null} parsed schedule or null if expression is invalid
68
68
  */
69
69
  export function parseCron(expr) {
70
70
  if (typeof expr !== 'string') return null
@@ -94,17 +94,17 @@ export function parseCron(expr) {
94
94
  }
95
95
 
96
96
  /**
97
- * Вычислить время следующего запуска (в миллисекундах epoch) строго после `fromMs`.
98
- * Поиск ограничен 5 годами вперед, чтобы избежать бесконечного цикла.
97
+ * Calculate next run timestamp (epoch ms) strictly after `fromMs`.
98
+ * Search capped at 5 years forward to prevent infinite loops.
99
99
  * @param {string|object} exprOrSchedule
100
100
  * @param {number} [fromMs]
101
- * @returns {number|undefined} время следующего запуска или undefined
101
+ * @returns {number|undefined} next run timestamp or undefined
102
102
  */
103
103
  export function computeNextRun(exprOrSchedule, fromMs = Date.now()) {
104
104
  const schedule = typeof exprOrSchedule === 'string' ? parseCron(exprOrSchedule) : exprOrSchedule
105
105
  if (!schedule) return undefined
106
106
 
107
- // Стартуем со следующей целой минуты
107
+ // Start at next whole minute
108
108
  const current = new Date(fromMs)
109
109
  current.setSeconds(0, 0)
110
110
  current.setMinutes(current.getMinutes() + 1)
package/lib/events.js CHANGED
@@ -1,4 +1,4 @@
1
- // Broadcaster для Server-Sent Events (SSE) (#222).
1
+ // Broadcaster for Server-Sent Events (SSE) (#222).
2
2
 
3
3
  export class KanbanEventHub {
4
4
  constructor(options = {}) {