@goodandready/dsh-kanban 0.2.18 → 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/board-tool.js +18 -48
- package/lib/client.js +85 -176
- package/lib/commands.js +21 -38
- package/lib/config.js +29 -54
- package/lib/cron.js +13 -13
- package/lib/events.js +1 -1
- package/lib/filters.js +58 -82
- package/lib/git-provider.js +23 -23
- package/lib/gitea.js +51 -109
- package/lib/github.js +27 -31
- package/lib/import.js +46 -67
- package/lib/index.js +88 -137
- package/lib/intake.js +23 -31
- package/lib/launcher.js +100 -167
- package/lib/lifecycle.js +11 -17
- package/lib/markdown.js +16 -23
- package/lib/metrics.js +23 -25
- package/lib/order.js +25 -28
- package/lib/outbound.js +25 -26
- package/lib/plan.js +23 -45
- package/lib/power.js +2 -2
- package/lib/progress-dump.js +17 -17
- package/lib/routes.js +86 -145
- package/lib/store.js +59 -78
- package/lib/sync.js +56 -98
- package/lib/templates.js +29 -29
- package/lib/time.js +8 -17
- package/lib/transitions.js +36 -57
- package/lib/webhook.js +16 -23
- package/lib/workflow-bridge.js +58 -58
- package/lib/worktree.js +58 -58
- package/package.json +1 -1
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
|
-
/**
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
12
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* @
|
|
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:
|
|
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], //
|
|
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
|
-
*
|
|
65
|
+
* Parse 5-position cron expression: "minute hour day month day_of_week".
|
|
66
66
|
* @param {string} expr
|
|
67
|
-
* @returns {object|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
|
-
*
|
|
98
|
-
*
|
|
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}
|
|
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