@goodandready/dsh-kanban 0.2.40 → 0.2.41
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/client.js +3 -3
- package/lib/pr-generator.js +16 -11
- package/lib/remote-runner.js +5 -3
- package/lib/routes/board.js +13 -2
- package/lib/routes/task-actions.js +6 -4
- package/lib/store.js +11 -2
- package/lib/worktree.js +6 -1
- package/package.json +3 -2
package/lib/client.js
CHANGED
|
@@ -493,7 +493,7 @@ window.__ModuleLoader__.load({
|
|
|
493
493
|
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.28)
|
|
494
494
|
osc.start(now); osc.stop(now + 0.28)
|
|
495
495
|
}
|
|
496
|
-
} catch (_) {}
|
|
496
|
+
} catch (_) { /* audio alert best effort */ }
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
function showDesktopNotification(title, body, onClick) {
|
|
@@ -501,7 +501,7 @@ window.__ModuleLoader__.load({
|
|
|
501
501
|
if (typeof Notification === 'undefined' || Notification.permission !== 'granted') return
|
|
502
502
|
const n = new Notification(title, { body, tag: 'dsh-kanban' })
|
|
503
503
|
if (onClick) { n.onclick = () => { window.focus?.(); onClick() } }
|
|
504
|
-
} catch (_) {}
|
|
504
|
+
} catch (_) { /* desktop notification best effort */ }
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
function createErrorBoundary() {
|
|
@@ -2350,7 +2350,7 @@ window.__ModuleLoader__.load({
|
|
|
2350
2350
|
method: 'POST', body: JSON.stringify({ title: tTitle, description: tDesc, board: currentBoard, excludeId: openTask?.id })
|
|
2351
2351
|
})
|
|
2352
2352
|
setDuplicates(res?.duplicates || [])
|
|
2353
|
-
} catch (_) {} finally { setCheckingDup(false) }
|
|
2353
|
+
} catch (_) { /* duplicate search best effort */ } finally { setCheckingDup(false) }
|
|
2354
2354
|
}
|
|
2355
2355
|
const runTaskAction = async (path, body, okMsg) => {
|
|
2356
2356
|
const res = await api(path, { method: 'POST', body: JSON.stringify(body || {}) })
|
package/lib/pr-generator.js
CHANGED
|
@@ -98,6 +98,7 @@ export function parseDiffStat(statText) {
|
|
|
98
98
|
* @returns {string} PR type
|
|
99
99
|
*/
|
|
100
100
|
export function inferPrType(task, commits = []) {
|
|
101
|
+
const commitList = typeof commits === 'string' ? parseGitLog(commits) : (Array.isArray(commits) ? commits : [])
|
|
101
102
|
const title = String(task?.title ?? '').toLowerCase()
|
|
102
103
|
const labels = Array.isArray(task?.labels) ? task.labels.map((l) => String(l).toLowerCase()) : []
|
|
103
104
|
|
|
@@ -116,7 +117,7 @@ export function inferPrType(task, commits = []) {
|
|
|
116
117
|
if (title.startsWith('docs:') || title.includes('document')) return 'docs'
|
|
117
118
|
if (title.startsWith('chore:') || title.includes('cleanup')) return 'chore'
|
|
118
119
|
|
|
119
|
-
for (const c of
|
|
120
|
+
for (const c of commitList) {
|
|
120
121
|
const msg = String(c?.message ?? '').toLowerCase()
|
|
121
122
|
if (msg.startsWith('fix')) return 'fix'
|
|
122
123
|
if (msg.startsWith('refactor')) return 'refactor'
|
|
@@ -134,7 +135,8 @@ export function inferPrType(task, commits = []) {
|
|
|
134
135
|
* @returns {string} PR title
|
|
135
136
|
*/
|
|
136
137
|
export function generatePrTitle(task, commits = []) {
|
|
137
|
-
const
|
|
138
|
+
const commitList = typeof commits === 'string' ? parseGitLog(commits) : (Array.isArray(commits) ? commits : [])
|
|
139
|
+
const type = inferPrType(task, commitList)
|
|
138
140
|
const scope = inferScope(task)
|
|
139
141
|
let cleanTitle = String(task?.title ?? '').trim()
|
|
140
142
|
|
|
@@ -159,6 +161,8 @@ export function generatePrTitle(task, commits = []) {
|
|
|
159
161
|
* @returns {string} Complete Markdown description for PR
|
|
160
162
|
*/
|
|
161
163
|
export function generatePrDescription({ task, commits = [], diffStats = null, verification = null }) {
|
|
164
|
+
const commitList = typeof commits === 'string' ? parseGitLog(commits) : (Array.isArray(commits) ? commits : [])
|
|
165
|
+
const stats = typeof diffStats === 'string' ? parseDiffStat(diffStats) : diffStats
|
|
162
166
|
const title = String(task?.title ?? '').trim()
|
|
163
167
|
const body = String(task?.body ?? '').trim()
|
|
164
168
|
const issueNum = typeof task?.issueNumber === 'number' ? task.issueNumber : null
|
|
@@ -193,17 +197,17 @@ export function generatePrDescription({ task, commits = [], diffStats = null, ve
|
|
|
193
197
|
lines.push('')
|
|
194
198
|
|
|
195
199
|
// 3. Changed components & diff statistics
|
|
196
|
-
if (
|
|
200
|
+
if (stats && (stats.files?.length > 0 || stats.filesChanged > 0)) {
|
|
197
201
|
lines.push('### Changed Components & Files')
|
|
198
|
-
lines.push(`- Total files changed: ${
|
|
202
|
+
lines.push(`- Total files changed: ${stats.filesChanged || stats.files.length}`)
|
|
199
203
|
if (diffStats.additions || diffStats.deletions) {
|
|
200
|
-
lines.push(`- Changes: +${
|
|
204
|
+
lines.push(`- Changes: +${stats.additions} / -${stats.deletions}`)
|
|
201
205
|
}
|
|
202
|
-
if (Array.isArray(
|
|
203
|
-
for (const f of
|
|
206
|
+
if (Array.isArray(stats.files) && stats.files.length > 0) {
|
|
207
|
+
for (const f of stats.files.slice(0, 12)) {
|
|
204
208
|
lines.push(` - \`${f.path}\``)
|
|
205
209
|
}
|
|
206
|
-
if (
|
|
210
|
+
if (stats.files.length > 12) {
|
|
207
211
|
lines.push(` - ... and ${diffStats.files.length - 12} more files`)
|
|
208
212
|
}
|
|
209
213
|
}
|
|
@@ -213,7 +217,7 @@ export function generatePrDescription({ task, commits = [], diffStats = null, ve
|
|
|
213
217
|
// 4. Commits list
|
|
214
218
|
if (Array.isArray(commits) && commits.length > 0) {
|
|
215
219
|
lines.push('### Commits')
|
|
216
|
-
for (const c of
|
|
220
|
+
for (const c of commitList) {
|
|
217
221
|
const shaShort = c.sha ? `\`${c.sha.slice(0, 7)}\` ` : ''
|
|
218
222
|
lines.push(`- ${shaShort}${c.message}`)
|
|
219
223
|
}
|
|
@@ -252,14 +256,15 @@ export function generatePrDescription({ task, commits = [], diffStats = null, ve
|
|
|
252
256
|
* @returns {string} Formatted release notes bullet
|
|
253
257
|
*/
|
|
254
258
|
export function generateReleaseNotes({ task, commits = [] }) {
|
|
259
|
+
const commitList = typeof commits === 'string' ? parseGitLog(commits) : (Array.isArray(commits) ? commits : [])
|
|
255
260
|
const title = String(task?.title ?? '').trim()
|
|
256
261
|
const issueNum = typeof task?.issueNumber === 'number' ? ` (#${task.issueNumber})` : ''
|
|
257
262
|
const cleanTitle = title.replace(/^[A-Z]:\s*(Feature|Bug|Fix|Refactor|Task):\s*/i, '')
|
|
258
263
|
|
|
259
264
|
const lines = [`- **${cleanTitle}${issueNum}**:`]
|
|
260
265
|
|
|
261
|
-
if (Array.isArray(
|
|
262
|
-
for (const c of
|
|
266
|
+
if (Array.isArray(commitList) && commitList.length > 0) {
|
|
267
|
+
for (const c of commitList.slice(0, 5)) {
|
|
263
268
|
lines.push(` - ${c.message}`)
|
|
264
269
|
}
|
|
265
270
|
} else {
|
package/lib/remote-runner.js
CHANGED
|
@@ -112,7 +112,7 @@ export function createRemoteGitRunner(config, localGitRunner) {
|
|
|
112
112
|
*/
|
|
113
113
|
export async function testRemoteConnection(config) {
|
|
114
114
|
const root = String(config?.remoteRoot || '/mnt/external/Project/DEV').trim()
|
|
115
|
-
const probeCmd =
|
|
115
|
+
const probeCmd = 'echo REMOTE_OK'
|
|
116
116
|
const sshArgs = buildSshArgs(config, probeCmd)
|
|
117
117
|
|
|
118
118
|
const startTime = Date.now()
|
|
@@ -122,9 +122,11 @@ export async function testRemoteConnection(config) {
|
|
|
122
122
|
timeout: 12000,
|
|
123
123
|
env: { ...process.env, LC_ALL: 'C' },
|
|
124
124
|
})
|
|
125
|
+
if (!(stdout || '').includes('REMOTE_OK')) {
|
|
126
|
+
return { ok: false, error: 'Remote probe failed' }
|
|
127
|
+
}
|
|
125
128
|
const latencyMs = Date.now() - startTime
|
|
126
|
-
const
|
|
127
|
-
const remoteRootFound = trimmed.includes('REMOTE_ROOT_OK')
|
|
129
|
+
const remoteRootFound = await remotePathExists(root, config)
|
|
128
130
|
|
|
129
131
|
return {
|
|
130
132
|
ok: true,
|
package/lib/routes/board.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkCostLimit, calculateTokenCost, extractSessionUsage } from '../cost-telemetry.js'
|
|
1
2
|
import { COLUMN_ORDER, columnsOf, wipLimitField, columnNamesOf } from '../config.js'
|
|
2
3
|
import { parsePlan, planItems, planProgress, taskState, queuePosition } from '../plan.js'
|
|
3
4
|
import { columnStats, doneWithin, stale } from '../metrics.js'
|
|
@@ -18,9 +19,19 @@ export function withPlan(task, liveOf, config, now, sameSession) {
|
|
|
18
19
|
? (live.step || live.activity || live.stateDetail || '')
|
|
19
20
|
: ''
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
let costMetrics = task.costMetrics || null
|
|
23
|
+
if (!costMetrics && (task.sessionUsage || live?.usage || live?.projectionView)) {
|
|
24
|
+
const usage = extractSessionUsage(task.sessionUsage || live?.usage || live?.projectionView)
|
|
25
|
+
if (usage.totalTokens > 0) {
|
|
26
|
+
costMetrics = {
|
|
27
|
+
...usage,
|
|
28
|
+
...calculateTokenCost({ usage, usdRate: config?.usdRate, currency: config?.currency }),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
const costLimitExceeded = (config?.costMeterEnabled !== false && Number(config?.taskCostLimit) > 0 && costMetrics?.costUsd)
|
|
23
|
-
? costMetrics.costUsd
|
|
34
|
+
? checkCostLimit({ costUsd: costMetrics.costUsd, limit: Number(config.taskCostLimit) }).exceeded
|
|
24
35
|
: Boolean(task.costLimitExceeded)
|
|
25
36
|
|
|
26
37
|
return {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isTimeMachineAvailable, listTaskSnapshots, createCheckpointSnapshot, rollbackToSnapshot } from '../time-machine.js'
|
|
2
2
|
import { generateTechnicalSpec, parseSpecMarkdown } from '../plan-generator.js'
|
|
3
|
-
import { generatePrTitle, generatePrDescription, generateReleaseNotes } from '../pr-generator.js'
|
|
3
|
+
import { generatePrTitle, generatePrDescription, generateReleaseNotes, parseGitLog, parseDiffStat } from '../pr-generator.js'
|
|
4
4
|
import { getTaskCommits, getTaskDiffInspector } from '../worktree.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -484,9 +484,11 @@ export async function handleTaskPrDescription({ store, id, method, body, gitRunn
|
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
const
|
|
488
|
-
const
|
|
489
|
-
const
|
|
487
|
+
const parsedCommits = typeof commits === 'string' ? parseGitLog(commits) : commits
|
|
488
|
+
const parsedDiff = typeof diffStats === 'string' ? parseDiffStat(diffStats) : diffStats
|
|
489
|
+
const generatedTitle = generatePrTitle(task, parsedCommits)
|
|
490
|
+
const generatedDescription = generatePrDescription({ task, commits: parsedCommits, diffStats: parsedDiff })
|
|
491
|
+
const releaseNotes = generateReleaseNotes({ task, commits: parsedCommits })
|
|
490
492
|
|
|
491
493
|
return {
|
|
492
494
|
status: 200,
|
package/lib/store.js
CHANGED
|
@@ -124,7 +124,11 @@ const WRITABLE = [
|
|
|
124
124
|
/** Valid transition sources; unexpected values are rejected to preserve log integrity. */
|
|
125
125
|
const SOURCES = new Set(['manual', 'session', 'gitea', 'github', 'tool', 'workflow', 'watchdog'])
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
let corruptedCostMetricsCount = 0
|
|
128
|
+
export function getCorruptedCostMetricsCount() { return corruptedCostMetricsCount }
|
|
129
|
+
export function resetCorruptedCostMetricsCount() { corruptedCostMetricsCount = 0 }
|
|
130
|
+
|
|
131
|
+
export function rowToTask(row) {
|
|
128
132
|
if (row === undefined) return undefined
|
|
129
133
|
const { col, labels, waiting, labelColors, runs, checklist, report, comments, rejectReason, rejectedAt, isEpic, parentId, stalled, blockedBy, inceptionSnapshotId, spec, prDescription, ...rest } = row
|
|
130
134
|
let parsedChecklist = []
|
|
@@ -177,7 +181,10 @@ function rowToTask(row) {
|
|
|
177
181
|
try {
|
|
178
182
|
const val = JSON.parse(rest.costMetrics);
|
|
179
183
|
if (val && typeof val === 'object') return val;
|
|
180
|
-
} catch {
|
|
184
|
+
} catch (err) {
|
|
185
|
+
corruptedCostMetricsCount++;
|
|
186
|
+
// Corrupted telemetry JSON counted for observability without breaking task hydration
|
|
187
|
+
}
|
|
181
188
|
}
|
|
182
189
|
return rest.costMetrics && typeof rest.costMetrics === 'object' ? rest.costMetrics : null;
|
|
183
190
|
})(),
|
|
@@ -952,6 +959,8 @@ export function openStore({ dir }) {
|
|
|
952
959
|
secureStorePermissions(dir)
|
|
953
960
|
},
|
|
954
961
|
|
|
962
|
+
getCorruptedMetricsCount: () => getCorruptedCostMetricsCount(),
|
|
963
|
+
resetCorruptedMetricsCount: () => resetCorruptedCostMetricsCount(),
|
|
955
964
|
close() {
|
|
956
965
|
try { secureStorePermissions(dir) } catch { /* ignore */ }
|
|
957
966
|
db.close()
|
package/lib/worktree.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { remotePathExists } from './remote-runner.js'
|
|
1
2
|
// Managed worktrees for task agent sessions (Issue #193).
|
|
2
3
|
//
|
|
3
4
|
// Isolating agent sessions in git worktrees protects the main repository
|
|
@@ -78,8 +79,12 @@ export function safeRepoKey(task) {
|
|
|
78
79
|
* @param {Function} [gitRunner]
|
|
79
80
|
* @returns {Promise<boolean>}
|
|
80
81
|
*/
|
|
81
|
-
export async function safeWorktreeExists(worktreePath, gitRunner) {
|
|
82
|
+
export async function safeWorktreeExists(worktreePath, gitRunner, config = null) {
|
|
82
83
|
if (!worktreePath) return false
|
|
84
|
+
if (config?.remoteMode) {
|
|
85
|
+
const existsRemotely = await remotePathExists(worktreePath, config)
|
|
86
|
+
if (existsRemotely) return true
|
|
87
|
+
}
|
|
83
88
|
if (existsSync(worktreePath)) return true
|
|
84
89
|
if (typeof gitRunner === 'function') {
|
|
85
90
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-kanban",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.41",
|
|
4
4
|
"description": "Visual Kanban board for DeepSeek Harness: Gitea and GitHub-backed tasks, workflow columns, and a dedicated agent session per task.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -93,5 +93,6 @@
|
|
|
93
93
|
"@deepseek-ai/dsh-tools": {
|
|
94
94
|
"optional": true
|
|
95
95
|
}
|
|
96
|
-
}
|
|
96
|
+
},
|
|
97
|
+
"packageManager": "npm@10.9.8"
|
|
97
98
|
}
|