♻️ refactor(vscode): 抽出 worktreePath 模块(slug / 目录模板解析)
Claude-Session: https://claude.ai/code/session_011cEyL6k351U2BzX1Qmygph
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import type { Terminal } from 'vscode'
|
||||
import type { KanbanWebviewPanel } from '../KanbanPanel'
|
||||
import { execFile } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import * as fs from 'node:fs'
|
||||
import { promises as fsp } from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
@@ -16,6 +15,7 @@ import { resolveProfilePath } from '../../cc/profiles'
|
||||
import { getBrainstormContinuePrompt, getImplementPlanPrompt } from '../../cc/prompts'
|
||||
import { projectsDirFor, watchForNewSession } from '../../cc/sessionWatcher'
|
||||
import { detectRepo } from '../../git/remote'
|
||||
import { DEFAULT_WORKTREE_TEMPLATE, deriveSlug, resolveWorktreeDir } from '../../git/worktreePath'
|
||||
import { ensureWorktree, resolveWorktreePath } from '../../git/worktree'
|
||||
import { updateIssueAssignees } from '../../gitea/api'
|
||||
import { logger } from '../../logging/logger'
|
||||
@@ -634,50 +634,9 @@ async function triggerAutoReviewTabUnlocked(panel: KanbanWebviewPanel, opts: {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a human-readable slug from a spec/plan file path.
|
||||
*
|
||||
* spec/plan live as directories on disk
|
||||
* (docs/superpowers/{specs,plans}/<slug>/{spec,plan}.md), so the slug is the
|
||||
* path segment right after the `specs`/`plans` segment — that's the name the
|
||||
* user sees in the tree. Falls back to the parent dir name, then to a stable
|
||||
* 8-char sha256 of the path so the worktree dir name is never empty. Paths
|
||||
* come from gitea body comments and use `/` separators.
|
||||
*/
|
||||
function deriveSlug(planFile: string): string {
|
||||
const segments = planFile.split('/').filter(Boolean)
|
||||
const anchor = segments.findIndex(s => s === 'plans' || s === 'specs')
|
||||
if (anchor >= 0 && segments[anchor + 1]) {
|
||||
return segments[anchor + 1]
|
||||
}
|
||||
const parent = path.basename(path.dirname(planFile))
|
||||
if (parent && parent !== '.' && parent !== '/') {
|
||||
return parent
|
||||
}
|
||||
return createHash('sha256').update(planFile).digest('hex').slice(0, 8)
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the `superpowers.worktreeDirectory` template into an ABSOLUTE path.
|
||||
* Placeholders: $project_root (workspace abs path), $project_name (its
|
||||
* basename), $feature_name (the slug). A leading `~` is expanded here because
|
||||
* git/execFile run without a shell and won't do tilde expansion themselves.
|
||||
* The result is resolved to absolute since the worktree now lives outside the
|
||||
* workspace and is stored/compared as an absolute path downstream.
|
||||
*/
|
||||
function resolveWorktreeDir(template: string, workspaceRoot: string, slug: string): string {
|
||||
let p = template
|
||||
.split('$project_name')
|
||||
.join(path.basename(workspaceRoot))
|
||||
.split('$project_root')
|
||||
.join(workspaceRoot)
|
||||
.split('$feature_name')
|
||||
.join(slug)
|
||||
if (p.startsWith('~')) {
|
||||
p = path.join(os.homedir(), p.slice(1))
|
||||
}
|
||||
return path.resolve(p)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Kick off the end-to-end "implement this plan" flow:
|
||||
@@ -766,7 +725,7 @@ export async function handleImplement(
|
||||
// 目录模板取自插件自己的设置面板(globalState),不是 VS Code 原生配置;
|
||||
// 留空时回退到默认 ~/Sources/worktree/<项目>/<slug>。
|
||||
const worktreeTemplate = getSettings(panel.context).worktreeDirectory
|
||||
|| '~/Sources/worktree/$project_name/$feature_name'
|
||||
|| DEFAULT_WORKTREE_TEMPLATE
|
||||
let worktreePath = resolveWorktreeDir(worktreeTemplate, workspaceRoot, slug)
|
||||
|
||||
// 工单级 profilePath > 默认 fallback。工单的 profile 锁定可在工单详情面板里覆盖。
|
||||
|
||||
Reference in New Issue
Block a user