diff --git a/src/task/worktree.ts b/src/task/worktree.ts index bcf86b9..4caedbc 100644 --- a/src/task/worktree.ts +++ b/src/task/worktree.ts @@ -38,9 +38,9 @@ function generateTimestamp(): string { /** * 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 { if (typeof options.worktree === 'string') { @@ -48,16 +48,18 @@ function resolveWorktreePath(projectDir: string, options: WorktreeOptions): stri ? options.worktree : path.resolve(projectDir, options.worktree); + if (!isPathSafe(projectDir, resolved)) { + throw new Error(`Worktree path escapes project directory: ${options.worktree}`); + } + return resolved; } - // worktree: true → sibling directory: ../{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. + // worktree: true → .takt/worktrees/{timestamp}-{task-slug}/ const timestamp = generateTimestamp(); const slug = slugify(options.taskSlug); const dirName = slug ? `${timestamp}-${slug}` : timestamp; - return path.join(path.dirname(projectDir), dirName); + return path.join(projectDir, '.takt', 'worktrees', dirName); } /** diff --git a/src/workflow/instruction-builder.ts b/src/workflow/instruction-builder.ts index 1a22dcd..44562c4 100644 --- a/src/workflow/instruction-builder.ts +++ b/src/workflow/instruction-builder.ts @@ -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(''); + 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'); } @@ -145,14 +147,15 @@ export function buildInstruction( 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 if (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; }