@alpacachen/dsh-kanban 1.3.7 → 1.5.0
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 +4 -3
- package/README.zh.md +4 -3
- package/index.js +128 -28
- package/lib/client.js +85 -15
- package/package.json +20 -4
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ You can take over at any time—drag a card, refine a note, reorder a list—and
|
|
|
27
27
|
|
|
28
28
|
| 💬 Plan naturally | 🧭 Stay in control | ⚡ Start instantly |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| Manage the board with
|
|
30
|
+
| Manage the board with 15 built-in `kanban_*` tools. | Edit, comment, drag, prioritize, label, and filter from the UI. | Install the prebuilt plugin—no frontend setup required. |
|
|
31
31
|
| **🗂️ Keep work separate** | **🌓 Feel at home** | **🤝 Share one source of truth** |
|
|
32
32
|
| Every DSH workspace gets its own board. | Follows your DSH language, theme, and dark mode. | You and the agent always see the same tasks and status. |
|
|
33
33
|
|
|
@@ -67,8 +67,9 @@ The cards appear directly on your board. Continue in conversation, or switch to
|
|
|
67
67
|
|
|
68
68
|
- Create, edit, delete, and drag cards between lists
|
|
69
69
|
- Add notes, labels, and P0 / P1 / P2 priorities
|
|
70
|
+
- Exchange persistent card comments with the agent and see comment counts at a glance
|
|
70
71
|
- Open a card's activity timeline to see when it changed, what changed, and whether you or the agent made the change
|
|
71
|
-
- Track card creation, moves, title and note edits, label changes, and priority changes
|
|
72
|
+
- Track card creation, comments, moves, title and note edits, label changes, and priority changes
|
|
72
73
|
- Send any card to the agent in your current conversation or a new one
|
|
73
74
|
- Create, rename, reorder, and remove workflow lists
|
|
74
75
|
- Create color-coded labels for fast visual scanning
|
|
@@ -85,7 +86,7 @@ The plugin automatically gives the agent these tools in every workspace:
|
|
|
85
86
|
| Area | Tools |
|
|
86
87
|
| --- | --- |
|
|
87
88
|
| **Board** | `kanban_get` |
|
|
88
|
-
| **Cards** | `kanban_get_card`, `kanban_add_card`, `kanban_update_card`, `kanban_delete_card`, `kanban_move_card` |
|
|
89
|
+
| **Cards** | `kanban_get_card`, `kanban_add_comment`, `kanban_add_card`, `kanban_update_card`, `kanban_delete_card`, `kanban_move_card` |
|
|
89
90
|
| **Lists** | `kanban_add_column`, `kanban_rename_column`, `kanban_delete_column`, `kanban_move_column` |
|
|
90
91
|
| **Labels** | `kanban_get_label`, `kanban_add_label`, `kanban_update_label`, `kanban_delete_label` |
|
|
91
92
|
|
package/README.zh.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
| 💬 自然对话 | 🧭 随时掌控 | ⚡ 开箱即用 |
|
|
29
29
|
| --- | --- | --- |
|
|
30
|
-
| 通过
|
|
30
|
+
| 通过 15 个 `kanban_*` 工具直接管理任务。 | 在界面中编辑、评论、拖拽、标记和筛选卡片。 | 浏览器端已预构建,无需配置前端环境。 |
|
|
31
31
|
| **🗂️ 项目隔离** | **🌓 原生体验** | **🤝 实时协作** |
|
|
32
32
|
| 每个 DSH 工作区都有自己的独立看板。 | 自动跟随 DSH 的语言、主题与暗色模式。 | 你做的调整,Agent 立即可见;反之亦然。 |
|
|
33
33
|
|
|
@@ -67,8 +67,9 @@ dsh --profile web --dump-config
|
|
|
67
67
|
|
|
68
68
|
- 创建、编辑、删除卡片,并将卡片拖到其他列表
|
|
69
69
|
- 为卡片补充备注、标签和 P0 / P1 / P2 优先级
|
|
70
|
+
- 与 Agent 在卡片下持续评论,并在看板上快速查看评论数量
|
|
70
71
|
- 打开卡片活动时间线,查看变更时间、具体内容,以及操作来自你还是 Agent
|
|
71
|
-
-
|
|
72
|
+
- 追踪卡片创建、评论、移动、标题与备注编辑、标签变化和优先级变化
|
|
72
73
|
- 从任意卡片发起对话,在当前会话或新会话中让 Agent 接着处理
|
|
73
74
|
- 创建、重命名、排序和删除工作流列表
|
|
74
75
|
- 创建彩色标签,让不同任务一眼可辨
|
|
@@ -85,7 +86,7 @@ dsh --profile web --dump-config
|
|
|
85
86
|
| 范围 | 工具 |
|
|
86
87
|
| --- | --- |
|
|
87
88
|
| **看板** | `kanban_get` |
|
|
88
|
-
| **卡片** | `kanban_get_card`、`kanban_add_card`、`kanban_update_card`、`kanban_delete_card`、`kanban_move_card` |
|
|
89
|
+
| **卡片** | `kanban_get_card`、`kanban_add_comment`、`kanban_add_card`、`kanban_update_card`、`kanban_delete_card`、`kanban_move_card` |
|
|
89
90
|
| **列表** | `kanban_add_column`、`kanban_rename_column`、`kanban_delete_column`、`kanban_move_column` |
|
|
90
91
|
| **标签** | `kanban_get_label`、`kanban_add_label`、`kanban_update_label`、`kanban_delete_label` |
|
|
91
92
|
|
package/index.js
CHANGED
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
* 职责:
|
|
8
8
|
* - 按工作区(项目)隔离:boards 以 workspaceId 为键,每个工作区一块独立看板
|
|
9
9
|
* - 磁盘持久化:经 ctx.fs 写入 <workspace.path>/.dsh-kanban.json
|
|
10
|
-
* - 模型工具:经 ctx.tools.register 注册
|
|
10
|
+
* - 模型工具:经 ctx.tools.register 注册 15 个 kanban_* 工具
|
|
11
11
|
* - 浏览器数据层:经 ctx.get('webServer') 注册 /api/kanban 前缀路由
|
|
12
12
|
*
|
|
13
13
|
* 数据模型(每工作区,磁盘文件带 schemaVersion):
|
|
14
|
-
* schemaVersion:
|
|
14
|
+
* schemaVersion: 3
|
|
15
15
|
* columns: [{ id, title }]
|
|
16
16
|
* labels: [{ name, color }] —— 标签与颜色绑定,name 为唯一键
|
|
17
|
-
* cards: [{ id, columnId, title, note, label, priority, createdAt, createdBy
|
|
17
|
+
* cards: [{ id, columnId, title, note, label, priority, createdAt, createdBy,
|
|
18
|
+
* comments: [{ id, content, source, createdAt }] }]
|
|
18
19
|
* activities: [{ id, ts, cardId, type, source, field?, from?, to?, meta? }] —— 追加式活动日志
|
|
19
20
|
*
|
|
20
21
|
* 数据安全:
|
|
@@ -44,12 +45,21 @@ export const inject = ['tools']
|
|
|
44
45
|
// 旧文件在首次打开时自动沿迁移链升级,升级前原文件先备份。
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
46
47
|
|
|
47
|
-
export const SCHEMA_VERSION =
|
|
48
|
+
export const SCHEMA_VERSION = 3
|
|
48
49
|
export const LEGACY_VERSION = 0
|
|
49
50
|
|
|
50
51
|
// 活动日志:追加式只读记录,随看板一起落盘;超过上限丢弃最旧事件,防止日志无界增长。
|
|
51
52
|
const ACTIVITY_LIMIT = 5000
|
|
52
53
|
|
|
54
|
+
// 输入长度上限:与工具 schema 描述、客户端编辑器(CardDialog)保持一致。
|
|
55
|
+
// 超长输入在写入前被截断,并通过看板 warnings 通道发出一次性警告(见 clampText),
|
|
56
|
+
// 因此 agent 与用户都能感知到内容被裁剪,而不是静默丢失。
|
|
57
|
+
export const TITLE_LIMIT = 120
|
|
58
|
+
export const NOTE_LIMIT = 2000
|
|
59
|
+
export const LABEL_LIMIT = 20
|
|
60
|
+
export const COLUMN_TITLE_LIMIT = 40
|
|
61
|
+
export const COMMENT_LIMIT = 2000
|
|
62
|
+
|
|
53
63
|
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
54
64
|
const strField = (v, fb) => (typeof v === 'string' && v ? v : fb)
|
|
55
65
|
const normColor = (v) => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : '#94a3b8')
|
|
@@ -104,6 +114,20 @@ export const MIGRATIONS = {
|
|
|
104
114
|
activities: [],
|
|
105
115
|
}
|
|
106
116
|
},
|
|
117
|
+
2: (data) => {
|
|
118
|
+
// v2 → v3:卡片新增评论数组;历史卡片默认没有评论
|
|
119
|
+
const src = isObj(data) ? data : {}
|
|
120
|
+
const pick = (arr) => (Array.isArray(arr) ? arr : [])
|
|
121
|
+
return {
|
|
122
|
+
schemaVersion: 3,
|
|
123
|
+
columns: pick(src.columns),
|
|
124
|
+
labels: pick(src.labels),
|
|
125
|
+
cards: pick(src.cards)
|
|
126
|
+
.map((c) => (isObj(c) ? { ...c, comments: [] } : null))
|
|
127
|
+
.filter(Boolean),
|
|
128
|
+
activities: pick(src.activities),
|
|
129
|
+
}
|
|
130
|
+
},
|
|
107
131
|
}
|
|
108
132
|
|
|
109
133
|
/**
|
|
@@ -177,8 +201,9 @@ export function validateBoard(data) {
|
|
|
177
201
|
|
|
178
202
|
if (Array.isArray(data.cards)) {
|
|
179
203
|
const seen = new Set()
|
|
204
|
+
const commentIds = new Set()
|
|
180
205
|
for (const c of data.cards) {
|
|
181
|
-
if (!isObj(c) || typeof c.id !== 'string' || !c.id || typeof c.columnId !== 'string' || typeof c.title !== 'string') {
|
|
206
|
+
if (!isObj(c) || typeof c.id !== 'string' || !c.id || typeof c.columnId !== 'string' || typeof c.title !== 'string' || !Array.isArray(c.comments)) {
|
|
182
207
|
errors.push('cards contain an invalid entry')
|
|
183
208
|
break
|
|
184
209
|
}
|
|
@@ -189,6 +214,17 @@ export function validateBoard(data) {
|
|
|
189
214
|
if (!columnIds.has(c.columnId)) errors.push('card references missing column: ' + c.id)
|
|
190
215
|
if (c.label != null && !labelNames.has(c.label)) errors.push('card references missing label: ' + c.id)
|
|
191
216
|
if (c.priority != null && !['high', 'medium', 'low'].includes(c.priority)) errors.push('card has invalid priority: ' + c.id)
|
|
217
|
+
for (const comment of c.comments) {
|
|
218
|
+
if (!isObj(comment) || typeof comment.id !== 'string' || !comment.id || typeof comment.content !== 'string' || !comment.content.trim() || comment.content.length > 2000 || !['human', 'agent'].includes(comment.source) || typeof comment.createdAt !== 'string' || !comment.createdAt) {
|
|
219
|
+
errors.push('card comments contain an invalid entry: ' + c.id)
|
|
220
|
+
break
|
|
221
|
+
}
|
|
222
|
+
if (commentIds.has(comment.id)) {
|
|
223
|
+
errors.push('duplicate comment id: ' + comment.id)
|
|
224
|
+
break
|
|
225
|
+
}
|
|
226
|
+
commentIds.add(comment.id)
|
|
227
|
+
}
|
|
192
228
|
seen.add(c.id)
|
|
193
229
|
}
|
|
194
230
|
}
|
|
@@ -444,7 +480,10 @@ export function apply(ctx) {
|
|
|
444
480
|
}
|
|
445
481
|
}
|
|
446
482
|
for (const col of board.columns) bumpSeq(col.id)
|
|
447
|
-
for (const card of board.cards)
|
|
483
|
+
for (const card of board.cards) {
|
|
484
|
+
bumpSeq(card.id)
|
|
485
|
+
for (const comment of card.comments) bumpSeq(comment.id)
|
|
486
|
+
}
|
|
448
487
|
for (const act of board.activities) bumpSeq(act.id)
|
|
449
488
|
if (board.columns.length === 0) {
|
|
450
489
|
for (const title of DEFAULT_COLUMNS) board.columns.push({ id: nextId('c'), title })
|
|
@@ -496,6 +535,16 @@ export function apply(ctx) {
|
|
|
496
535
|
|
|
497
536
|
// ---- 校验 / 查找 / 序列化 ----
|
|
498
537
|
const str = (v, fb) => (typeof v === 'string' ? v : fb)
|
|
538
|
+
|
|
539
|
+
// 截断辅助:把输入裁到 limit 以内;一旦实际发生截断,就经看板 warnings 通道
|
|
540
|
+
// 发出一次性警告(同时写入宿主日志),避免内容被静默丢弃。
|
|
541
|
+
const clampText = (value, limit, field, board) => {
|
|
542
|
+
const s = str(value, '')
|
|
543
|
+
if (s.length <= limit) return s
|
|
544
|
+
warn(board, field + ' truncated to ' + limit + ' characters')
|
|
545
|
+
return s.slice(0, limit)
|
|
546
|
+
}
|
|
547
|
+
|
|
499
548
|
const PRIORITIES = ['high', 'medium', 'low']
|
|
500
549
|
const normPriority = (v) => (typeof v === 'string' && PRIORITIES.includes(v) ? v : undefined)
|
|
501
550
|
const normColor = (v) => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : undefined)
|
|
@@ -515,6 +564,14 @@ export function apply(ctx) {
|
|
|
515
564
|
priority: c.priority ?? null,
|
|
516
565
|
createdAt: typeof c.createdAt === 'string' ? c.createdAt : null,
|
|
517
566
|
createdBy: typeof c.createdBy === 'string' ? c.createdBy : null,
|
|
567
|
+
comments: Array.isArray(c.comments)
|
|
568
|
+
? c.comments.map((comment) => ({
|
|
569
|
+
id: comment.id,
|
|
570
|
+
content: comment.content,
|
|
571
|
+
source: comment.source,
|
|
572
|
+
createdAt: comment.createdAt,
|
|
573
|
+
}))
|
|
574
|
+
: [],
|
|
518
575
|
})),
|
|
519
576
|
activities: Array.isArray(b.activities) ? b.activities.map((a) => ({ ...a })) : [],
|
|
520
577
|
})
|
|
@@ -531,6 +588,7 @@ export function apply(ctx) {
|
|
|
531
588
|
title: c.title,
|
|
532
589
|
label: c.label ?? null,
|
|
533
590
|
priority: c.priority ?? null,
|
|
591
|
+
commentCount: c.comments.length,
|
|
534
592
|
})),
|
|
535
593
|
})
|
|
536
594
|
|
|
@@ -572,17 +630,18 @@ export function apply(ctx) {
|
|
|
572
630
|
case 'addCard': {
|
|
573
631
|
const col = findColumn(board, str(a.columnId, '')) || board.columns[0]
|
|
574
632
|
if (!col) return result({ error: 'No list available' })
|
|
575
|
-
const label = typeof a.label === 'string' ? a.label
|
|
633
|
+
const label = typeof a.label === 'string' ? clampText(a.label, LABEL_LIMIT, 'Label', board) : undefined
|
|
576
634
|
if (label && !findLabel(board, label)) return result({ error: 'Label not found: ' + label })
|
|
577
635
|
const card = {
|
|
578
636
|
id: nextId('k'),
|
|
579
637
|
columnId: col.id,
|
|
580
|
-
title:
|
|
581
|
-
note:
|
|
638
|
+
title: clampText(a.title, TITLE_LIMIT, 'Title', board) || 'Untitled card',
|
|
639
|
+
note: clampText(a.note, NOTE_LIMIT, 'Note', board),
|
|
582
640
|
label,
|
|
583
641
|
priority: normPriority(a.priority),
|
|
584
642
|
createdAt: new Date().toISOString(),
|
|
585
643
|
createdBy: actor,
|
|
644
|
+
comments: [],
|
|
586
645
|
}
|
|
587
646
|
board.cards.push(card)
|
|
588
647
|
record(board, {
|
|
@@ -603,7 +662,7 @@ export function apply(ctx) {
|
|
|
603
662
|
case 'updateCard': {
|
|
604
663
|
const card = findCard(board, str(a.id, ''))
|
|
605
664
|
if (card) {
|
|
606
|
-
const nextLabel = typeof a.label === 'string' ? a.label
|
|
665
|
+
const nextLabel = typeof a.label === 'string' ? clampText(a.label, LABEL_LIMIT, 'Label', board) : undefined
|
|
607
666
|
if (nextLabel && !findLabel(board, nextLabel)) return result({ error: 'Label not found: ' + nextLabel })
|
|
608
667
|
const before = {
|
|
609
668
|
title: card.title,
|
|
@@ -611,8 +670,8 @@ export function apply(ctx) {
|
|
|
611
670
|
label: card.label ?? null,
|
|
612
671
|
priority: card.priority ?? null,
|
|
613
672
|
}
|
|
614
|
-
if (typeof a.title === 'string') card.title = a.title
|
|
615
|
-
if (typeof a.note === 'string') card.note = a.note
|
|
673
|
+
if (typeof a.title === 'string') card.title = clampText(a.title, TITLE_LIMIT, 'Title', board) || card.title
|
|
674
|
+
if (typeof a.note === 'string') card.note = clampText(a.note, NOTE_LIMIT, 'Note', board)
|
|
616
675
|
if (typeof a.label === 'string') card.label = nextLabel || undefined
|
|
617
676
|
if (typeof a.priority === 'string') card.priority = normPriority(a.priority)
|
|
618
677
|
const after = {
|
|
@@ -640,6 +699,24 @@ export function apply(ctx) {
|
|
|
640
699
|
: result({ error: 'Card not found: ' + str(a.id, '') })
|
|
641
700
|
}
|
|
642
701
|
|
|
702
|
+
case 'addComment': {
|
|
703
|
+
const card = findCard(board, str(a.id, ''))
|
|
704
|
+
if (!card) return result({ error: 'Card not found: ' + str(a.id, '') })
|
|
705
|
+
const content = str(a.content, '').trim()
|
|
706
|
+
if (!content) return result({ error: 'Comment content required' })
|
|
707
|
+
if (content.length > COMMENT_LIMIT) return result({ error: 'Comment exceeds ' + COMMENT_LIMIT + ' characters' })
|
|
708
|
+
const comment = {
|
|
709
|
+
id: nextId('m'),
|
|
710
|
+
content,
|
|
711
|
+
source: actor,
|
|
712
|
+
createdAt: new Date().toISOString(),
|
|
713
|
+
}
|
|
714
|
+
card.comments.push(comment)
|
|
715
|
+
record(board, { cardId: card.id, type: 'card_comment_added', source: actor, meta: { title: card.title } })
|
|
716
|
+
await save(workspace, session)
|
|
717
|
+
return result({ message: 'Comment added' })
|
|
718
|
+
}
|
|
719
|
+
|
|
643
720
|
case 'deleteCard': {
|
|
644
721
|
const id = str(a.id, '')
|
|
645
722
|
const card = findCard(board, id)
|
|
@@ -673,7 +750,7 @@ export function apply(ctx) {
|
|
|
673
750
|
}
|
|
674
751
|
|
|
675
752
|
case 'addColumn': {
|
|
676
|
-
const title =
|
|
753
|
+
const title = clampText(a.title, COLUMN_TITLE_LIMIT, 'List title', board) || 'New list'
|
|
677
754
|
board.columns.push({ id: nextId('c'), title })
|
|
678
755
|
record(board, { cardId: null, type: 'column_added', source: actor, meta: { column: title } })
|
|
679
756
|
await save(workspace, session)
|
|
@@ -684,7 +761,7 @@ export function apply(ctx) {
|
|
|
684
761
|
const col = findColumn(board, str(a.id, ''))
|
|
685
762
|
if (col && typeof a.title === 'string') {
|
|
686
763
|
const before = col.title
|
|
687
|
-
col.title = a.title
|
|
764
|
+
col.title = clampText(a.title, COLUMN_TITLE_LIMIT, 'List title', board) || col.title
|
|
688
765
|
if (col.title !== before) {
|
|
689
766
|
record(board, { cardId: null, type: 'column_renamed', source: actor, field: 'title', from: before, to: col.title, meta: { column: col.title } })
|
|
690
767
|
}
|
|
@@ -726,7 +803,7 @@ export function apply(ctx) {
|
|
|
726
803
|
}
|
|
727
804
|
|
|
728
805
|
case 'addLabel': {
|
|
729
|
-
const name =
|
|
806
|
+
const name = clampText(a.name, LABEL_LIMIT, 'Label name', board)
|
|
730
807
|
if (!name) return result({ error: 'Label name required' })
|
|
731
808
|
if (findLabel(board, name)) return result({ error: 'Label already exists' })
|
|
732
809
|
board.labels.push({ name, color: normColor(a.color) || '#94a3b8' })
|
|
@@ -739,7 +816,7 @@ export function apply(ctx) {
|
|
|
739
816
|
const name = str(a.name, '')
|
|
740
817
|
const label = findLabel(board, name)
|
|
741
818
|
if (!label) return result({ error: 'Label not found' })
|
|
742
|
-
const newName =
|
|
819
|
+
const newName = clampText(a.newName, LABEL_LIMIT, 'Label name', board)
|
|
743
820
|
if (newName && newName !== name) {
|
|
744
821
|
if (findLabel(board, newName)) return result({ error: 'Label name already exists' })
|
|
745
822
|
label.name = newName
|
|
@@ -1033,7 +1110,8 @@ export function apply(ctx) {
|
|
|
1033
1110
|
' - [' + card.id + '] ' +
|
|
1034
1111
|
(card.priority ? '[' + card.priority + '] ' : '') +
|
|
1035
1112
|
(card.label ? '[' + card.label + '] ' : '') +
|
|
1036
|
-
card.title
|
|
1113
|
+
card.title +
|
|
1114
|
+
(card.commentCount ? ' (' + card.commentCount + ' comments)' : ''),
|
|
1037
1115
|
)
|
|
1038
1116
|
}
|
|
1039
1117
|
}
|
|
@@ -1054,7 +1132,7 @@ export function apply(ctx) {
|
|
|
1054
1132
|
},
|
|
1055
1133
|
{
|
|
1056
1134
|
name: 'kanban_get_card',
|
|
1057
|
-
description: "Read one card's full details (title, note, label, priority) by id from the current project (workspace) board. Use kanban_get first to discover card ids, then this tool to read
|
|
1135
|
+
description: "Read one card's full details (title, note, label, priority and comments) by id from the current project (workspace) board. Use kanban_get first to discover card ids, then this tool to read the complete card and comment history.",
|
|
1058
1136
|
parameters: {
|
|
1059
1137
|
type: 'object',
|
|
1060
1138
|
properties: {
|
|
@@ -1086,6 +1164,12 @@ export function apply(ctx) {
|
|
|
1086
1164
|
if (c.priority) lines.push('Priority: ' + c.priority)
|
|
1087
1165
|
if (c.label) lines.push('Label: ' + c.label)
|
|
1088
1166
|
if (c.note) lines.push('Note: ' + c.note)
|
|
1167
|
+
if (Array.isArray(c.comments) && c.comments.length > 0) {
|
|
1168
|
+
lines.push('Comments:')
|
|
1169
|
+
for (const comment of c.comments) {
|
|
1170
|
+
lines.push(' - [' + comment.createdAt + '] ' + comment.source + ': ' + comment.content)
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1089
1173
|
}
|
|
1090
1174
|
return [{ type: 'text', text: lines.join('\n') }]
|
|
1091
1175
|
},
|
|
@@ -1099,16 +1183,32 @@ export function apply(ctx) {
|
|
|
1099
1183
|
return { ok: true, message: 'Card ' + String(args.id || '') + ' details', card: r.card, warnings: Array.isArray(r.warnings) ? r.warnings : [] }
|
|
1100
1184
|
},
|
|
1101
1185
|
},
|
|
1186
|
+
{
|
|
1187
|
+
name: 'kanban_add_comment',
|
|
1188
|
+
description: 'Add a comment to a card in the current project (workspace). Use it to leave progress updates, decisions, questions, or review feedback.',
|
|
1189
|
+
parameters: {
|
|
1190
|
+
type: 'object',
|
|
1191
|
+
properties: {
|
|
1192
|
+
id: { type: 'string', description: 'Card id' },
|
|
1193
|
+
content: { type: 'string', description: 'Comment text (1-2000 characters)' },
|
|
1194
|
+
},
|
|
1195
|
+
required: ['id', 'content'],
|
|
1196
|
+
},
|
|
1197
|
+
output: output(renderBoard),
|
|
1198
|
+
async execute(args, exec) {
|
|
1199
|
+
return runTool('addComment', args, exec)
|
|
1200
|
+
},
|
|
1201
|
+
},
|
|
1102
1202
|
{
|
|
1103
1203
|
name: 'kanban_add_card',
|
|
1104
1204
|
description: 'Add a card to the current project (workspace) kanban board. Write feature breakdowns and plans to the board: one card per task step.',
|
|
1105
1205
|
parameters: {
|
|
1106
1206
|
type: 'object',
|
|
1107
1207
|
properties: {
|
|
1108
|
-
title: { type: 'string', description:
|
|
1208
|
+
title: { type: 'string', description: `Card title (task name, concise and actionable; max ${TITLE_LIMIT} chars)` },
|
|
1109
1209
|
columnId: { type: 'string', description: 'Target list id; defaults to the first list (Todo)' },
|
|
1110
|
-
note: { type: 'string', description:
|
|
1111
|
-
label: { type: 'string', description:
|
|
1210
|
+
note: { type: 'string', description: `Note: background, acceptance criteria or breakdown details (optional; max ${NOTE_LIMIT} chars)` },
|
|
1211
|
+
label: { type: 'string', description: `Label name (optional; max ${LABEL_LIMIT} chars): e.g. New Feature, bug, Feedback` },
|
|
1112
1212
|
priority: { type: 'string', enum: ['high', 'medium', 'low'], description: 'Priority (optional): high=P0 / medium=P1 / low=P2' },
|
|
1113
1213
|
},
|
|
1114
1214
|
required: ['title'],
|
|
@@ -1125,9 +1225,9 @@ export function apply(ctx) {
|
|
|
1125
1225
|
type: 'object',
|
|
1126
1226
|
properties: {
|
|
1127
1227
|
id: { type: 'string', description: 'Card id' },
|
|
1128
|
-
title: { type: 'string', description:
|
|
1129
|
-
note: { type: 'string', description:
|
|
1130
|
-
label: { type: 'string', description:
|
|
1228
|
+
title: { type: 'string', description: `New title (optional; max ${TITLE_LIMIT} chars)` },
|
|
1229
|
+
note: { type: 'string', description: `New note (optional; max ${NOTE_LIMIT} chars)` },
|
|
1230
|
+
label: { type: 'string', description: `New label name (optional; max ${LABEL_LIMIT} chars); pass empty string to clear` },
|
|
1131
1231
|
priority: { type: 'string', enum: ['high', 'medium', 'low', ''], description: 'New priority (optional): high=P0 / medium=P1 / low=P2; pass empty string to clear' },
|
|
1132
1232
|
},
|
|
1133
1233
|
required: ['id'],
|
|
@@ -1172,7 +1272,7 @@ export function apply(ctx) {
|
|
|
1172
1272
|
description: 'Add a list (column) to the board. Use when a new workflow stage (e.g. Review, Blocked) is needed.',
|
|
1173
1273
|
parameters: {
|
|
1174
1274
|
type: 'object',
|
|
1175
|
-
properties: { title: { type: 'string', description:
|
|
1275
|
+
properties: { title: { type: 'string', description: `List name (max ${COLUMN_TITLE_LIMIT} chars)` } },
|
|
1176
1276
|
required: ['title'],
|
|
1177
1277
|
},
|
|
1178
1278
|
output: output(renderBoard),
|
|
@@ -1187,7 +1287,7 @@ export function apply(ctx) {
|
|
|
1187
1287
|
type: 'object',
|
|
1188
1288
|
properties: {
|
|
1189
1289
|
id: { type: 'string', description: 'List id' },
|
|
1190
|
-
title: { type: 'string', description:
|
|
1290
|
+
title: { type: 'string', description: `New name (max ${COLUMN_TITLE_LIMIT} chars)` },
|
|
1191
1291
|
},
|
|
1192
1292
|
required: ['id', 'title'],
|
|
1193
1293
|
},
|
|
@@ -1231,7 +1331,7 @@ export function apply(ctx) {
|
|
|
1231
1331
|
parameters: {
|
|
1232
1332
|
type: 'object',
|
|
1233
1333
|
properties: {
|
|
1234
|
-
name: { type: 'string', description:
|
|
1334
|
+
name: { type: 'string', description: `Label name (unique, max ${LABEL_LIMIT} chars; e.g. Urgent, Refactor)` },
|
|
1235
1335
|
color: { type: 'string', description: 'Label color (optional): #rrggbb hex; defaults to gray' },
|
|
1236
1336
|
},
|
|
1237
1337
|
required: ['name'],
|
|
@@ -1248,7 +1348,7 @@ export function apply(ctx) {
|
|
|
1248
1348
|
type: 'object',
|
|
1249
1349
|
properties: {
|
|
1250
1350
|
name: { type: 'string', description: 'Current label name' },
|
|
1251
|
-
newName: { type: 'string', description:
|
|
1351
|
+
newName: { type: 'string', description: `New name (optional; max ${LABEL_LIMIT} chars)` },
|
|
1252
1352
|
color: { type: 'string', description: 'New color (optional): #rrggbb hex' },
|
|
1253
1353
|
},
|
|
1254
1354
|
required: ['name'],
|