@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/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 +66 -132
- package/lib/intake.js +18 -26
- 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 -77
- package/lib/sync.js +56 -98
- package/lib/time.js +8 -17
- package/lib/transitions.js +36 -57
- package/lib/webhook.js +16 -23
- package/lib/workflow-bridge.js +57 -57
- package/lib/worktree.js +58 -58
- package/package.json +1 -1
package/lib/import.js
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Transfer Gitea issue to a board task.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// её сюда нельзя.
|
|
3
|
+
// Synchronization in 0.1 is one-way: Gitea -> kanban, at import time and on
|
|
4
|
+
// manual refresh. Two-way synchronization is handled separately.
|
|
6
5
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Gitea client is passed as a parameter — configured by `lib/gitea.js`. Here
|
|
7
|
+
// we only map fields and handle cases where access is not configured.
|
|
9
8
|
|
|
10
9
|
import { safeSegment } from './gitea.js'
|
|
11
10
|
import { colorsOfIssue } from './filters.js'
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
13
|
+
* When the issue was created in Gitea.
|
|
15
14
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Without this, the card would show the import timestamp: a year-old issue
|
|
16
|
+
* would appear created today, losing chronological context.
|
|
18
17
|
*/
|
|
19
18
|
export function issueBornAt(issue) {
|
|
20
19
|
const at = Date.parse(issue?.created_at ?? '')
|
|
@@ -22,11 +21,10 @@ export function issueBornAt(issue) {
|
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
24
|
+
* Issue author.
|
|
26
25
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* показывать то одно, то другое.
|
|
26
|
+
* Uses `login`: matches what Gitea displays under the issue and search queries.
|
|
27
|
+
* Full name is not set for all users, so using it would cause inconsistencies.
|
|
30
28
|
*/
|
|
31
29
|
export function issueAuthor(issue) {
|
|
32
30
|
const login = issue?.user?.login
|
|
@@ -34,12 +32,11 @@ export function issueAuthor(issue) {
|
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
|
-
*
|
|
35
|
+
* Issue assignee.
|
|
38
36
|
*
|
|
39
|
-
* Gitea
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* никто».
|
|
37
|
+
* Gitea returns assignees in two ways: single `assignee` and list `assignees`.
|
|
38
|
+
* Pick the first from the list if single is absent: board tracks a single
|
|
39
|
+
* responsible assignee.
|
|
43
40
|
*/
|
|
44
41
|
export function issueAssignee(issue) {
|
|
45
42
|
const one = issue?.assignee?.login
|
|
@@ -50,10 +47,10 @@ export function issueAssignee(issue) {
|
|
|
50
47
|
}
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
|
-
*
|
|
50
|
+
* Milestone the issue belongs to.
|
|
54
51
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
52
|
+
* Releases are tracked via Gitea milestones; board stores milestone title
|
|
53
|
+
* so release progress can be monitored directly on cards.
|
|
57
54
|
*/
|
|
58
55
|
export function issueMilestone(issue) {
|
|
59
56
|
const title = issue?.milestone?.title
|
|
@@ -61,13 +58,13 @@ export function issueMilestone(issue) {
|
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/**
|
|
64
|
-
*
|
|
61
|
+
* Map task fields from an issue.
|
|
65
62
|
*
|
|
66
|
-
* `owner`
|
|
67
|
-
*
|
|
63
|
+
* `owner` and `repo` come from request context, not the issue payload: Gitea API
|
|
64
|
+
* does not return them in all response shapes.
|
|
68
65
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
66
|
+
* Fields `branch`, `worktree`, `sessionId`, `model` are NOT populated here:
|
|
67
|
+
* they are assigned later when work begins. The plugin does not create branches on import.
|
|
71
68
|
*/
|
|
72
69
|
export function issueToTask(issue, { board = 'main', column = 'backlog', owner, repo, provider }) {
|
|
73
70
|
const p = provider || (typeof issue?.html_url === 'string' && issue.html_url.includes('github.com') ? 'github' : 'gitea')
|
|
@@ -80,8 +77,7 @@ export function issueToTask(issue, { board = 'main', column = 'backlog', owner,
|
|
|
80
77
|
labels: Array.isArray(issue?.labels)
|
|
81
78
|
? issue.labels.map((l) => (typeof l === 'string' ? l : l?.name)).filter((n) => typeof n === 'string')
|
|
82
79
|
: [],
|
|
83
|
-
//
|
|
84
|
-
// сверка, чип и поиск.
|
|
80
|
+
// Colors are kept alongside names for filters, reconciler, chip, and search.
|
|
85
81
|
labelColors: colorsOfIssue(issue?.labels),
|
|
86
82
|
owner,
|
|
87
83
|
repo,
|
|
@@ -95,13 +91,10 @@ export function issueToTask(issue, { board = 'main', column = 'backlog', owner,
|
|
|
95
91
|
}
|
|
96
92
|
|
|
97
93
|
/**
|
|
98
|
-
*
|
|
94
|
+
* Mark issues that already exist on the board.
|
|
99
95
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* Помеченные не прячутся, а приглушаются: задачу иногда заводят второй раз
|
|
104
|
-
* осознанно.
|
|
96
|
+
* Compared by owner-repo-number tuple to avoid colliding with issue #1 in another repo.
|
|
97
|
+
* Existing issues are dimmed rather than hidden, as re-importing may be intentional.
|
|
105
98
|
*/
|
|
106
99
|
export function markImported(issues, existing, { owner, repo }) {
|
|
107
100
|
const seen = new Set(
|
|
@@ -113,11 +106,10 @@ export function markImported(issues, existing, { owner, repo }) {
|
|
|
113
106
|
}
|
|
114
107
|
|
|
115
108
|
/**
|
|
116
|
-
*
|
|
109
|
+
* Fields to overwrite when updating a task from an issue.
|
|
117
110
|
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* работу, которая уже идёт.
|
|
111
|
+
* Only fields originating from Gitea are updated. Local fields (column,
|
|
112
|
+
* position, session, model) are preserved to avoid resetting active work.
|
|
121
113
|
*/
|
|
122
114
|
export function refreshPatch(task, issue) {
|
|
123
115
|
const patch = {}
|
|
@@ -134,24 +126,20 @@ export function refreshPatch(task, issue) {
|
|
|
134
126
|
if (born !== undefined) patch.createdAt = born
|
|
135
127
|
const author = issueAuthor(issue)
|
|
136
128
|
if (author !== '') patch.author = author
|
|
137
|
-
//
|
|
138
|
-
// с именем: иначе доска показывала бы вчерашнего владельца задачи, с которой
|
|
139
|
-
// его уже сняли.
|
|
129
|
+
// Explicit empty assignee when cleared in Gitea so board does not retain stale assignee.
|
|
140
130
|
if (issue?.assignee !== undefined || Array.isArray(issue?.assignees)) {
|
|
141
131
|
patch.assignee = issueAssignee(issue)
|
|
142
132
|
}
|
|
143
|
-
//
|
|
144
|
-
// тоже новость, а не отсутствие новостей.
|
|
133
|
+
// Milestone removal is also explicitly recorded.
|
|
145
134
|
if ('milestone' in (issue ?? {})) patch.milestone = issueMilestone(issue)
|
|
146
135
|
return patch
|
|
147
136
|
}
|
|
148
137
|
|
|
149
138
|
/**
|
|
150
|
-
*
|
|
151
|
-
* разные беды, и человеку они говорят разное.
|
|
139
|
+
* Check if Gitea client is configured and ready.
|
|
152
140
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
141
|
+
* Missing client vs unconfigured access are distinct error states.
|
|
142
|
+
* Async check includes token credential verification.
|
|
155
143
|
*/
|
|
156
144
|
export async function giteaState(gitea) {
|
|
157
145
|
if (gitea === undefined || gitea === null) return 'absent'
|
|
@@ -163,9 +151,7 @@ export async function giteaState(gitea) {
|
|
|
163
151
|
}
|
|
164
152
|
}
|
|
165
153
|
|
|
166
|
-
/**
|
|
167
|
-
* Список issue для диалога импорта: открытые, уже импортированные помечены.
|
|
168
|
-
*/
|
|
154
|
+
/** List open issues for import dialog, marking already-imported ones. */
|
|
169
155
|
export async function listImportable({ gitea, store, owner, repo, board = 'main' }) {
|
|
170
156
|
const state = await giteaState(gitea)
|
|
171
157
|
if (state !== 'ready') return { error: `gitea-${state}`, status: 409 }
|
|
@@ -174,20 +160,19 @@ export async function listImportable({ gitea, store, owner, repo, board = 'main'
|
|
|
174
160
|
return { issues: markImported(issues, existing, { owner, repo }) }
|
|
175
161
|
}
|
|
176
162
|
|
|
177
|
-
/**
|
|
163
|
+
/** Create a task from an issue. */
|
|
178
164
|
export async function importIssue({ gitea, store, owner, repo, issueNumber, board = 'main', column = 'backlog' }) {
|
|
179
165
|
const state = await giteaState(gitea)
|
|
180
166
|
if (state !== 'ready') return { error: `gitea-${state}`, status: 409 }
|
|
181
167
|
if (typeof issueNumber !== 'number') return { error: 'issue-number-required', status: 400 }
|
|
182
|
-
//
|
|
183
|
-
// веткой и PR в набор колонок, где их некуда показать.
|
|
168
|
+
// Simple board is for non-issue tasks. Importing here would bring branches/PRs into columns unable to display them.
|
|
184
169
|
if (store.boardKind(board) !== 'project') return { error: 'board-not-for-issues', status: 400 }
|
|
185
170
|
const issue = await gitea.getIssue({ owner, repo, index: issueNumber })
|
|
186
171
|
if (issue === undefined || issue === null) return { error: 'issue-not-found', status: 404 }
|
|
187
172
|
return { task: store.createTask(issueToTask(issue, { board, column, owner, repo })) }
|
|
188
173
|
}
|
|
189
174
|
|
|
190
|
-
/**
|
|
175
|
+
/** Re-read issue and update task, preserving local state. */
|
|
191
176
|
export async function refreshTask({ gitea, store, id }) {
|
|
192
177
|
const state = await giteaState(gitea)
|
|
193
178
|
if (state !== 'ready') return { error: `gitea-${state}`, status: 409 }
|
|
@@ -199,7 +184,7 @@ export async function refreshTask({ gitea, store, id }) {
|
|
|
199
184
|
return { task: store.updateTask(id, refreshPatch(task, issue)) }
|
|
200
185
|
}
|
|
201
186
|
|
|
202
|
-
/**
|
|
187
|
+
/** Search repositories for import dialog. */
|
|
203
188
|
export async function searchRepos({ gitea, query, limit = 20 }) {
|
|
204
189
|
const state = await giteaState(gitea)
|
|
205
190
|
if (state !== 'ready') return { error: `gitea-${state}`, status: 409 }
|
|
@@ -207,16 +192,11 @@ export async function searchRepos({ gitea, query, limit = 20 }) {
|
|
|
207
192
|
}
|
|
208
193
|
|
|
209
194
|
/**
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* Репозиторий либо выбирается из существующих, либо заводится по имени.
|
|
213
|
-
* Заводит его ЧЕЛОВЕК кнопкой: инструмента у агента для этого нет — он
|
|
214
|
-
* пользуется `gitea_issue_create` из соседнего плагина, а карточка приезжает
|
|
215
|
-
* подхватом.
|
|
195
|
+
* Create a project task: Gitea issue plus linked board card.
|
|
216
196
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
197
|
+
* Repository is selected from existing list or created by name.
|
|
198
|
+
* Order of operations: repo first, then issue, then board card.
|
|
199
|
+
* Failure midway avoids leaving orphaned cards.
|
|
220
200
|
*/
|
|
221
201
|
export async function createProjectTask({
|
|
222
202
|
gitea, store, owner, repo, newRepo, title, body, board = 'main',
|
|
@@ -231,7 +211,7 @@ export async function createProjectTask({
|
|
|
231
211
|
if (targetRepo === '') {
|
|
232
212
|
const wanted = typeof newRepo === 'string' ? newRepo.trim() : ''
|
|
233
213
|
if (wanted === '') return { error: 'repo-required', status: 400 }
|
|
234
|
-
//
|
|
214
|
+
// Validate repository name before API call.
|
|
235
215
|
if (safeSegment(wanted) === undefined) return { error: 'bad-repo-name', status: 400 }
|
|
236
216
|
try {
|
|
237
217
|
await gitea.createRepo({ owner, name: wanted })
|
|
@@ -245,8 +225,7 @@ export async function createProjectTask({
|
|
|
245
225
|
try {
|
|
246
226
|
issue = await gitea.createIssue({ owner, repo: targetRepo, title: name, body })
|
|
247
227
|
} catch (error) {
|
|
248
|
-
//
|
|
249
|
-
// добра из-за своей неудачи хуже лишнего пустого репозитория.
|
|
228
|
+
// Repository may have already been created; avoid deleting on issue creation failure.
|
|
250
229
|
return { error: 'issue-not-created', status: 502, detail: String(error?.message ?? error) }
|
|
251
230
|
}
|
|
252
231
|
|