@alpacachen/dsh-kanban 1.5.0 → 1.5.2
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 +1 -1
- package/cordis.patch.yml +3 -3
- package/index.js +87 -87
- package/lib/client.js +18 -20
- package/package.json +15 -6
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A collaborative kanban board where you and your AI agent plan, organize, and shi
|
|
|
11
11
|
[](https://awesome-dsh-plugin.com)
|
|
12
12
|

|
|
13
13
|
|
|
14
|
-
[
|
|
14
|
+
[Simplified Chinese](README.zh.md) · **English**
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
package/cordis.patch.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Insert the kanban plugin into the profile composition.
|
|
2
|
+
# Node resolves @alpacachen/dsh-kanban from the profile node_modules after installation.
|
|
3
|
+
# Layer order: @deepseek-ai/dsh-base -> this bundle -> profile cordis.patch.yml -> user --patch overrides.
|
|
4
4
|
- insert:
|
|
5
5
|
- id: dsh-kanban
|
|
6
6
|
name: "@alpacachen/dsh-kanban"
|
package/index.js
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-kanban
|
|
2
|
+
* dsh-kanban: DSH bundle host plugin (standard Cordis function plugin).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* package.json points dsh.bundle.patch to cordis.patch.yml, which inserts this
|
|
5
|
+
* plugin into the profile composition. The loader resolves its package entry.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Workspace isolation: boards are keyed by workspaceId, one board per workspace.
|
|
9
|
+
* - Persistence: ctx.fs writes <workspace.path>/.dsh-kanban.json.
|
|
10
|
+
* - Agent tools: ctx.tools.register exposes 15 kanban_* tools.
|
|
11
|
+
* - Browser API: ctx.get('webServer') registers routes under /api/kanban.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Per-workspace data model (disk files include schemaVersion):
|
|
14
14
|
* schemaVersion: 3
|
|
15
15
|
* columns: [{ id, title }]
|
|
16
|
-
* labels:
|
|
16
|
+
* labels: [{ name, color }] // name is the unique key and binds the color
|
|
17
17
|
* cards: [{ id, columnId, title, note, label, priority, createdAt, createdBy,
|
|
18
18
|
* comments: [{ id, content, source, createdAt }] }]
|
|
19
|
-
* activities: [{ id, ts, cardId, type, source, field?, from?, to?, meta? }]
|
|
19
|
+
* activities: [{ id, ts, cardId, type, source, field?, from?, to?, meta? }] // append-only log
|
|
20
20
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* Data safety:
|
|
22
|
+
* - Files without schemaVersion are v0 and migrate on first access (see MIGRATIONS).
|
|
23
|
+
* - Back up originals before migration or recovery: .bak-vN / .corrupt-<ts> / .unsupported-vN.
|
|
24
|
+
* - Check all workspace boards at startup; back up corrupt files and keep boards usable.
|
|
25
25
|
*/
|
|
26
26
|
export const name = 'dsh-kanban'
|
|
27
27
|
|
|
28
28
|
export const inject = ['tools']
|
|
29
29
|
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
|
-
//
|
|
31
|
+
// Persistence format version and migrations.
|
|
32
32
|
//
|
|
33
|
-
//
|
|
33
|
+
// On-disk structure:
|
|
34
34
|
// { schemaVersion, columns, labels, cards }
|
|
35
35
|
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
36
|
+
// Version conventions:
|
|
37
|
+
// v1: first versioned format, matching the unversioned columns/labels/cards arrays
|
|
38
|
+
// used in 1.0.x through 1.2.x, with normalized note/label/priority card fields.
|
|
39
|
+
// LEGACY_VERSION (0): files without schemaVersion.
|
|
40
40
|
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
41
|
+
// Adding or removing fields:
|
|
42
|
+
// 1) Increment SCHEMA_VERSION.
|
|
43
|
+
// 2) Register a v(n) -> v(n+1) function in MIGRATIONS, keyed by the old version.
|
|
44
|
+
// 3) Return schemaVersion: n+1 from the pure migration (checked by migrateBoard).
|
|
45
|
+
// Back up old files before following the migration chain on first access.
|
|
46
46
|
// ---------------------------------------------------------------------------
|
|
47
47
|
|
|
48
48
|
export const SCHEMA_VERSION = 3
|
|
49
49
|
export const LEGACY_VERSION = 0
|
|
50
50
|
|
|
51
|
-
//
|
|
51
|
+
// Append-only activity log persisted with the board; drop oldest events above the limit.
|
|
52
52
|
const ACTIVITY_LIMIT = 5000
|
|
53
53
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
54
|
+
// Input limits must match tool schemas and the CardDialog editor.
|
|
55
|
+
// clampText truncates oversized input before writing and emits a one-time board warning
|
|
56
|
+
// so both agents and users know when content was shortened.
|
|
57
57
|
export const TITLE_LIMIT = 120
|
|
58
58
|
export const NOTE_LIMIT = 2000
|
|
59
59
|
export const LABEL_LIMIT = 20
|
|
@@ -64,7 +64,7 @@ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
|
64
64
|
const strField = (v, fb) => (typeof v === 'string' && v ? v : fb)
|
|
65
65
|
const normColor = (v) => (typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v.toLowerCase() : '#94a3b8')
|
|
66
66
|
|
|
67
|
-
// v1
|
|
67
|
+
// Canonical v1 entity shapes: fill missing defaults without changing valid data.
|
|
68
68
|
const normColumn = (c) =>
|
|
69
69
|
isObj(c) ? { id: strField(c.id, ''), title: strField(c.title, 'Untitled') } : null
|
|
70
70
|
const normLabel = (l) =>
|
|
@@ -82,12 +82,12 @@ const normCard = (c) =>
|
|
|
82
82
|
: null
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
85
|
+
* Migration registry: key = old version, value = (old data) => new data.
|
|
86
|
+
* Each result must set schemaVersion = key + 1; migrateBoard validates every step.
|
|
87
87
|
*/
|
|
88
88
|
export const MIGRATIONS = {
|
|
89
89
|
0: (data) => {
|
|
90
|
-
// v0
|
|
90
|
+
// v0 -> v1: declare the version and normalize legacy entity fields.
|
|
91
91
|
const src = isObj(data) ? data : {}
|
|
92
92
|
const pick = (arr) => (Array.isArray(arr) ? arr : [])
|
|
93
93
|
return {
|
|
@@ -98,7 +98,7 @@ export const MIGRATIONS = {
|
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
1: (data) => {
|
|
101
|
-
// v1
|
|
101
|
+
// v1 -> v2: add activities and card createdAt/createdBy (null for legacy cards).
|
|
102
102
|
const src = isObj(data) ? data : {}
|
|
103
103
|
const pick = (arr) => (Array.isArray(arr) ? arr : [])
|
|
104
104
|
return {
|
|
@@ -115,7 +115,7 @@ export const MIGRATIONS = {
|
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
117
|
2: (data) => {
|
|
118
|
-
// v2
|
|
118
|
+
// v2 -> v3: add comments; legacy cards start with an empty array.
|
|
119
119
|
const src = isObj(data) ? data : {}
|
|
120
120
|
const pick = (arr) => (Array.isArray(arr) ? arr : [])
|
|
121
121
|
return {
|
|
@@ -131,8 +131,8 @@ export const MIGRATIONS = {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
134
|
+
* Migrate from fromVersion to SCHEMA_VERSION one step at a time.
|
|
135
|
+
* Throw for missing steps or invalid output; the caller handles backup and fallback.
|
|
136
136
|
*/
|
|
137
137
|
export function migrateBoard(data, fromVersion) {
|
|
138
138
|
let out = data
|
|
@@ -152,8 +152,8 @@ export function migrateBoard(data, fromVersion) {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
155
|
+
* Validate the final migrated structure.
|
|
156
|
+
* Return { ok, errors }; nonempty errors mark the file as invalid.
|
|
157
157
|
*/
|
|
158
158
|
export function validateBoard(data) {
|
|
159
159
|
const errors = []
|
|
@@ -241,16 +241,16 @@ export function validateBoard(data) {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
|
-
*
|
|
244
|
+
* Parse and migrate board JSON without accessing disk.
|
|
245
245
|
*
|
|
246
|
-
*
|
|
247
|
-
* { ok: true, kind: 'ok', data, migrated, fromVersion, warnings }
|
|
248
|
-
* { ok: false, kind: 'corrupt'|'invalid'|'unsupported', warnings }
|
|
246
|
+
* Results:
|
|
247
|
+
* { ok: true, kind: 'ok', data, migrated, fromVersion, warnings }: usable current data
|
|
248
|
+
* { ok: false, kind: 'corrupt'|'invalid'|'unsupported', warnings }: back up the original
|
|
249
249
|
*
|
|
250
|
-
*
|
|
251
|
-
* corrupt
|
|
252
|
-
* invalid
|
|
253
|
-
* unsupported
|
|
250
|
+
* Failure kinds:
|
|
251
|
+
* corrupt: JSON parsing failed
|
|
252
|
+
* invalid: validation or migration failed
|
|
253
|
+
* unsupported: schemaVersion exceeds this plugin version
|
|
254
254
|
*/
|
|
255
255
|
export function parseBoardText(text) {
|
|
256
256
|
const warnings = []
|
|
@@ -334,12 +334,12 @@ export function apply(ctx) {
|
|
|
334
334
|
const getWorkspaceRegistry = () => ctx.get('workspaceRegistry')
|
|
335
335
|
|
|
336
336
|
const boards = new Map() // workspaceId -> { columns, labels, cards }
|
|
337
|
-
const boardLoads = new Map() // workspaceId -> Promise<board
|
|
338
|
-
const workspaceQueues = new Map() // workspaceId -> Promise
|
|
339
|
-
const fileTargets = new Map() // workspaceId -> FsTarget
|
|
340
|
-
let seq = 0 //
|
|
337
|
+
const boardLoads = new Map() // workspaceId -> Promise<board>; publish only fully initialized boards
|
|
338
|
+
const workspaceQueues = new Map() // workspaceId -> Promise; serialize the entire mutation
|
|
339
|
+
const fileTargets = new Map() // workspaceId -> FsTarget; retry failed resolution instead of caching it
|
|
340
|
+
let seq = 0 // Global sequence for unique cN (column) and kN (card) ids.
|
|
341
341
|
|
|
342
|
-
// ----
|
|
342
|
+
// ---- ID generation ----
|
|
343
343
|
const nextId = (prefix) => prefix + (++seq)
|
|
344
344
|
const bumpSeq = (id) => {
|
|
345
345
|
if (typeof id !== 'string') return
|
|
@@ -347,7 +347,7 @@ export function apply(ctx) {
|
|
|
347
347
|
if (Number.isFinite(n) && n > seq) seq = n
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
-
// ----
|
|
350
|
+
// ---- Default board ----
|
|
351
351
|
const DEFAULT_COLUMNS = ['Todo', 'In Progress', 'Review', 'Done']
|
|
352
352
|
const DEFAULT_LABELS = [
|
|
353
353
|
{ name: 'New Feature', color: '#38bdf8' },
|
|
@@ -355,7 +355,7 @@ export function apply(ctx) {
|
|
|
355
355
|
{ name: 'Feedback', color: '#34d399' },
|
|
356
356
|
]
|
|
357
357
|
|
|
358
|
-
// ----
|
|
358
|
+
// ---- Persistence target resolution ----
|
|
359
359
|
const BOARD_FILE = '.dsh-kanban.json'
|
|
360
360
|
const workspaceKey = (workspace) => String(workspace.id || workspace.path)
|
|
361
361
|
const writePolicyFor = (workspace, session) => {
|
|
@@ -373,7 +373,7 @@ export function apply(ctx) {
|
|
|
373
373
|
return await fs.resolve(BOARD_FILE, { cwd: workspace.path })
|
|
374
374
|
} catch (err) {
|
|
375
375
|
console.log(
|
|
376
|
-
'dsh-kanban:
|
|
376
|
+
'dsh-kanban: Failed to resolve workspace board file ' + workspaceKey(workspace) + ': ' + ((err && err.message) || err),
|
|
377
377
|
)
|
|
378
378
|
return null
|
|
379
379
|
}
|
|
@@ -387,9 +387,9 @@ export function apply(ctx) {
|
|
|
387
387
|
}
|
|
388
388
|
const persistedFlag = (workspace) => fileTargets.has(workspaceKey(workspace))
|
|
389
389
|
|
|
390
|
-
// ----
|
|
390
|
+
// ---- Board reads and writes ----
|
|
391
391
|
|
|
392
|
-
//
|
|
392
|
+
// Copy to .dsh-kanban.json.<suffix>, keeping the original until the replacement is written.
|
|
393
393
|
const backupFile = async (workspace, suffix, session) => {
|
|
394
394
|
const fs = getFs()
|
|
395
395
|
const target = await targetOf(workspace)
|
|
@@ -401,12 +401,12 @@ export function apply(ctx) {
|
|
|
401
401
|
await fs.writeText(backupTarget, text, undefined, undefined, writePolicyFor(workspace, session))
|
|
402
402
|
return backupTarget
|
|
403
403
|
} catch (err) {
|
|
404
|
-
console.log('dsh-kanban:
|
|
404
|
+
console.log('dsh-kanban: Backup failed ' + key + ' (' + suffix + '): ' + ((err && err.message) || err))
|
|
405
405
|
return null
|
|
406
406
|
}
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
-
//
|
|
409
|
+
// Queue a one-time board warning and write it to the host log.
|
|
410
410
|
const warn = (board, message) => {
|
|
411
411
|
if (Array.isArray(board.warnings)) board.warnings.push(message)
|
|
412
412
|
console.log('dsh-kanban: ' + message)
|
|
@@ -420,7 +420,7 @@ export function apply(ctx) {
|
|
|
420
420
|
|
|
421
421
|
const isNotFound = (err) => err && (err.code === 'ENOENT' || err.code === 'FS_NOT_FOUND' || err.message === 'ENOENT')
|
|
422
422
|
|
|
423
|
-
//
|
|
423
|
+
// Cache initialization promises; publish only after reads, migrations and defaults finish.
|
|
424
424
|
const boardOf = async (workspace, session) => {
|
|
425
425
|
const key = workspaceKey(workspace)
|
|
426
426
|
const existing = boards.get(key)
|
|
@@ -472,7 +472,7 @@ export function apply(ctx) {
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
} catch (err) {
|
|
475
|
-
console.log('dsh-kanban:
|
|
475
|
+
console.log('dsh-kanban: Failed to read board ' + key + ': ' + ((err && err.message) || err))
|
|
476
476
|
if (!isNotFound(err)) {
|
|
477
477
|
board.readOnlyReason = 'Board could not be read; changes are disabled to protect the existing file.'
|
|
478
478
|
warn(board, board.readOnlyReason)
|
|
@@ -528,16 +528,16 @@ export function apply(ctx) {
|
|
|
528
528
|
writePolicyFor(workspace, session),
|
|
529
529
|
)
|
|
530
530
|
} catch (err) {
|
|
531
|
-
console.log('dsh-kanban:
|
|
531
|
+
console.log('dsh-kanban: Save failed ' + key + ': ' + ((err && err.message) || err))
|
|
532
532
|
throw err
|
|
533
533
|
}
|
|
534
534
|
}
|
|
535
535
|
|
|
536
|
-
// ----
|
|
536
|
+
// ---- Validation, lookup and serialization ----
|
|
537
537
|
const str = (v, fb) => (typeof v === 'string' ? v : fb)
|
|
538
538
|
|
|
539
|
-
//
|
|
540
|
-
//
|
|
539
|
+
// Clamp input to its limit and warn through the board warnings queue and host log
|
|
540
|
+
// whenever truncation occurs, so content is never silently discarded.
|
|
541
541
|
const clampText = (value, limit, field, board) => {
|
|
542
542
|
const s = str(value, '')
|
|
543
543
|
if (s.length <= limit) return s
|
|
@@ -592,7 +592,7 @@ export function apply(ctx) {
|
|
|
592
592
|
})),
|
|
593
593
|
})
|
|
594
594
|
|
|
595
|
-
//
|
|
595
|
+
// Append a read-only activity event, persisted with the board.
|
|
596
596
|
const record = (board, ev) => {
|
|
597
597
|
if (!Array.isArray(board.activities)) board.activities = []
|
|
598
598
|
board.activities.push({
|
|
@@ -605,7 +605,7 @@ export function apply(ctx) {
|
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
|
|
608
|
-
// ----
|
|
608
|
+
// ---- Core operations shared by agent tools and browser HTTP ----
|
|
609
609
|
const READ_METHODS = new Set(['get', 'getCard'])
|
|
610
610
|
const dispatchUnlocked = async (workspace, method, args, source, session) => {
|
|
611
611
|
const board = await boardOf(workspace, session)
|
|
@@ -881,7 +881,7 @@ export function apply(ctx) {
|
|
|
881
881
|
return run
|
|
882
882
|
}
|
|
883
883
|
|
|
884
|
-
// ----
|
|
884
|
+
// ---- Tool execution context and browser workspaceId resolution ----
|
|
885
885
|
const workspaceOfExec = async (exec) => {
|
|
886
886
|
const agent = exec && exec.agent
|
|
887
887
|
const session = agent && agent.session
|
|
@@ -893,10 +893,10 @@ export function apply(ctx) {
|
|
|
893
893
|
const workspace = await registry.resolveByPath(cwd)
|
|
894
894
|
if (workspace) return workspace
|
|
895
895
|
} catch (err) {
|
|
896
|
-
console.log('dsh-kanban:
|
|
896
|
+
console.log('dsh-kanban: Failed to resolve workspace: ' + ((err && err.message) || err))
|
|
897
897
|
}
|
|
898
898
|
}
|
|
899
|
-
//
|
|
899
|
+
// Unregistered sessions use their own cwd as the workspace root.
|
|
900
900
|
return { id: 'cwd:' + cwd, path: cwd, title: cwd }
|
|
901
901
|
}
|
|
902
902
|
const workspaceOfId = (id) => {
|
|
@@ -934,7 +934,7 @@ export function apply(ctx) {
|
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
-
// ----
|
|
937
|
+
// ---- Browser API through the official webServer extension ----
|
|
938
938
|
const MAX_HTTP_BODY = 1024 * 1024
|
|
939
939
|
const sendJson = (res, status, value) => {
|
|
940
940
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' })
|
|
@@ -984,9 +984,9 @@ export function apply(ctx) {
|
|
|
984
984
|
const dispose = webServer.register({ kind: 'prefix', path: '/api/kanban', handler: httpHandler })
|
|
985
985
|
routeState.dispose = typeof dispose === 'function' ? dispose : null
|
|
986
986
|
routeState.registered = true
|
|
987
|
-
console.log('dsh-kanban: /api/kanban
|
|
987
|
+
console.log('dsh-kanban: /api/kanban route registered')
|
|
988
988
|
} catch (err) {
|
|
989
|
-
console.log('dsh-kanban:
|
|
989
|
+
console.log('dsh-kanban: Route registration failed: ' + ((err && err.message) || err))
|
|
990
990
|
}
|
|
991
991
|
}
|
|
992
992
|
registerRoute()
|
|
@@ -1005,7 +1005,7 @@ export function apply(ctx) {
|
|
|
1005
1005
|
}
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
|
-
// ----
|
|
1008
|
+
// ---- Startup checks: read each workspace board and back up corrupt files ----
|
|
1009
1009
|
const startupState = { done: false }
|
|
1010
1010
|
const maybeStartupCheck = () => {
|
|
1011
1011
|
if (startupState.done) return
|
|
@@ -1035,49 +1035,49 @@ export function apply(ctx) {
|
|
|
1035
1035
|
if (parsed.ok) {
|
|
1036
1036
|
if (parsed.migrated) {
|
|
1037
1037
|
pendingUpgrade++
|
|
1038
|
-
console.log('dsh-kanban:
|
|
1038
|
+
console.log('dsh-kanban: Startup check ' + key + ': schemaVersion ' + parsed.fromVersion + '; will migrate on first access')
|
|
1039
1039
|
} else {
|
|
1040
1040
|
ok++
|
|
1041
1041
|
}
|
|
1042
1042
|
} else if (parsed.kind === 'unsupported') {
|
|
1043
1043
|
unsupported++
|
|
1044
|
-
console.log('dsh-kanban:
|
|
1044
|
+
console.log('dsh-kanban: Startup check ' + key + ': file written by a newer plugin (schemaVersion ' + parsed.version + ')')
|
|
1045
1045
|
} else {
|
|
1046
1046
|
corrupt++
|
|
1047
1047
|
const suffix = 'corrupt-' + timestamp()
|
|
1048
1048
|
const backupTarget = await fs.resolve(BOARD_FILE + '.' + suffix, { cwd: workspace.path })
|
|
1049
1049
|
await fs.writeText(backupTarget, text, undefined, undefined, writePolicyFor(workspace))
|
|
1050
|
-
console.log('dsh-kanban:
|
|
1050
|
+
console.log('dsh-kanban: Startup check ' + key + ': corrupt data file (' + parsed.kind + '); backed up to ' + BOARD_FILE + '.' + suffix)
|
|
1051
1051
|
}
|
|
1052
1052
|
} catch (err) {
|
|
1053
1053
|
if (!err || (err.code !== 'ENOENT' && err.message !== 'ENOENT')) {
|
|
1054
|
-
console.log('dsh-kanban:
|
|
1054
|
+
console.log('dsh-kanban: Startup check ' + key + ': check failed ' + ((err && err.message) || err))
|
|
1055
1055
|
}
|
|
1056
1056
|
}
|
|
1057
1057
|
}
|
|
1058
1058
|
if (found === 0) {
|
|
1059
|
-
console.log('dsh-kanban:
|
|
1059
|
+
console.log('dsh-kanban: Startup check complete: no board data files found')
|
|
1060
1060
|
return
|
|
1061
1061
|
}
|
|
1062
1062
|
console.log(
|
|
1063
|
-
'dsh-kanban:
|
|
1063
|
+
'dsh-kanban: Startup check complete: total ' +
|
|
1064
1064
|
found +
|
|
1065
|
-
'
|
|
1065
|
+
' board files, valid ' +
|
|
1066
1066
|
ok +
|
|
1067
|
-
'
|
|
1067
|
+
', corrupt and backed up ' +
|
|
1068
1068
|
corrupt +
|
|
1069
|
-
'
|
|
1069
|
+
', pending migration ' +
|
|
1070
1070
|
pendingUpgrade +
|
|
1071
|
-
'
|
|
1071
|
+
', unsupported version ' +
|
|
1072
1072
|
unsupported,
|
|
1073
1073
|
)
|
|
1074
1074
|
} catch (err) {
|
|
1075
|
-
console.log('dsh-kanban:
|
|
1075
|
+
console.log('dsh-kanban: Startup check failed: ' + ((err && err.message) || err))
|
|
1076
1076
|
}
|
|
1077
1077
|
}
|
|
1078
1078
|
maybeStartupCheck()
|
|
1079
1079
|
|
|
1080
|
-
// ----
|
|
1080
|
+
// ---- Tool registration ----
|
|
1081
1081
|
const resultSchema = {
|
|
1082
1082
|
type: 'object',
|
|
1083
1083
|
properties: {
|