feat(vscode): handoff 纯决策与 state/UI payload

Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
2026-08-26 16:09:14 +08:00
parent bffdcb4d42
commit f7b97bbe9e
2 changed files with 206 additions and 0 deletions
+95
View File
@@ -0,0 +1,95 @@
import type { HandoffProfiles, HandoffSessions } from '../../cc/handoffManifest'
import type { IssueColumn } from '../../gitea/types'
import { LOCAL_STATE_FIELDS } from '../../issues/localState'
export function canStartHandoff(issue: {
source?: 'gitea' | 'youtrack'
column: IssueColumn
handoffAttachmentId?: string
}): boolean {
return issue.source !== 'youtrack' && issue.column !== 'done' && !issue.handoffAttachmentId
}
export function canAcceptHandoff(issue: {
source?: 'gitea' | 'youtrack'
handoffAttachmentId?: string
assignees?: string[]
}, me: string | undefined): boolean {
return issue.source !== 'youtrack'
&& !!issue.handoffAttachmentId
&& me !== undefined
&& (issue.assignees ?? []).includes(me)
}
/** 移交前 worktree 必须「干净 + 全部 push」,否则接管方从远端重建会丢工作。 */
export function checkWorktreePushed(input: {
statusPorcelain: string
localHead: string
remoteHead: string
}): { ok: true } | { ok: false, reason: string } {
if (input.statusPorcelain.trim().length > 0)
return { ok: false, reason: '工作区有未提交改动,请先提交并 push' }
if (input.localHead.trim() !== input.remoteHead.trim())
return { ok: false, reason: '本地分支与远端不一致,请先 push' }
return { ok: true }
}
export function handoffStartedStateExtra(attachmentId: number, from: string): Record<string, unknown> {
const extra: Record<string, unknown> = { handoffAttachmentId: String(attachmentId), handoffFrom: from }
for (const f of LOCAL_STATE_FIELDS)
extra[f] = ''
return extra
}
export function handoffStartedUiPatch(attachmentId: number, from: string, to: string): Record<string, unknown> {
return {
handoffAttachmentId: String(attachmentId),
handoffFrom: from,
assignees: [to],
sessionId: null,
implementSessionId: null,
reviewSessionId: null,
testSessionId: null,
worktreePath: null,
worktreeExists: false,
}
}
function definedEntries(o: Record<string, any>): Record<string, string> {
const out: Record<string, string> = {}
for (const [k, v] of Object.entries(o)) {
if (v)
out[k] = v
}
return out
}
export function handoffAcceptedStateExtra(local: {
sessions: HandoffSessions
profiles: HandoffProfiles
worktreePath?: string
}): Record<string, unknown> {
return {
handoffAttachmentId: '',
handoffFrom: '',
...definedEntries(local.sessions),
...definedEntries(local.profiles),
...(local.worktreePath ? { worktreePath: local.worktreePath } : {}),
}
}
export function handoffAcceptedUiPatch(local: {
sessions: HandoffSessions
profiles: HandoffProfiles
worktreePath?: string
worktreeExists: boolean
}): Record<string, unknown> {
return {
handoffAttachmentId: null,
handoffFrom: null,
...definedEntries(local.sessions),
...definedEntries(local.profiles),
...(local.worktreePath ? { worktreePath: local.worktreePath } : {}),
worktreeExists: local.worktreeExists,
}
}