@goodandready/dsh-kanban 0.2.2 → 0.2.8
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/README.md +24 -0
- package/{docs/README.ru.md → README.ru.md} +10 -0
- package/{docs/README.zh.md → README.zh.md} +21 -0
- package/lib/board-tool.js +151 -0
- package/lib/client.js +1020 -763
- package/lib/commands.js +6 -11
- package/lib/config.js +31 -29
- package/lib/events.js +2 -2
- package/lib/filters.js +2 -2
- package/lib/gitea.js +15 -0
- package/lib/index.js +171 -142
- package/lib/power.js +1 -1
- package/lib/progress-dump.js +3 -3
- package/lib/routes.js +403 -5
- package/lib/store.js +59 -9
- package/lib/sync.js +26 -3
- package/lib/task-service.js +56 -0
- package/lib/updater.js +338 -0
- package/lib/worktree.js +1 -1
- package/package.json +7 -3
- package/docs/design/DESIGN.md +0 -94
- package/docs/plans/2026-08-26-dsh-kanban-0.1.md +0 -169
- package/docs/plans/2026-09-07-quality-acceptance-gateway.md +0 -25
- package/docs/plans/2026-09-08-concurrency-safety.md +0 -8
- package/docs/plans/2026-09-08-diff-viewer-merge.md +0 -7
- package/docs/plans/2026-09-08-session-worktree-stability.md +0 -33
- package/docs/plans/2026-09-10-issue-238-settings-scope.md +0 -13
package/README.md
CHANGED
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
|
@@ -286,3 +296,17 @@ deregistration.
|
|
|
286
296
|
## 📄 License
|
|
287
297
|
|
|
288
298
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
299
|
+
|
|
300
|
+
## Public composition service
|
|
301
|
+
|
|
302
|
+
The server half exposes an optional dshKanban composition service. Its
|
|
303
|
+
createTask({ title, body, board, column, labels, owner, repo, issueNumber,
|
|
304
|
+
issueUrl, externalRef }) method validates the target board and column, stores
|
|
305
|
+
the task through the existing SQLite-backed path, and returns { ok, task,
|
|
306
|
+
taskId, alreadyExists }. A repeated externalRef returns the original card,
|
|
307
|
+
including after a restart.
|
|
308
|
+
|
|
309
|
+
This service only creates a card. It does not launch an agent, create a branch,
|
|
310
|
+
create a worktree, or accept credentials from a consumer. The canonical
|
|
311
|
+
consumer contract for task provisioning is
|
|
312
|
+
dsh-drives.task-provision.v1.
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
|
+
<table align="center">
|
|
25
|
+
<tr>
|
|
26
|
+
<td align="center">
|
|
27
|
+
⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
|
|
28
|
+
<br><br>
|
|
29
|
+
🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
|
|
30
|
+
</td>
|
|
31
|
+
</tr>
|
|
32
|
+
</table>
|
|
33
|
+
|
|
24
34
|
</div>
|
|
25
35
|
|
|
26
36
|
---
|
|
@@ -171,3 +181,14 @@ npm test
|
|
|
171
181
|
## 📄 开源许可证
|
|
172
182
|
|
|
173
183
|
MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
184
|
+
|
|
185
|
+
## 公共组合服务
|
|
186
|
+
|
|
187
|
+
服务器端暴露可选的 dshKanban 组合服务。其
|
|
188
|
+
createTask({ title, body, board, column, labels, owner, repo, issueNumber,
|
|
189
|
+
issueUrl, externalRef }) 方法校验目标看板和列,通过现有的 SQLite 存储路径
|
|
190
|
+
保存任务,并返回 { ok, task, taskId, alreadyExists }。重复的 externalRef
|
|
191
|
+
会返回原有卡片,重启后仍然有效。
|
|
192
|
+
|
|
193
|
+
该服务只创建卡片,不启动代理、不创建分支或 worktree,也不接受调用方传入的
|
|
194
|
+
凭据。任务供应的规范消费者契约是 dsh-drives.task-provision.v1。
|
package/lib/board-tool.js
CHANGED
|
@@ -367,3 +367,154 @@ export function boardCommentAddDefinition({ store }) {
|
|
|
367
367
|
},
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
+
|
|
371
|
+
/** Определение board_decompose (#248) */
|
|
372
|
+
export function boardDecomposeDefinition({ store, gitea }) {
|
|
373
|
+
return {
|
|
374
|
+
name: 'board_decompose',
|
|
375
|
+
description: 'Decompose a task into child subtasks, converting the task into an Epic with linked progress tracking.',
|
|
376
|
+
parameters: {
|
|
377
|
+
subtasks: {
|
|
378
|
+
type: 'array',
|
|
379
|
+
items: { type: 'string' },
|
|
380
|
+
description: 'List of child subtask titles to create.',
|
|
381
|
+
required: true,
|
|
382
|
+
},
|
|
383
|
+
create_gitea_issues: {
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
description: 'Whether to create linked Gitea issues for each subtask if repository is linked.',
|
|
386
|
+
},
|
|
387
|
+
task: {
|
|
388
|
+
type: 'string',
|
|
389
|
+
description: 'Task reference (`repo#number` or card id). Required if this session carries multiple tasks.',
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
output: {
|
|
393
|
+
schema: { type: 'string', description: 'Status and list of created subtasks.' },
|
|
394
|
+
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
395
|
+
},
|
|
396
|
+
async execute({ subtasks, create_gitea_issues, task: ref }, exec) {
|
|
397
|
+
const picked = pickTask(store, exec, ref)
|
|
398
|
+
if (picked.error !== undefined) return picked.error
|
|
399
|
+
const task = picked.task
|
|
400
|
+
if (!Array.isArray(subtasks) || subtasks.length === 0) {
|
|
401
|
+
return 'At least one subtask title is required in `subtasks`.'
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
store.updateTask(task.id, { isEpic: true })
|
|
405
|
+
const created = []
|
|
406
|
+
const giteaClient = typeof gitea === 'function' ? gitea() : gitea
|
|
407
|
+
const createGitea = create_gitea_issues === true && Boolean(task.owner && task.repo && giteaClient)
|
|
408
|
+
|
|
409
|
+
for (const item of subtasks) {
|
|
410
|
+
const title = (typeof item === 'string' ? item : item?.title || '').trim()
|
|
411
|
+
if (!title) continue
|
|
412
|
+
const body = typeof item === 'object' && item?.body ? String(item.body).trim() : ''
|
|
413
|
+
let issueNumber = null
|
|
414
|
+
let issueUrl = null
|
|
415
|
+
|
|
416
|
+
if (createGitea) {
|
|
417
|
+
try {
|
|
418
|
+
const issueBody = body ? `${body}\n\nParent epic: #${task.issueNumber || task.id}` : `Parent epic: #${task.issueNumber || task.id}`
|
|
419
|
+
const giteaIssue = await giteaClient.createIssue({
|
|
420
|
+
owner: task.owner,
|
|
421
|
+
repo: task.repo,
|
|
422
|
+
title,
|
|
423
|
+
body: issueBody,
|
|
424
|
+
})
|
|
425
|
+
if (giteaIssue?.number) {
|
|
426
|
+
issueNumber = giteaIssue.number
|
|
427
|
+
issueUrl = giteaIssue.html_url || null
|
|
428
|
+
}
|
|
429
|
+
} catch { /* gitea issue creation best-effort */ }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const sub = store.createTask({
|
|
433
|
+
board: task.board || 'main',
|
|
434
|
+
column: 'backlog',
|
|
435
|
+
title,
|
|
436
|
+
body,
|
|
437
|
+
parentId: task.id,
|
|
438
|
+
owner: task.owner || null,
|
|
439
|
+
repo: task.repo || null,
|
|
440
|
+
issueNumber,
|
|
441
|
+
issueUrl,
|
|
442
|
+
workspaceId: task.workspaceId || '',
|
|
443
|
+
})
|
|
444
|
+
created.push(sub)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
store.addTransition({
|
|
448
|
+
taskId: task.id,
|
|
449
|
+
fromCol: task.column,
|
|
450
|
+
toCol: task.column,
|
|
451
|
+
source: 'tool',
|
|
452
|
+
detail: `agent decomposed task into ${created.length} subtasks`,
|
|
453
|
+
})
|
|
454
|
+
|
|
455
|
+
return `Decomposed task "${task.title}" into ${created.length} subtasks:\n`
|
|
456
|
+
+ created.map((s, i) => `${i + 1}. [${s.id.slice(0, 8)}] ${s.title}${s.issueNumber ? ` (#${s.issueNumber})` : ''}`).join('\n')
|
|
457
|
+
},
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Определение board_checklist_item (#248) */
|
|
462
|
+
export function boardChecklistItemDefinition({ store }) {
|
|
463
|
+
return {
|
|
464
|
+
name: 'board_checklist_item',
|
|
465
|
+
description: 'Toggle or check a specific DoD checklist item by text or index without replacing the entire list.',
|
|
466
|
+
parameters: {
|
|
467
|
+
text: {
|
|
468
|
+
type: 'string',
|
|
469
|
+
description: 'Text substring matching the checklist item to toggle.',
|
|
470
|
+
},
|
|
471
|
+
index: {
|
|
472
|
+
type: 'number',
|
|
473
|
+
description: 'Zero-based index of the checklist item to toggle.',
|
|
474
|
+
},
|
|
475
|
+
done: {
|
|
476
|
+
type: 'boolean',
|
|
477
|
+
description: 'Explicit completion state (true to mark done, false to unmark). If omitted, toggles state.',
|
|
478
|
+
},
|
|
479
|
+
task: {
|
|
480
|
+
type: 'string',
|
|
481
|
+
description: 'Task reference (`repo#number` or card id). Required if this session carries multiple tasks.',
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
output: {
|
|
485
|
+
schema: { type: 'string', description: 'Status confirmation.' },
|
|
486
|
+
render: (_args, value) => [{ type: 'text', text: String(value) }],
|
|
487
|
+
},
|
|
488
|
+
async execute({ text, index, done, task: ref }, exec) {
|
|
489
|
+
const picked = pickTask(store, exec, ref)
|
|
490
|
+
if (picked.error !== undefined) return picked.error
|
|
491
|
+
const task = picked.task
|
|
492
|
+
const checklist = Array.isArray(task.checklist) ? [...task.checklist] : []
|
|
493
|
+
let targetIndex = -1
|
|
494
|
+
|
|
495
|
+
if (typeof index === 'number' && index >= 0 && index < checklist.length) {
|
|
496
|
+
targetIndex = index
|
|
497
|
+
} else if (typeof text === 'string' && text.trim()) {
|
|
498
|
+
const q = text.trim().toLowerCase()
|
|
499
|
+
targetIndex = checklist.findIndex((item) => {
|
|
500
|
+
const t = typeof item === 'string' ? item : item?.text
|
|
501
|
+
return typeof t === 'string' && t.toLowerCase().includes(q)
|
|
502
|
+
})
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (targetIndex === -1) {
|
|
506
|
+
return `Checklist item matching index=${index} / text="${text}" not found.`
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const item = checklist[targetIndex]
|
|
510
|
+
const oldDone = typeof item === 'object' && item !== null ? Boolean(item.done) : false
|
|
511
|
+
const newDone = done !== undefined ? Boolean(done) : !oldDone
|
|
512
|
+
const title = typeof item === 'string' ? item : (item?.text || '')
|
|
513
|
+
checklist[targetIndex] = { text: title, done: newDone }
|
|
514
|
+
|
|
515
|
+
store.updateChecklist(task.id, checklist)
|
|
516
|
+
return `Checklist item #${targetIndex + 1} "${title}" marked as ${newDone ? 'DONE' : 'PENDING'}.`
|
|
517
|
+
},
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|