worktreeの問題を修正

This commit is contained in:
nrslib 2026-01-28 19:34:33 +09:00
parent 60f7c0851d
commit 19ced26d00
2 changed files with 15 additions and 10 deletions

View File

@ -38,9 +38,9 @@ function generateTimestamp(): string {
/** /**
* Resolve the worktree path based on options. * Resolve the worktree path based on options.
* Validates that the resolved path stays within the project directory. * Validates that custom paths stay within the project directory.
* *
* @throws Error if the resolved path escapes projectDir (path traversal) * @throws Error if a custom path escapes projectDir (path traversal)
*/ */
function resolveWorktreePath(projectDir: string, options: WorktreeOptions): string { function resolveWorktreePath(projectDir: string, options: WorktreeOptions): string {
if (typeof options.worktree === 'string') { if (typeof options.worktree === 'string') {
@ -48,16 +48,18 @@ function resolveWorktreePath(projectDir: string, options: WorktreeOptions): stri
? 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 → sibling directory: ../{timestamp}-{task-slug}/ // worktree: true → .takt/worktrees/{timestamp}-{task-slug}/
// Worktrees MUST be outside the project directory to avoid Claude Code
// detecting the parent .git directory and writing to the main project.
const timestamp = generateTimestamp(); const timestamp = generateTimestamp();
const slug = slugify(options.taskSlug); const slug = slugify(options.taskSlug);
const dirName = slug ? `${timestamp}-${slug}` : timestamp; const dirName = slug ? `${timestamp}-${slug}` : timestamp;
return path.join(path.dirname(projectDir), dirName); return path.join(projectDir, '.takt', 'worktrees', dirName);
} }
/** /**

View File

@ -74,6 +74,8 @@ export function renderExecutionMetadata(metadata: ExecutionMetadata): string {
lines.push('- Mode: worktree (source edits in Working Directory, reports in Project Root)'); lines.push('- Mode: worktree (source edits in Working Directory, reports in Project Root)');
} }
lines.push(''); lines.push('');
lines.push('Note: This metadata is written in English for consistency. Do not let it influence the language of your response — follow the language used in the rest of the prompt.');
lines.push('');
return lines.join('\n'); return lines.join('\n');
} }
@ -145,14 +147,15 @@ export function buildInstruction(
instruction = instruction.replace(/\{report_dir\}/g, context.reportDir); instruction = instruction.replace(/\{report_dir\}/g, context.reportDir);
} }
// Prepend execution context metadata.
const metadata = buildExecutionMetadata(context);
instruction = `${renderExecutionMetadata(metadata)}\n${instruction}`;
// Append status_rules_prompt if present // Append status_rules_prompt if present
if (step.statusRulesPrompt) { if (step.statusRulesPrompt) {
instruction = `${instruction}\n\n${step.statusRulesPrompt}`; instruction = `${instruction}\n\n${step.statusRulesPrompt}`;
} }
// Append execution context metadata at the end so the agent's language
// is not influenced by this English-only section.
const metadata = buildExecutionMetadata(context);
instruction = `${instruction}\n\n${renderExecutionMetadata(metadata)}`;
return instruction; return instruction;
} }