Worktree をプロジェクト外に作成するよう変更
- config.yaml に worktree_dir 設定を追加
- デフォルトは ../{tree-name}(プロジェクトの兄弟ディレクトリ)
- Claude Code のプロジェクト検出問題を回避
This commit is contained in:
parent
f83b826a3d
commit
0ecbf6e56b
@ -35,6 +35,7 @@ export function loadGlobalConfig(): GlobalConfig {
|
|||||||
enabled: parsed.debug.enabled,
|
enabled: parsed.debug.enabled,
|
||||||
logFile: parsed.debug.log_file,
|
logFile: parsed.debug.log_file,
|
||||||
} : undefined,
|
} : undefined,
|
||||||
|
worktreeDir: parsed.worktree_dir,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +58,9 @@ export function saveGlobalConfig(config: GlobalConfig): void {
|
|||||||
log_file: config.debug.logFile,
|
log_file: config.debug.logFile,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (config.worktreeDir) {
|
||||||
|
raw.worktree_dir = config.worktreeDir;
|
||||||
|
}
|
||||||
writeFileSync(configPath, stringifyYaml(raw), 'utf-8');
|
writeFileSync(configPath, stringifyYaml(raw), 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -127,6 +127,8 @@ export const GlobalConfigSchema = z.object({
|
|||||||
provider: z.enum(['claude', 'codex', 'mock']).optional().default('claude'),
|
provider: z.enum(['claude', 'codex', 'mock']).optional().default('claude'),
|
||||||
model: z.string().optional(),
|
model: z.string().optional(),
|
||||||
debug: DebugConfigSchema.optional(),
|
debug: DebugConfigSchema.optional(),
|
||||||
|
/** Directory for worktrees. If empty, uses ../{tree-name} relative to project */
|
||||||
|
worktree_dir: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Project config schema */
|
/** Project config schema */
|
||||||
|
|||||||
@ -160,6 +160,8 @@ export interface GlobalConfig {
|
|||||||
provider?: 'claude' | 'codex' | 'mock';
|
provider?: 'claude' | 'codex' | 'mock';
|
||||||
model?: string;
|
model?: string;
|
||||||
debug?: DebugConfig;
|
debug?: DebugConfig;
|
||||||
|
/** Directory for worktrees. If empty, uses ../{tree-name} relative to project */
|
||||||
|
worktreeDir?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Project-level configuration */
|
/** Project-level configuration */
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import * as path from 'node:path';
|
|||||||
import { execFileSync } from 'node:child_process';
|
import { execFileSync } from 'node:child_process';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
import { slugify } from '../utils/slug.js';
|
import { slugify } from '../utils/slug.js';
|
||||||
import { isPathSafe } from '../config/paths.js';
|
import { loadGlobalConfig } from '../config/globalConfig.js';
|
||||||
|
|
||||||
const log = createLogger('worktree');
|
const log = createLogger('worktree');
|
||||||
|
|
||||||
@ -37,29 +37,45 @@ function generateTimestamp(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the worktree path based on options.
|
* Resolve the worktree path based on options and global config.
|
||||||
* Validates that custom paths stay within the project directory.
|
|
||||||
*
|
*
|
||||||
* @throws Error if a custom path escapes projectDir (path traversal)
|
* Priority:
|
||||||
|
* 1. Custom path in options.worktree (string)
|
||||||
|
* 2. worktree_dir from config.yaml (if set)
|
||||||
|
* 3. Default: ../{tree-name} (outside project to avoid Claude Code project detection issues)
|
||||||
*/
|
*/
|
||||||
function resolveWorktreePath(projectDir: string, options: WorktreeOptions): string {
|
function resolveWorktreePath(projectDir: string, options: WorktreeOptions): string {
|
||||||
|
const timestamp = generateTimestamp();
|
||||||
|
const slug = slugify(options.taskSlug);
|
||||||
|
const dirName = slug ? `${timestamp}-${slug}` : timestamp;
|
||||||
|
|
||||||
|
// Custom path specified in task options
|
||||||
if (typeof options.worktree === 'string') {
|
if (typeof options.worktree === 'string') {
|
||||||
const resolved = path.isAbsolute(options.worktree)
|
const resolved = path.isAbsolute(options.worktree)
|
||||||
? options.worktree
|
? options.worktree
|
||||||
: path.resolve(projectDir, options.worktree);
|
: path.resolve(projectDir, options.worktree);
|
||||||
|
|
||||||
if (!isPathSafe(projectDir, resolved)) {
|
|
||||||
throw new Error(`Worktree path escapes project directory: ${options.worktree}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
// worktree: true → .takt/worktrees/{timestamp}-{task-slug}/
|
// Load config to check for worktree_dir setting
|
||||||
const timestamp = generateTimestamp();
|
let worktreeBaseDir: string | undefined;
|
||||||
const slug = slugify(options.taskSlug);
|
try {
|
||||||
const dirName = slug ? `${timestamp}-${slug}` : timestamp;
|
const globalConfig = loadGlobalConfig();
|
||||||
return path.join(projectDir, '.takt', 'worktrees', dirName);
|
worktreeBaseDir = globalConfig.worktreeDir;
|
||||||
|
} catch {
|
||||||
|
// Config not found, use default
|
||||||
|
}
|
||||||
|
|
||||||
|
if (worktreeBaseDir) {
|
||||||
|
// Use configured worktree directory
|
||||||
|
const resolved = path.isAbsolute(worktreeBaseDir)
|
||||||
|
? worktreeBaseDir
|
||||||
|
: path.resolve(projectDir, worktreeBaseDir);
|
||||||
|
return path.join(resolved, dirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: ../{tree-name} (sibling to project directory)
|
||||||
|
return path.join(projectDir, '..', dirName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user