♻️ refactor(vscode): 工单来源 gitea 改名 forge,激活时把本机按旧名存的记录迁过去
This commit is contained in:
1 parent
14bee9f505
commit
451bc91a6b
24 files changed
+234
-90
No files matched your search
@@ -17,7 +17,7 @@ const KEY = 'spx-handoff/o/r/issue-9-20261008T010203Z.tar.xz'
|
||||
describe('canStartHandoff', () => {
|
||||
it('gitea 工单、非 done、无待接管、无 assignee 或 assignee 含我 → true', () => {
|
||||
expect(canStartHandoff({ column: 'in-progress' }, 'me')).toBe(true)
|
||||
expect(canStartHandoff({ column: 'todo', source: 'gitea' }, 'me')).toBe(true)
|
||||
expect(canStartHandoff({ column: 'todo', source: 'forge' }, 'me')).toBe(true)
|
||||
expect(canStartHandoff({ column: 'todo', assignees: [] }, 'me')).toBe(true)
|
||||
expect(canStartHandoff({ column: 'todo', assignees: ['me', 'x'] }, 'me')).toBe(true)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { formatByteSize } from '../../cc/handoffArchive'
|
||||
import { LOCAL_PROFILE_FIELDS, LOCAL_STATE_FIELDS } from '../../issues/localState'
|
||||
|
||||
export function canStartHandoff(issue: {
|
||||
source?: 'gitea' | 'youtrack'
|
||||
source?: 'forge' | 'youtrack'
|
||||
column: IssueColumn
|
||||
handoffBundleKey?: string
|
||||
assignees?: string[]
|
||||
@@ -15,7 +15,7 @@ export function canStartHandoff(issue: {
|
||||
}
|
||||
|
||||
export function canAcceptHandoff(issue: {
|
||||
source?: 'gitea' | 'youtrack'
|
||||
source?: 'forge' | 'youtrack'
|
||||
handoffBundleKey?: string
|
||||
assignees?: string[]
|
||||
}, me: string | undefined): boolean {
|
||||
|
||||
@@ -185,7 +185,7 @@ export async function handleHandoffStart(panel: KanbanWebviewPanel, issueNumber:
|
||||
const knownColumns: IssueColumn[] = ['todo', 'in-progress', 'review', 'done']
|
||||
const column = knownColumns.includes(state.column as IssueColumn) ? (state.column as IssueColumn) : 'todo'
|
||||
const assignees = issue.assignees.map(a => a.login)
|
||||
if (!canStartHandoff({ source: 'gitea', column, handoffBundleKey: str(state.handoffBundleKey), assignees }, me)) {
|
||||
if (!canStartHandoff({ source: 'forge', column, handoffBundleKey: str(state.handoffBundleKey), assignees }, me)) {
|
||||
const message = `#${issueNumber} 不是你负责的工单,或已在移交中 / 已完成,不能移交`
|
||||
toast(panel, 'error', message)
|
||||
return { ok: false, message }
|
||||
@@ -377,7 +377,7 @@ export async function handleHandoffStart(panel: KanbanWebviewPanel, issueNumber:
|
||||
}
|
||||
}
|
||||
|
||||
async function requestFacts(panel: KanbanWebviewPanel, issueNumber: number): Promise<{ me: string, facts: { source: 'gitea', column: IssueColumn, handoffBundleKey?: string, handoffRequestFrom?: string, assignees: string[] } } | undefined> {
|
||||
async function requestFacts(panel: KanbanWebviewPanel, issueNumber: number): Promise<{ me: string, facts: { source: 'forge', column: IssueColumn, handoffBundleKey?: string, handoffRequestFrom?: string, assignees: string[] } } | undefined> {
|
||||
const ctx = await resolveRepoCtx(panel)
|
||||
if (!ctx)
|
||||
return
|
||||
@@ -399,7 +399,7 @@ async function requestFacts(panel: KanbanWebviewPanel, issueNumber: number): Pro
|
||||
return {
|
||||
me: ctx.me,
|
||||
facts: {
|
||||
source: 'gitea',
|
||||
source: 'forge',
|
||||
column,
|
||||
...(str(state.handoffBundleKey) ? { handoffBundleKey: str(state.handoffBundleKey) } : {}),
|
||||
...(str(state.handoffRequestFrom) ? { handoffRequestFrom: str(state.handoffRequestFrom) } : {}),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
||||
import { canAnswerHandoffRequest, canAskHandoff, canCancelHandoffRequest, handoffRequestNotices, handoffRequestSystemNotice } from './handoffRequest'
|
||||
|
||||
const review = {
|
||||
source: 'gitea' as const,
|
||||
source: 'forge' as const,
|
||||
column: 'review' as const,
|
||||
assignees: ['chw'],
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface HandoffRequestFacts {
|
||||
source?: 'gitea' | 'youtrack'
|
||||
source?: 'forge' | 'youtrack'
|
||||
column: 'todo' | 'in-progress' | 'review' | 'done'
|
||||
handoffBundleKey?: string
|
||||
handoffRequestFrom?: string
|
||||
|
||||
@@ -45,7 +45,7 @@ const LAUNCH = { cwd: '/worktree', startedAt: STARTED_AT, prompt: PROMPT }
|
||||
const panels: KanbanWebviewPanel[] = []
|
||||
let resolveSession!: (id: string | null) => void
|
||||
|
||||
function fixture(source: 'gitea' | 'youtrack' = 'gitea') {
|
||||
function fixture(source: 'forge' | 'youtrack' = 'forge') {
|
||||
const store = new Map<string, unknown>()
|
||||
const context = {
|
||||
workspaceState: {
|
||||
@@ -126,7 +126,7 @@ describe('reviewSessionCapture', () => {
|
||||
mocks.watch.mockResolvedValue({ sessionId: Promise.resolve(SID), initialSessionId: SID })
|
||||
await recoverOpenReviewSessions(f.panel, [466])
|
||||
expect(mocks.watch).toHaveBeenCalledWith(expect.objectContaining({ ...LAUNCH, startedAt: STARTED_AT }))
|
||||
expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBe(SID)
|
||||
expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBe(SID)
|
||||
expect(f.terminal.sendText).not.toHaveBeenCalled()
|
||||
expect(f.terminal.show).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -134,28 +134,28 @@ describe('reviewSessionCapture', () => {
|
||||
it('尚未落盘的恢复立即返回,之后后台补回ID', async () => {
|
||||
const f = fixture()
|
||||
await expect(recoverReviewSessionCapture(f.panel, 466, f.terminal)).resolves.toBeNull()
|
||||
expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBeUndefined()
|
||||
expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBeUndefined()
|
||||
resolveSession(SID)
|
||||
await vi.waitFor(() => expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBe(SID))
|
||||
await vi.waitFor(() => expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBe(SID))
|
||||
})
|
||||
|
||||
it('用户清除留下的墓碑不得自动补回', async () => {
|
||||
const f = fixture()
|
||||
f.store.set('superpowers.localIssueState', { 'gitea:466': { reviewSessionId: '' } })
|
||||
f.store.set('superpowers.localIssueState', { 'forge:466': { reviewSessionId: '' } })
|
||||
await expect(recoverReviewSessionCapture(f.panel, 466, f.terminal)).resolves.toBeNull()
|
||||
expect(mocks.exec).not.toHaveBeenCalled()
|
||||
expect(mocks.watch).not.toHaveBeenCalled()
|
||||
expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBe('')
|
||||
expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBe('')
|
||||
})
|
||||
|
||||
it('用户明确新建的审查可以替换之前清除留下的墓碑', async () => {
|
||||
const f = fixture()
|
||||
f.store.set('superpowers.localIssueState', { 'gitea:466': { reviewSessionId: '' } })
|
||||
f.store.set('superpowers.localIssueState', { 'forge:466': { reviewSessionId: '' } })
|
||||
const prepared = await prepareReviewSessionCapture(f.panel, 466, LAUNCH)
|
||||
const captured = prepared.attach(f.terminal)
|
||||
resolveSession(SID)
|
||||
await expect(captured).resolves.toBe(SID)
|
||||
expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBe(SID)
|
||||
expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBe(SID)
|
||||
})
|
||||
|
||||
it('清除时中止待捕获并删除启动记录,之后不写回ID', async () => {
|
||||
@@ -166,7 +166,7 @@ describe('reviewSessionCapture', () => {
|
||||
resolveSession(SID)
|
||||
await expect(captured).resolves.toBeNull()
|
||||
expect(f.store.has(f.launchKey)).toBe(false)
|
||||
expect(getLocalIssueState(f.context, 'gitea', 466).reviewSessionId).toBeUndefined()
|
||||
expect(getLocalIssueState(f.context, 'forge', 466).reviewSessionId).toBeUndefined()
|
||||
})
|
||||
|
||||
it('准备期间被取消,不允许稍后继续创建终端', async () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { window } from 'vscode'
|
||||
import { watchForCodexSession } from '../../cc/codexSessionWatcher'
|
||||
import { getLocalIssueState, mergeLocalIssueState } from '../../issues/localState'
|
||||
import { getLocalIssueState, localIssueKey, mergeLocalIssueState } from '../../issues/localState'
|
||||
import { logger } from '../../logging/logger'
|
||||
import { defaultCodexSessionsDir } from '../../sessions/sessionFiles'
|
||||
|
||||
@@ -32,7 +32,7 @@ function captureMap(panel: KanbanWebviewPanel): Map<number, ReviewCapture> {
|
||||
}
|
||||
|
||||
function launchKey(source: ReviewCapture['source'], issueNumber: number): string {
|
||||
return `superpowers.reviewSessionLaunch.${source}:${issueNumber}`
|
||||
return `superpowers.reviewSessionLaunch.${localIssueKey(source, issueNumber)}`
|
||||
}
|
||||
|
||||
/** 用户清除关联时先中止捕获,防止稍后落盘的文件把关联补回来。 */
|
||||
@@ -40,7 +40,7 @@ export async function cancelReviewSessionCapture(panel: KanbanWebviewPanel, issu
|
||||
const map = captureMap(panel)
|
||||
const capture = map.get(issueNumber)
|
||||
const recovery = recoveries.get(panel)?.get(issueNumber)
|
||||
const source = capture?.source ?? recovery?.source ?? panel.refFor(issueNumber).source ?? 'gitea'
|
||||
const source = capture?.source ?? recovery?.source ?? panel.refFor(issueNumber).source ?? 'forge'
|
||||
recovery?.abort.abort()
|
||||
capture?.abort.abort()
|
||||
map.delete(issueNumber)
|
||||
@@ -85,7 +85,7 @@ export async function prepareReviewSessionCapture(
|
||||
panel: KanbanWebviewPanel,
|
||||
issueNumber: number,
|
||||
launch: CodexSessionLaunch,
|
||||
source: ReviewCapture['source'] = panel.refFor(issueNumber).source ?? 'gitea',
|
||||
source: ReviewCapture['source'] = panel.refFor(issueNumber).source ?? 'forge',
|
||||
): Promise<{
|
||||
attach: (terminal: Terminal) => Promise<string | null>
|
||||
initialSessionId: string | null
|
||||
@@ -269,7 +269,7 @@ export function recoverReviewSessionCapture(panel: KanbanWebviewPanel, issueNumb
|
||||
if (existing)
|
||||
return existing.result
|
||||
const abort = new AbortController()
|
||||
const source = panel.refFor(issueNumber).source ?? 'gitea'
|
||||
const source = panel.refFor(issueNumber).source ?? 'forge'
|
||||
const result = recoverReviewSessionCaptureInner(panel, issueNumber, terminal, abort.signal, source)
|
||||
.finally(() => {
|
||||
if (map.get(issueNumber)?.result === result)
|
||||
|
||||
Reference in new issue
Block a user