🔧 fix(vscode): worktree 目录配置改用插件自带设置面板,撤销 VS Code 原生配置

上一版误把 worktree 目录模板注册成 VS Code 原生设置(contributes.configuration
+ workspace.getConfiguration),但本插件有自己的设置面板(SettingsModal,存 globalState)。
改为:
- Settings 新增 worktreeDirectory 字段(默认 ~/Sources/worktree/$project_name/$feature_name,
  留空回退默认),贯穿 store/handler/两端 messages/useIssues/App/SettingsModal
- SettingsModal「钩子」分组新增「工作树目录」输入项,走既有 save/load 流程
- handleImplement 改从 getSettings(panel.context).worktreeDirectory 读模板
- 删除 package.json 的 contributes.configuration(meta.ts 为 gitignore 生成物,已本地还原)

deriveSlug / resolveWorktreeDir / ~ 展开 / 绝对路径 / feature/<slug> 逻辑不变。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 02:56:33 +08:00
co-authored by Claude Opus 4.7
parent 11ea6b345c
commit 9a69aeed48
10 changed files with 74 additions and 13 deletions
+1 -11
View File
@@ -2,7 +2,7 @@
"publisher": "clurdra",
"name": "superpowers-vscode-clurdra",
"displayName": "Superpowers-clurdra",
"version": "0.2.43",
"version": "0.2.44",
"packageManager": "pnpm@10.27.0",
"description": "Superpowers specs and plans Kanban explorer",
"author": "clurdra",
@@ -93,16 +93,6 @@
"group": "navigation"
}
]
},
"configuration": {
"title": "Superpowers",
"properties": {
"superpowers.worktreeDirectory": {
"type": "string",
"default": "~/Sources/worktree/$project_name/$feature_name",
"markdownDescription": "运行 Plan 时创建 worktree 的目录模板。支持占位符 `$project_root`(工作区根目录的绝对路径)、`$project_name`(工作区目录名)、`$feature_name`spec/plan 的 slug)。开头的 `~` 会展开为当前用户主目录。默认会把 worktree 建到 `~/Sources/worktree/<项目名>/<slug>`。"
}
}
}
},
"scripts": {
+2
View File
@@ -840,6 +840,7 @@ export class KanbanWebviewPanel {
reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript,
@@ -899,6 +900,7 @@ export class KanbanWebviewPanel {
reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript,
+3 -2
View File
@@ -684,8 +684,9 @@ export async function handleImplement(
// worktree 目录名用 spec/plan 的 slug(可读、与 spec 目录同名),不再用 planFile 的 hash。
const slug = deriveSlug(planFile)
const branch = `feature/${slug}`
// 目录走 superpowers.worktreeDirectory 模板(设置里可改),默认 ~/Sources/worktree/<项目>/<slug>。
const worktreeTemplate = workspace.getConfiguration('superpowers').get<string>('worktreeDirectory')
// 目录模板取自插件自己的设置面板(globalState),不是 VS Code 原生配置;
// 留空时回退到默认 ~/Sources/worktree/<项目>/<slug>。
const worktreeTemplate = getSettings(panel.context).worktreeDirectory
|| '~/Sources/worktree/$project_name/$feature_name'
const worktreePath = resolveWorktreeDir(worktreeTemplate, workspaceRoot, slug)
+7
View File
@@ -25,6 +25,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -43,6 +44,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
const trimmedYtToken = payload.youtrackToken.trim()
const trimmedDevBranch = payload.devBranch.trim()
const trimmedAutoBuildBranch = payload.autoBuildBranch.trim()
// worktree dir template: '' = "use default", so don't preserve blanks
// specially — getSettings falls back to the default at read time.
const trimmedWorktreeDir = payload.worktreeDirectory.trim()
// Hook script paths: keep '' meaningful (= "use default
// .spx/*.sh"). Just strip whitespace.
const trimmedPostCreate = payload.worktreePostCreateScript.trim()
@@ -71,6 +75,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
reviewPrompt: payload.reviewPrompt || prev.reviewPrompt,
devBranch: trimmedDevBranch || prev.devBranch,
autoBuildBranch: trimmedAutoBuildBranch,
worktreeDirectory: trimmedWorktreeDir || prev.worktreeDirectory,
worktreePostCreateScript: trimmedPostCreate,
worktreePreRemoveScript: trimmedPreRemove,
implTabPreCreateScript: trimmedImplPre,
@@ -89,6 +94,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
// fallback at read time.
devBranch: trimmedDevBranch,
autoBuildBranch: trimmedAutoBuildBranch,
worktreeDirectory: trimmedWorktreeDir,
worktreePostCreateScript: trimmedPostCreate,
worktreePreRemoveScript: trimmedPreRemove,
implTabPreCreateScript: trimmedImplPre,
@@ -168,6 +174,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript,
+2
View File
@@ -61,6 +61,7 @@ export type ExtensionToWebview
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -115,6 +116,7 @@ export type WebviewToExtension
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
+15
View File
@@ -86,6 +86,14 @@ export interface Settings {
* disabled because there's nothing to fast-forward.
*/
autoBuildBranch: string
/**
* Directory template for the worktree created when implementing a plan.
* Placeholders: `$project_root` (workspace abs path), `$project_name` (its
* basename), `$feature_name` (the spec/plan slug). A leading `~` expands to
* the user's home dir. Empty string means "use the default
* `~/Sources/worktree/$project_name/$feature_name`".
*/
worktreeDirectory: string
/**
* Path to a user-provided shell script that runs *after* the extension
* creates a worktree via `git worktree add`. Empty string means use the
@@ -157,6 +165,7 @@ function defaults(ctx: ExtensionContext): Settings {
reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'),
devBranch: 'main',
autoBuildBranch: '',
worktreeDirectory: '~/Sources/worktree/$project_name/$feature_name',
worktreePostCreateScript: '',
worktreePreRemoveScript: '',
implTabPreCreateScript: '',
@@ -200,6 +209,11 @@ export function getSettings(ctx: ExtensionContext): Settings {
const autoBuildBranch = typeof stored.autoBuildBranch === 'string'
? stored.autoBuildBranch
: base.autoBuildBranch
// worktree dir template: empty/blank means "use the default template", so
// fall back to base (unlike the hook paths below, where '' is meaningful).
const worktreeDirectory = typeof stored.worktreeDirectory === 'string' && stored.worktreeDirectory.length > 0
? stored.worktreeDirectory
: base.worktreeDirectory
// worktree hook script paths: '' is meaningful (= "use default
// .spx/worktree-*.sh"), so don't coerce. Non-string legacy values fall
// back to ''. The hook runner resolves '' to the default at execution
@@ -238,6 +252,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
reviewPrompt,
devBranch,
autoBuildBranch,
worktreeDirectory,
worktreePostCreateScript,
worktreePreRemoveScript,
implTabPreCreateScript,
+1
View File
@@ -445,6 +445,7 @@ export function App() {
initialReviewPrompt={settings?.reviewPrompt ?? ''}
initialDevBranch={settings?.devBranch ?? 'main'}
initialAutoBuildBranch={settings?.autoBuildBranch ?? ''}
initialWorktreeDirectory={settings?.worktreeDirectory ?? ''}
initialWorktreePostCreateScript={settings?.worktreePostCreateScript ?? ''}
initialWorktreePreRemoveScript={settings?.worktreePreRemoveScript ?? ''}
initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''}
@@ -23,6 +23,7 @@ interface SubmitValues {
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -59,6 +60,7 @@ export interface SettingsModalProps {
initialReviewPrompt: string
initialDevBranch: string
initialAutoBuildBranch: string
initialWorktreeDirectory: string
initialWorktreePostCreateScript: string
initialWorktreePreRemoveScript: string
initialImplTabPreCreateScript: string
@@ -128,6 +130,7 @@ export function SettingsModal({
initialReviewPrompt,
initialDevBranch,
initialAutoBuildBranch,
initialWorktreeDirectory,
initialWorktreePostCreateScript,
initialWorktreePreRemoveScript,
initialImplTabPreCreateScript,
@@ -151,6 +154,7 @@ export function SettingsModal({
const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt)
const [devBranch, setDevBranch] = useState(initialDevBranch)
const [autoBuildBranch, setAutoBuildBranch] = useState(initialAutoBuildBranch)
const [worktreeDirectory, setWorktreeDirectory] = useState(initialWorktreeDirectory)
const [worktreePostCreateScript, setWorktreePostCreateScript] = useState(initialWorktreePostCreateScript)
const [worktreePreRemoveScript, setWorktreePreRemoveScript] = useState(initialWorktreePreRemoveScript)
const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript)
@@ -252,6 +256,8 @@ export function SettingsModal({
reviewPrompt,
devBranch: devBranch.trim(),
autoBuildBranch: autoBuildBranch.trim(),
// worktree 目录模板:trim 后留空 = 用默认模板(getSettings 读时回退)
worktreeDirectory: worktreeDirectory.trim(),
// 钩子路径保持原样 trim(前后空格无意义;空串 = 用默认 .spx/*.sh
worktreePostCreateScript: worktreePostCreateScript.trim(),
worktreePreRemoveScript: worktreePreRemoveScript.trim(),
@@ -605,6 +611,37 @@ export function SettingsModal({
{activeGroup === 'hooks' && (
<>
<Field
label="工作树目录"
hint={(
<>
Plan worktree
{' '}
<code>$project_root</code>
<code>$project_name</code>
<code>$feature_name</code>
spec/plan slug
{' '}
<code>~</code>
{' '}
=
{' '}
<code>~/Sources/worktree/$project_name/$feature_name</code>
</>
)}
>
<input
type="text"
value={worktreeDirectory}
onChange={e => setWorktreeDirectory(e.target.value)}
placeholder="~/Sources/worktree/$project_name/$feature_name"
className={inputClass}
/>
</Field>
<Field
label="worktree 创建后脚本"
hint={(
+4
View File
@@ -33,6 +33,7 @@ export interface SettingsValues {
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -55,6 +56,7 @@ export interface SettingsOverlayState {
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -265,6 +267,7 @@ export function useIssues(): UseIssuesResult {
reviewPrompt: values.reviewPrompt,
devBranch: values.devBranch,
autoBuildBranch: values.autoBuildBranch,
worktreeDirectory: values.worktreeDirectory,
worktreePostCreateScript: values.worktreePostCreateScript,
worktreePreRemoveScript: values.worktreePreRemoveScript,
implTabPreCreateScript: values.implTabPreCreateScript,
@@ -590,6 +593,7 @@ export function useIssues(): UseIssuesResult {
reviewPrompt: msg.reviewPrompt,
devBranch: msg.devBranch,
autoBuildBranch: msg.autoBuildBranch,
worktreeDirectory: msg.worktreeDirectory,
worktreePostCreateScript: msg.worktreePostCreateScript,
worktreePreRemoveScript: msg.worktreePreRemoveScript,
implTabPreCreateScript: msg.implTabPreCreateScript,
+2
View File
@@ -76,6 +76,7 @@ export type ExtensionToWebview
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string
@@ -130,6 +131,7 @@ export type WebviewToExtension
reviewPrompt: string
devBranch: string
autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string
worktreePreRemoveScript: string
implTabPreCreateScript: string