diff --git a/vscode/package.json b/vscode/package.json index b49e14b..39affb9 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,7 @@ "publisher": "clurdra", "name": "superpowers-vscode-clurdra", "displayName": "Superpowers-clurdra", - "version": "0.2.127", + "version": "0.2.128", "packageManager": "pnpm@10.27.0", "description": "Superpowers specs and plans Kanban explorer", "author": "clurdra", diff --git a/vscode/src/panel/KanbanPanel.ts b/vscode/src/panel/KanbanPanel.ts index 9b33d82..bc13e2a 100644 --- a/vscode/src/panel/KanbanPanel.ts +++ b/vscode/src/panel/KanbanPanel.ts @@ -970,6 +970,9 @@ export class KanbanWebviewPanel { implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, splitNewTerminals: s.splitNewTerminals, + defaultBrainstormProfilePath: s.defaultBrainstormProfilePath, + defaultImplementProfilePath: s.defaultImplementProfilePath, + defaultTestProfilePath: s.defaultTestProfilePath, conflictResolutionProfilePath: s.conflictResolutionProfilePath, commitProfilePath: await getEffectiveCommitProfilePath(this.context), prDiffSummaryProfilePath: await getEffectivePrDiffSummaryProfilePath(this.context), @@ -1055,6 +1058,9 @@ export class KanbanWebviewPanel { implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, splitNewTerminals: s.splitNewTerminals, + defaultBrainstormProfilePath: s.defaultBrainstormProfilePath, + defaultImplementProfilePath: s.defaultImplementProfilePath, + defaultTestProfilePath: s.defaultTestProfilePath, conflictResolutionProfilePath: s.conflictResolutionProfilePath, commitProfilePath: await getEffectiveCommitProfilePath(this.context), prDiffSummaryProfilePath: await getEffectivePrDiffSummaryProfilePath(this.context), diff --git a/vscode/src/panel/handlers/issues.ts b/vscode/src/panel/handlers/issues.ts index ecbf282..273a852 100644 --- a/vscode/src/panel/handlers/issues.ts +++ b/vscode/src/panel/handlers/issues.ts @@ -10,7 +10,7 @@ import { commands, env, ThemeColor, Uri, window, workspace } from 'vscode' import { getToken } from '../../auth/secrets' import { buildCcCommand } from '../../cc/ccCommand' import { resolveCommitProfileForRun } from '../../cc/commitProfile' -import { getDefaultProfilePath, resolveProfilePath } from '../../cc/profiles' +import { resolveProfilePath } from '../../cc/profiles' import { getBrainstormPrompt } from '../../cc/prompts' import { claudeProjectsRoot, installClaudeSession } from '../../cc/sessionBundle' import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher' @@ -1429,8 +1429,8 @@ export async function handleIssueCreate( return } - const effectiveProfilePath - = brainstormProfilePath && brainstormProfilePath.trim() !== '' ? brainstormProfilePath : getDefaultProfilePath() + // 新建工单弹窗里选的 profile > 设置里的「头脑风暴默认配置文件」> DEFAULT。 + const effectiveProfilePath = sessions.resolveBrainstormProfilePath(panel, brainstormProfilePath) if (effectiveProfilePath.includes('\'')) { panel.postMessage({ type: 'toast/show', @@ -1888,7 +1888,7 @@ export async function handleUpdateAutoReview(panel: KanbanWebviewPanel, issueNum /** * Persist a per-issue `profilePath` override into the issue's state JSON * comment. Implement / implement-resume sessions use this profile - * (see resolveImplementProfilePath); empty = getDefaultProfilePath(). + * (see resolveImplementProfilePath); empty = 设置里的「实施默认配置文件」,再空 = getDefaultProfilePath()。 * Mirrors handleUpdateAutoReview: optimistic update on the webview, rolled * back via `issue/patch` if the persist fails. */ @@ -1979,7 +1979,7 @@ export async function handleUpdateProfilePath(panel: KanbanWebviewPanel, issueNu /** * Persist a per-issue `brainstormProfilePath` override into the issue's state * JSON. Brainstorm start/resume use this profile (see resolveBrainstormProfilePath); - * empty = getDefaultProfilePath(). 与实施/测试 profile 互不回退。 + * empty = 设置里的「头脑风暴默认配置文件」,再空 = getDefaultProfilePath()。与实施/测试 profile 互不回退。 */ export async function handleUpdateBrainstormProfilePath(panel: KanbanWebviewPanel, issueNumber: number, brainstormProfilePath: string): Promise { const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath @@ -2063,7 +2063,7 @@ export async function handleUpdateBrainstormProfilePath(panel: KanbanWebviewPane /** * Persist a per-issue `testProfilePath` override into the issue's state JSON * comment. Test start/resume use this profile (see resolveTestProfilePath); - * empty = getDefaultProfilePath(),不回退实施 profile。 + * empty = 设置里的「测试默认配置文件」,再空 = getDefaultProfilePath(),不回退实施 profile。 */ export async function handleUpdateTestProfilePath(panel: KanbanWebviewPanel, issueNumber: number, testProfilePath: string): Promise { const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath diff --git a/vscode/src/panel/handlers/sessions.ts b/vscode/src/panel/handlers/sessions.ts index 42c1d04..f45b4f0 100644 --- a/vscode/src/panel/handlers/sessions.ts +++ b/vscode/src/panel/handlers/sessions.ts @@ -1599,30 +1599,43 @@ export async function startConflictResolution(panel: KanbanWebviewPanel, opts: { * (handleImplement / handleResumeSession when sessionKind === 'implement') * should launch with. Priority: * 1. 工单级 `profilePath`(详情面板「实施配置文件」) - * 2. getDefaultProfilePath() + * 2. 设置里的「实施默认配置文件」 + * 3. getDefaultProfilePath() * * 冲突解决用全局 `settings.conflictResolutionProfilePath`。 * 头脑风暴用 resolveBrainstormProfilePath;测试用 resolveTestProfilePath。 * 三者互不回退。 */ -export function resolveImplementProfilePath(_panel: KanbanWebviewPanel, issueLevelProfilePath: string | undefined): string { - return resolveProfilePath(issueLevelProfilePath?.trim() || undefined) +export function resolveImplementProfilePath(panel: KanbanWebviewPanel, issueLevelProfilePath: string | undefined): string { + return resolveProfilePath( + issueLevelProfilePath?.trim() + || getSettings(panel.context).defaultImplementProfilePath.trim() + || undefined, + ) } /** - * 头脑风暴会话 profile:工单级 `brainstormProfilePath` > DEFAULT。 - * 不回退实施 profile。 + * 头脑风暴会话 profile:工单级 `brainstormProfilePath` > 设置里的 + * 「头脑风暴默认配置文件」> DEFAULT。不回退实施 profile。 */ -export function resolveBrainstormProfilePath(_panel: KanbanWebviewPanel, brainstormProfilePath: string | undefined): string { - return resolveProfilePath(brainstormProfilePath?.trim() || undefined) +export function resolveBrainstormProfilePath(panel: KanbanWebviewPanel, brainstormProfilePath: string | undefined): string { + return resolveProfilePath( + brainstormProfilePath?.trim() + || getSettings(panel.context).defaultBrainstormProfilePath.trim() + || undefined, + ) } /** - * 测试会话 profile:工单级 `testProfilePath` > DEFAULT。 - * 不回退实施 profile。 + * 测试会话 profile:工单级 `testProfilePath` > 设置里的「测试默认配置文件」 + * > DEFAULT。不回退实施 profile。 */ -export function resolveTestProfilePath(_panel: KanbanWebviewPanel, testProfilePath: string | undefined): string { - return resolveProfilePath(testProfilePath?.trim() || undefined) +export function resolveTestProfilePath(panel: KanbanWebviewPanel, testProfilePath: string | undefined): string { + return resolveProfilePath( + testProfilePath?.trim() + || getSettings(panel.context).defaultTestProfilePath.trim() + || undefined, + ) } /** diff --git a/vscode/src/panel/handlers/settings.ts b/vscode/src/panel/handlers/settings.ts index 5040aeb..7b5045a 100644 --- a/vscode/src/panel/handlers/settings.ts +++ b/vscode/src/panel/handlers/settings.ts @@ -39,6 +39,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { implTabPreCreateScript: string implTabPostCloseScript: string splitNewTerminals: boolean + defaultBrainstormProfilePath: string + defaultImplementProfilePath: string + defaultTestProfilePath: string conflictResolutionProfilePath: string commitProfilePath: string prDiffSummaryProfilePath: string @@ -74,6 +77,10 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { const trimmedPreRemove = payload.worktreePreRemoveScript.trim() const trimmedImplPre = payload.implTabPreCreateScript.trim() const trimmedImplPost = payload.implTabPostCloseScript.trim() + // 三个会话默认 profile:'' 有意义(= 落到 DEFAULT_PROFILE_PATH),只 trim。 + const trimmedDefaultBrainstormProfile = payload.defaultBrainstormProfilePath.trim() + const trimmedDefaultImplementProfile = payload.defaultImplementProfilePath.trim() + const trimmedDefaultTestProfile = payload.defaultTestProfilePath.trim() // conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH). const trimmedConflictProfile = payload.conflictResolutionProfilePath.trim() // 提交代码 profile:'' 有意义(= 未配置 → 提交严格失败),只 trim。 @@ -118,6 +125,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { implTabPreCreateScript: trimmedImplPre, implTabPostCloseScript: trimmedImplPost, splitNewTerminals: payload.splitNewTerminals, + defaultBrainstormProfilePath: trimmedDefaultBrainstormProfile, + defaultImplementProfilePath: trimmedDefaultImplementProfile, + defaultTestProfilePath: trimmedDefaultTestProfile, conflictResolutionProfilePath: trimmedConflictProfile, commitProfilePath: trimmedCommitProfile, prDiffSummaryProfilePath: trimmedPrDiffSummaryProfile, @@ -147,6 +157,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: { implTabPreCreateScript: trimmedImplPre, implTabPostCloseScript: trimmedImplPost, splitNewTerminals: payload.splitNewTerminals, + defaultBrainstormProfilePath: trimmedDefaultBrainstormProfile, + defaultImplementProfilePath: trimmedDefaultImplementProfile, + defaultTestProfilePath: trimmedDefaultTestProfile, conflictResolutionProfilePath: trimmedConflictProfile, commitProfilePath: trimmedCommitProfile, prDiffSummaryProfilePath: trimmedPrDiffSummaryProfile, @@ -253,6 +266,9 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom implTabPreCreateScript: s.implTabPreCreateScript, implTabPostCloseScript: s.implTabPostCloseScript, splitNewTerminals: s.splitNewTerminals, + defaultBrainstormProfilePath: s.defaultBrainstormProfilePath, + defaultImplementProfilePath: s.defaultImplementProfilePath, + defaultTestProfilePath: s.defaultTestProfilePath, conflictResolutionProfilePath: s.conflictResolutionProfilePath, commitProfilePath, prDiffSummaryProfilePath, diff --git a/vscode/src/panel/messages.ts b/vscode/src/panel/messages.ts index 8c7e95b..cbd2869 100644 --- a/vscode/src/panel/messages.ts +++ b/vscode/src/panel/messages.ts @@ -86,6 +86,9 @@ export type ExtensionToWebview implTabPreCreateScript: string implTabPostCloseScript: string splitNewTerminals: boolean + defaultBrainstormProfilePath: string + defaultImplementProfilePath: string + defaultTestProfilePath: string conflictResolutionProfilePath: string commitProfilePath: string prDiffSummaryProfilePath: string @@ -165,6 +168,9 @@ export type WebviewToExtension implTabPreCreateScript: string implTabPostCloseScript: string splitNewTerminals: boolean + defaultBrainstormProfilePath: string + defaultImplementProfilePath: string + defaultTestProfilePath: string conflictResolutionProfilePath: string commitProfilePath: string prDiffSummaryProfilePath: string diff --git a/vscode/src/settings/store.ts b/vscode/src/settings/store.ts index c0e8487..b87acfe 100644 --- a/vscode/src/settings/store.ts +++ b/vscode/src/settings/store.ts @@ -180,6 +180,15 @@ export interface Settings { * extension finds the project's first `isResolved` state value and uses that. */ youtrackCloseCommand: string + /** + * 头脑风暴会话的默认 Claude profile 绝对路径。工单详情面板里的 + * 「头脑风暴配置文件」为空时用它;它也为空才落到 DEFAULT_PROFILE_PATH。 + */ + defaultBrainstormProfilePath: string + /** 实施会话的默认 Claude profile;工单级「实施配置文件」为空时用它。 */ + defaultImplementProfilePath: string + /** 测试会话的默认 Claude profile;工单级「测试配置文件」为空时用它。 */ + defaultTestProfilePath: string /** * Claude settings profile path for the conflict-resolution cc session * (drag to 完成 when PR merge conflicts). Empty = DEFAULT_PROFILE_PATH. @@ -272,6 +281,9 @@ function defaults(ctx: ExtensionContext): Settings { youtrackBaseUrl: '', youtrackProjectShortName: '', youtrackCloseCommand: '', + defaultBrainstormProfilePath: '', + defaultImplementProfilePath: '', + defaultTestProfilePath: '', conflictResolutionProfilePath: '', commitProfilePath: '', prDiffSummaryProfilePath: '', @@ -369,6 +381,16 @@ export function getSettings(ctx: ExtensionContext): Settings { const youtrackCloseCommand = typeof stored.youtrackCloseCommand === 'string' ? stored.youtrackCloseCommand : base.youtrackCloseCommand + // 三个会话默认 profile:'' 有意义(= 落到 DEFAULT_PROFILE_PATH),不强制回退。 + const defaultBrainstormProfilePath = typeof stored.defaultBrainstormProfilePath === 'string' + ? stored.defaultBrainstormProfilePath + : base.defaultBrainstormProfilePath + const defaultImplementProfilePath = typeof stored.defaultImplementProfilePath === 'string' + ? stored.defaultImplementProfilePath + : base.defaultImplementProfilePath + const defaultTestProfilePath = typeof stored.defaultTestProfilePath === 'string' + ? stored.defaultTestProfilePath + : base.defaultTestProfilePath // conflict-resolution profile: '' is meaningful (= DEFAULT_PROFILE_PATH at // launch time), so don't coerce. Non-string legacy values collapse to ''. const conflictResolutionProfilePath = typeof stored.conflictResolutionProfilePath === 'string' @@ -417,6 +439,9 @@ export function getSettings(ctx: ExtensionContext): Settings { youtrackBaseUrl, youtrackProjectShortName, youtrackCloseCommand, + defaultBrainstormProfilePath, + defaultImplementProfilePath, + defaultTestProfilePath, conflictResolutionProfilePath, commitProfilePath, prDiffSummaryProfilePath, diff --git a/vscode/webview-ui/src/App.tsx b/vscode/webview-ui/src/App.tsx index 16e2e29..8ef8070 100644 --- a/vscode/webview-ui/src/App.tsx +++ b/vscode/webview-ui/src/App.tsx @@ -544,6 +544,9 @@ export function App() { initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''} initialImplTabPostCloseScript={settings?.implTabPostCloseScript ?? ''} initialSplitNewTerminals={settings?.splitNewTerminals ?? true} + initialDefaultBrainstormProfilePath={settings?.defaultBrainstormProfilePath ?? ''} + initialDefaultImplementProfilePath={settings?.defaultImplementProfilePath ?? ''} + initialDefaultTestProfilePath={settings?.defaultTestProfilePath ?? ''} initialConflictResolutionProfilePath={settings?.conflictResolutionProfilePath ?? ''} initialCommitProfilePath={settings?.commitProfilePath ?? ''} initialPrDiffSummaryProfilePath={settings?.prDiffSummaryProfilePath ?? ''} diff --git a/vscode/webview-ui/src/components/SettingsModal.tsx b/vscode/webview-ui/src/components/SettingsModal.tsx index 737af49..e790a7c 100644 --- a/vscode/webview-ui/src/components/SettingsModal.tsx +++ b/vscode/webview-ui/src/components/SettingsModal.tsx @@ -37,6 +37,9 @@ interface SubmitValues { implTabPreCreateScript: string implTabPostCloseScript: string splitNewTerminals: boolean + defaultBrainstormProfilePath: string + defaultImplementProfilePath: string + defaultTestProfilePath: string conflictResolutionProfilePath: string commitProfilePath: string prDiffSummaryProfilePath: string @@ -87,6 +90,9 @@ export interface SettingsModalProps { initialImplTabPreCreateScript: string initialImplTabPostCloseScript: string initialSplitNewTerminals: boolean + initialDefaultBrainstormProfilePath: string + initialDefaultImplementProfilePath: string + initialDefaultTestProfilePath: string initialConflictResolutionProfilePath: string initialCommitProfilePath: string initialPrDiffSummaryProfilePath: string @@ -173,6 +179,9 @@ export function SettingsModal({ initialImplTabPreCreateScript, initialImplTabPostCloseScript, initialSplitNewTerminals, + initialDefaultBrainstormProfilePath, + initialDefaultImplementProfilePath, + initialDefaultTestProfilePath, initialConflictResolutionProfilePath, initialCommitProfilePath, initialPrDiffSummaryProfilePath, @@ -211,6 +220,9 @@ export function SettingsModal({ const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript) const [implTabPostCloseScript, setImplTabPostCloseScript] = useState(initialImplTabPostCloseScript) const [splitNewTerminals, setSplitNewTerminals] = useState(initialSplitNewTerminals) + const [defaultBrainstormProfilePath, setDefaultBrainstormProfilePath] = useState(initialDefaultBrainstormProfilePath) + const [defaultImplementProfilePath, setDefaultImplementProfilePath] = useState(initialDefaultImplementProfilePath) + const [defaultTestProfilePath, setDefaultTestProfilePath] = useState(initialDefaultTestProfilePath) const [conflictResolutionProfilePath, setConflictResolutionProfilePath] = useState(initialConflictResolutionProfilePath) const [commitProfilePath, setCommitProfilePath] = useState(initialCommitProfilePath) const [prDiffSummaryProfilePath, setPrDiffSummaryProfilePath] = useState(initialPrDiffSummaryProfilePath) @@ -251,6 +263,9 @@ export function SettingsModal({ setImplTabPreCreateScript(initialImplTabPreCreateScript) setImplTabPostCloseScript(initialImplTabPostCloseScript) setSplitNewTerminals(initialSplitNewTerminals) + setDefaultBrainstormProfilePath(initialDefaultBrainstormProfilePath) + setDefaultImplementProfilePath(initialDefaultImplementProfilePath) + setDefaultTestProfilePath(initialDefaultTestProfilePath) setConflictResolutionProfilePath(initialConflictResolutionProfilePath) setCommitProfilePath(initialCommitProfilePath) setPrDiffSummaryProfilePath(initialPrDiffSummaryProfilePath) @@ -342,6 +357,10 @@ export function SettingsModal({ implTabPreCreateScript: implTabPreCreateScript.trim(), implTabPostCloseScript: implTabPostCloseScript.trim(), splitNewTerminals, + // 三个会话默认 profile:空串 = 落到 DEFAULT_PROFILE_PATH + defaultBrainstormProfilePath: defaultBrainstormProfilePath.trim(), + defaultImplementProfilePath: defaultImplementProfilePath.trim(), + defaultTestProfilePath: defaultTestProfilePath.trim(), // 冲突解决 profile:空串 = 用 DEFAULT_PROFILE_PATH conflictResolutionProfilePath: conflictResolutionProfilePath.trim(), // 提交代码 profile:空串 = 未配置 → 提交严格失败 @@ -528,6 +547,78 @@ export function SettingsModal({ /> + + 新建工单 / 续聊的头脑风暴会话默认用此 profile;工单详情面板里的「头脑风暴配置文件」优先级更高。留空用 official。 + + )} + > + ({ value: p.path, label: p.name })), + ...(defaultBrainstormProfilePath + && !profiles.some(p => p.path === defaultBrainstormProfilePath) + ? [{ value: defaultBrainstormProfilePath, label: `自定义:${defaultBrainstormProfilePath}` }] + : []), + ]} + /> + + + + 实施会话默认用此 profile;工单详情面板里的「实施配置文件」优先级更高。留空用 official。 + + )} + > + ({ value: p.path, label: p.name })), + ...(defaultImplementProfilePath + && !profiles.some(p => p.path === defaultImplementProfilePath) + ? [{ value: defaultImplementProfilePath, label: `自定义:${defaultImplementProfilePath}` }] + : []), + ]} + /> + + + + 测试会话默认用此 profile;工单详情面板里的「测试配置文件」优先级更高。留空用 official。 + + )} + > + ({ value: p.path, label: p.name })), + ...(defaultTestProfilePath + && !profiles.some(p => p.path === defaultTestProfilePath) + ? [{ value: defaultTestProfilePath, label: `自定义:${defaultTestProfilePath}` }] + : []), + ]} + /> + +