🔧 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", "publisher": "clurdra",
"name": "superpowers-vscode-clurdra", "name": "superpowers-vscode-clurdra",
"displayName": "Superpowers-clurdra", "displayName": "Superpowers-clurdra",
"version": "0.2.43", "version": "0.2.44",
"packageManager": "pnpm@10.27.0", "packageManager": "pnpm@10.27.0",
"description": "Superpowers specs and plans Kanban explorer", "description": "Superpowers specs and plans Kanban explorer",
"author": "clurdra", "author": "clurdra",
@@ -93,16 +93,6 @@
"group": "navigation" "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": { "scripts": {
+2
View File
@@ -840,6 +840,7 @@ export class KanbanWebviewPanel {
reviewPrompt: s.reviewPrompt, reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch, devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch, autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript, worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript, worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript, implTabPreCreateScript: s.implTabPreCreateScript,
@@ -899,6 +900,7 @@ export class KanbanWebviewPanel {
reviewPrompt: s.reviewPrompt, reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch, devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch, autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript, worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript, worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript, implTabPreCreateScript: s.implTabPreCreateScript,
+3 -2
View File
@@ -684,8 +684,9 @@ export async function handleImplement(
// worktree 目录名用 spec/plan 的 slug(可读、与 spec 目录同名),不再用 planFile 的 hash。 // worktree 目录名用 spec/plan 的 slug(可读、与 spec 目录同名),不再用 planFile 的 hash。
const slug = deriveSlug(planFile) const slug = deriveSlug(planFile)
const branch = `feature/${slug}` const branch = `feature/${slug}`
// 目录走 superpowers.worktreeDirectory 模板(设置里可改),默认 ~/Sources/worktree/<项目>/<slug>。 // 目录模板取自插件自己的设置面板(globalState),不是 VS Code 原生配置;
const worktreeTemplate = workspace.getConfiguration('superpowers').get<string>('worktreeDirectory') // 留空时回退到默认 ~/Sources/worktree/<项目>/<slug>。
const worktreeTemplate = getSettings(panel.context).worktreeDirectory
|| '~/Sources/worktree/$project_name/$feature_name' || '~/Sources/worktree/$project_name/$feature_name'
const worktreePath = resolveWorktreeDir(worktreeTemplate, workspaceRoot, slug) const worktreePath = resolveWorktreeDir(worktreeTemplate, workspaceRoot, slug)
+7
View File
@@ -25,6 +25,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -43,6 +44,9 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
const trimmedYtToken = payload.youtrackToken.trim() const trimmedYtToken = payload.youtrackToken.trim()
const trimmedDevBranch = payload.devBranch.trim() const trimmedDevBranch = payload.devBranch.trim()
const trimmedAutoBuildBranch = payload.autoBuildBranch.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 // Hook script paths: keep '' meaningful (= "use default
// .spx/*.sh"). Just strip whitespace. // .spx/*.sh"). Just strip whitespace.
const trimmedPostCreate = payload.worktreePostCreateScript.trim() const trimmedPostCreate = payload.worktreePostCreateScript.trim()
@@ -71,6 +75,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
reviewPrompt: payload.reviewPrompt || prev.reviewPrompt, reviewPrompt: payload.reviewPrompt || prev.reviewPrompt,
devBranch: trimmedDevBranch || prev.devBranch, devBranch: trimmedDevBranch || prev.devBranch,
autoBuildBranch: trimmedAutoBuildBranch, autoBuildBranch: trimmedAutoBuildBranch,
worktreeDirectory: trimmedWorktreeDir || prev.worktreeDirectory,
worktreePostCreateScript: trimmedPostCreate, worktreePostCreateScript: trimmedPostCreate,
worktreePreRemoveScript: trimmedPreRemove, worktreePreRemoveScript: trimmedPreRemove,
implTabPreCreateScript: trimmedImplPre, implTabPreCreateScript: trimmedImplPre,
@@ -89,6 +94,7 @@ export async function handleSettingsSave(panel: KanbanWebviewPanel, payload: {
// fallback at read time. // fallback at read time.
devBranch: trimmedDevBranch, devBranch: trimmedDevBranch,
autoBuildBranch: trimmedAutoBuildBranch, autoBuildBranch: trimmedAutoBuildBranch,
worktreeDirectory: trimmedWorktreeDir,
worktreePostCreateScript: trimmedPostCreate, worktreePostCreateScript: trimmedPostCreate,
worktreePreRemoveScript: trimmedPreRemove, worktreePreRemoveScript: trimmedPreRemove,
implTabPreCreateScript: trimmedImplPre, implTabPreCreateScript: trimmedImplPre,
@@ -168,6 +174,7 @@ export async function handleEditSettingsRequest(panel: KanbanWebviewPanel): Prom
reviewPrompt: s.reviewPrompt, reviewPrompt: s.reviewPrompt,
devBranch: s.devBranch, devBranch: s.devBranch,
autoBuildBranch: s.autoBuildBranch, autoBuildBranch: s.autoBuildBranch,
worktreeDirectory: s.worktreeDirectory,
worktreePostCreateScript: s.worktreePostCreateScript, worktreePostCreateScript: s.worktreePostCreateScript,
worktreePreRemoveScript: s.worktreePreRemoveScript, worktreePreRemoveScript: s.worktreePreRemoveScript,
implTabPreCreateScript: s.implTabPreCreateScript, implTabPreCreateScript: s.implTabPreCreateScript,
+2
View File
@@ -61,6 +61,7 @@ export type ExtensionToWebview
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -115,6 +116,7 @@ export type WebviewToExtension
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
+15
View File
@@ -86,6 +86,14 @@ export interface Settings {
* disabled because there's nothing to fast-forward. * disabled because there's nothing to fast-forward.
*/ */
autoBuildBranch: string 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 * Path to a user-provided shell script that runs *after* the extension
* creates a worktree via `git worktree add`. Empty string means use the * 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'), reviewPrompt: readDefaultPrompt(ctx.extensionPath, 'review'),
devBranch: 'main', devBranch: 'main',
autoBuildBranch: '', autoBuildBranch: '',
worktreeDirectory: '~/Sources/worktree/$project_name/$feature_name',
worktreePostCreateScript: '', worktreePostCreateScript: '',
worktreePreRemoveScript: '', worktreePreRemoveScript: '',
implTabPreCreateScript: '', implTabPreCreateScript: '',
@@ -200,6 +209,11 @@ export function getSettings(ctx: ExtensionContext): Settings {
const autoBuildBranch = typeof stored.autoBuildBranch === 'string' const autoBuildBranch = typeof stored.autoBuildBranch === 'string'
? stored.autoBuildBranch ? stored.autoBuildBranch
: base.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 // worktree hook script paths: '' is meaningful (= "use default
// .spx/worktree-*.sh"), so don't coerce. Non-string legacy values fall // .spx/worktree-*.sh"), so don't coerce. Non-string legacy values fall
// back to ''. The hook runner resolves '' to the default at execution // back to ''. The hook runner resolves '' to the default at execution
@@ -238,6 +252,7 @@ export function getSettings(ctx: ExtensionContext): Settings {
reviewPrompt, reviewPrompt,
devBranch, devBranch,
autoBuildBranch, autoBuildBranch,
worktreeDirectory,
worktreePostCreateScript, worktreePostCreateScript,
worktreePreRemoveScript, worktreePreRemoveScript,
implTabPreCreateScript, implTabPreCreateScript,
+1
View File
@@ -445,6 +445,7 @@ export function App() {
initialReviewPrompt={settings?.reviewPrompt ?? ''} initialReviewPrompt={settings?.reviewPrompt ?? ''}
initialDevBranch={settings?.devBranch ?? 'main'} initialDevBranch={settings?.devBranch ?? 'main'}
initialAutoBuildBranch={settings?.autoBuildBranch ?? ''} initialAutoBuildBranch={settings?.autoBuildBranch ?? ''}
initialWorktreeDirectory={settings?.worktreeDirectory ?? ''}
initialWorktreePostCreateScript={settings?.worktreePostCreateScript ?? ''} initialWorktreePostCreateScript={settings?.worktreePostCreateScript ?? ''}
initialWorktreePreRemoveScript={settings?.worktreePreRemoveScript ?? ''} initialWorktreePreRemoveScript={settings?.worktreePreRemoveScript ?? ''}
initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''} initialImplTabPreCreateScript={settings?.implTabPreCreateScript ?? ''}
@@ -23,6 +23,7 @@ interface SubmitValues {
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -59,6 +60,7 @@ export interface SettingsModalProps {
initialReviewPrompt: string initialReviewPrompt: string
initialDevBranch: string initialDevBranch: string
initialAutoBuildBranch: string initialAutoBuildBranch: string
initialWorktreeDirectory: string
initialWorktreePostCreateScript: string initialWorktreePostCreateScript: string
initialWorktreePreRemoveScript: string initialWorktreePreRemoveScript: string
initialImplTabPreCreateScript: string initialImplTabPreCreateScript: string
@@ -128,6 +130,7 @@ export function SettingsModal({
initialReviewPrompt, initialReviewPrompt,
initialDevBranch, initialDevBranch,
initialAutoBuildBranch, initialAutoBuildBranch,
initialWorktreeDirectory,
initialWorktreePostCreateScript, initialWorktreePostCreateScript,
initialWorktreePreRemoveScript, initialWorktreePreRemoveScript,
initialImplTabPreCreateScript, initialImplTabPreCreateScript,
@@ -151,6 +154,7 @@ export function SettingsModal({
const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt) const [reviewPrompt, setReviewPrompt] = useState(initialReviewPrompt)
const [devBranch, setDevBranch] = useState(initialDevBranch) const [devBranch, setDevBranch] = useState(initialDevBranch)
const [autoBuildBranch, setAutoBuildBranch] = useState(initialAutoBuildBranch) const [autoBuildBranch, setAutoBuildBranch] = useState(initialAutoBuildBranch)
const [worktreeDirectory, setWorktreeDirectory] = useState(initialWorktreeDirectory)
const [worktreePostCreateScript, setWorktreePostCreateScript] = useState(initialWorktreePostCreateScript) const [worktreePostCreateScript, setWorktreePostCreateScript] = useState(initialWorktreePostCreateScript)
const [worktreePreRemoveScript, setWorktreePreRemoveScript] = useState(initialWorktreePreRemoveScript) const [worktreePreRemoveScript, setWorktreePreRemoveScript] = useState(initialWorktreePreRemoveScript)
const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript) const [implTabPreCreateScript, setImplTabPreCreateScript] = useState(initialImplTabPreCreateScript)
@@ -252,6 +256,8 @@ export function SettingsModal({
reviewPrompt, reviewPrompt,
devBranch: devBranch.trim(), devBranch: devBranch.trim(),
autoBuildBranch: autoBuildBranch.trim(), autoBuildBranch: autoBuildBranch.trim(),
// worktree 目录模板:trim 后留空 = 用默认模板(getSettings 读时回退)
worktreeDirectory: worktreeDirectory.trim(),
// 钩子路径保持原样 trim(前后空格无意义;空串 = 用默认 .spx/*.sh // 钩子路径保持原样 trim(前后空格无意义;空串 = 用默认 .spx/*.sh
worktreePostCreateScript: worktreePostCreateScript.trim(), worktreePostCreateScript: worktreePostCreateScript.trim(),
worktreePreRemoveScript: worktreePreRemoveScript.trim(), worktreePreRemoveScript: worktreePreRemoveScript.trim(),
@@ -605,6 +611,37 @@ export function SettingsModal({
{activeGroup === 'hooks' && ( {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 <Field
label="worktree 创建后脚本" label="worktree 创建后脚本"
hint={( hint={(
+4
View File
@@ -33,6 +33,7 @@ export interface SettingsValues {
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -55,6 +56,7 @@ export interface SettingsOverlayState {
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -265,6 +267,7 @@ export function useIssues(): UseIssuesResult {
reviewPrompt: values.reviewPrompt, reviewPrompt: values.reviewPrompt,
devBranch: values.devBranch, devBranch: values.devBranch,
autoBuildBranch: values.autoBuildBranch, autoBuildBranch: values.autoBuildBranch,
worktreeDirectory: values.worktreeDirectory,
worktreePostCreateScript: values.worktreePostCreateScript, worktreePostCreateScript: values.worktreePostCreateScript,
worktreePreRemoveScript: values.worktreePreRemoveScript, worktreePreRemoveScript: values.worktreePreRemoveScript,
implTabPreCreateScript: values.implTabPreCreateScript, implTabPreCreateScript: values.implTabPreCreateScript,
@@ -590,6 +593,7 @@ export function useIssues(): UseIssuesResult {
reviewPrompt: msg.reviewPrompt, reviewPrompt: msg.reviewPrompt,
devBranch: msg.devBranch, devBranch: msg.devBranch,
autoBuildBranch: msg.autoBuildBranch, autoBuildBranch: msg.autoBuildBranch,
worktreeDirectory: msg.worktreeDirectory,
worktreePostCreateScript: msg.worktreePostCreateScript, worktreePostCreateScript: msg.worktreePostCreateScript,
worktreePreRemoveScript: msg.worktreePreRemoveScript, worktreePreRemoveScript: msg.worktreePreRemoveScript,
implTabPreCreateScript: msg.implTabPreCreateScript, implTabPreCreateScript: msg.implTabPreCreateScript,
+2
View File
@@ -76,6 +76,7 @@ export type ExtensionToWebview
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string
@@ -130,6 +131,7 @@ export type WebviewToExtension
reviewPrompt: string reviewPrompt: string
devBranch: string devBranch: string
autoBuildBranch: string autoBuildBranch: string
worktreeDirectory: string
worktreePostCreateScript: string worktreePostCreateScript: string
worktreePreRemoveScript: string worktreePreRemoveScript: string
implTabPreCreateScript: string implTabPreCreateScript: string