Kanban:补齐任务操作链路,明确能力边界 (#615)
* [verified] fix(kanban): harden WUI parity bridge - Align board slug normalization with canonical underscore/lowercase/64-char rules - Validate malformed Kanban action bodies before CLI shell-out - Narrow task log no-log handling and expose phase-1 capabilities - Extend client/server regression coverage for parity actions * fix(kanban): guard archived task detail actions --------- Co-authored-by: ekko <152005280+EKKOLearnAI@users.noreply.github.com>
This commit is contained in:
@@ -121,10 +121,29 @@ export interface KanbanBoardCreateRequest {
|
||||
switchCurrent?: boolean
|
||||
}
|
||||
|
||||
export interface KanbanCapabilityStatus {
|
||||
key: string
|
||||
status: 'supported' | 'partial' | 'missing'
|
||||
reason?: string
|
||||
canonicalRoute?: string
|
||||
canonicalCommand?: string
|
||||
requiresBoard: boolean
|
||||
}
|
||||
|
||||
export interface KanbanCapabilities {
|
||||
source: 'hermes-cli'
|
||||
supports: Record<string, boolean>
|
||||
missing: string[]
|
||||
capabilities?: KanbanCapabilityStatus[]
|
||||
}
|
||||
|
||||
export interface KanbanTaskLog {
|
||||
task_id: string
|
||||
path: string | null
|
||||
exists: boolean
|
||||
size_bytes: number
|
||||
content: string
|
||||
truncated: boolean
|
||||
}
|
||||
|
||||
export interface KanbanCreateRequest {
|
||||
@@ -146,6 +165,39 @@ export interface KanbanListOptions extends KanbanBoardOptions {
|
||||
includeArchived?: boolean
|
||||
}
|
||||
|
||||
export interface KanbanCommentCreateRequest {
|
||||
body: string
|
||||
author?: string
|
||||
}
|
||||
|
||||
export interface KanbanTaskLogOptions extends KanbanBoardOptions {
|
||||
tail?: number
|
||||
}
|
||||
|
||||
export interface KanbanDiagnosticsOptions extends KanbanBoardOptions {
|
||||
task?: string
|
||||
severity?: 'warning' | 'error' | 'critical'
|
||||
}
|
||||
|
||||
export interface KanbanReclaimOptions extends KanbanBoardOptions {
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface KanbanReassignOptions extends KanbanBoardOptions {
|
||||
reclaim?: boolean
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export interface KanbanSpecifyOptions extends KanbanBoardOptions {
|
||||
author?: string
|
||||
}
|
||||
|
||||
export interface KanbanDispatchOptions extends KanbanBoardOptions {
|
||||
dryRun?: boolean
|
||||
max?: number
|
||||
failureLimit?: number
|
||||
}
|
||||
|
||||
function normalizedBoard(board?: string): string {
|
||||
const trimmed = board?.trim()
|
||||
return trimmed || 'default'
|
||||
@@ -240,6 +292,58 @@ export async function assignTask(taskId: string, profile: string, opts?: KanbanB
|
||||
})
|
||||
}
|
||||
|
||||
export async function addComment(taskId: string, data: KanbanCommentCreateRequest, opts?: KanbanBoardOptions): Promise<{ ok: boolean; output?: string }> {
|
||||
return request<{ ok: boolean; output?: string }>(appendQuery(`/api/hermes/kanban/${encodeURIComponent(taskId)}/comments`, boardParams(opts?.board)), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
}
|
||||
|
||||
export async function getTaskLog(taskId: string, opts?: KanbanTaskLogOptions): Promise<KanbanTaskLog> {
|
||||
const params = boardParams(opts?.board)
|
||||
if (opts?.tail !== undefined) params.set('tail', String(opts.tail))
|
||||
return request<KanbanTaskLog>(appendQuery(`/api/hermes/kanban/${encodeURIComponent(taskId)}/log`, params))
|
||||
}
|
||||
|
||||
export async function getDiagnostics(opts?: KanbanDiagnosticsOptions): Promise<unknown[]> {
|
||||
const params = boardParams(opts?.board)
|
||||
if (opts?.task) params.set('task', opts.task)
|
||||
if (opts?.severity) params.set('severity', opts.severity)
|
||||
const res = await request<{ diagnostics: unknown[] }>(appendQuery('/api/hermes/kanban/diagnostics', params))
|
||||
return res.diagnostics
|
||||
}
|
||||
|
||||
export async function reclaimTask(taskId: string, opts?: KanbanReclaimOptions): Promise<{ ok: boolean; output?: string }> {
|
||||
return request<{ ok: boolean; output?: string }>(appendQuery(`/api/hermes/kanban/${encodeURIComponent(taskId)}/reclaim`, boardParams(opts?.board)), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ reason: opts?.reason }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function reassignTask(taskId: string, profile: string, opts?: KanbanReassignOptions): Promise<{ ok: boolean; output?: string }> {
|
||||
return request<{ ok: boolean; output?: string }>(appendQuery(`/api/hermes/kanban/${encodeURIComponent(taskId)}/reassign`, boardParams(opts?.board)), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ profile, reclaim: opts?.reclaim, reason: opts?.reason }),
|
||||
})
|
||||
}
|
||||
|
||||
export async function specifyTask(taskId: string, opts?: KanbanSpecifyOptions): Promise<unknown[]> {
|
||||
const res = await request<{ results: unknown[] }>(appendQuery(`/api/hermes/kanban/${encodeURIComponent(taskId)}/specify`, boardParams(opts?.board)), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ author: opts?.author }),
|
||||
})
|
||||
return res.results
|
||||
}
|
||||
|
||||
export async function dispatch(opts?: KanbanDispatchOptions): Promise<unknown> {
|
||||
const params = boardParams(opts?.board)
|
||||
const res = await request<{ result: unknown }>(appendQuery('/api/hermes/kanban/dispatch', params), {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ dryRun: opts?.dryRun, max: opts?.max, failureLimit: opts?.failureLimit }),
|
||||
})
|
||||
return res.result
|
||||
}
|
||||
|
||||
export async function getStats(opts?: KanbanBoardOptions): Promise<KanbanStats> {
|
||||
const res = await request<{ stats: KanbanStats }>(appendQuery('/api/hermes/kanban/stats', boardParams(opts?.board)))
|
||||
return res.stats
|
||||
|
||||
Reference in New Issue
Block a user