diff --git a/resources/global/en/agents/default/planner.md b/resources/global/en/agents/default/planner.md index d2ae5b5..48373c7 100644 --- a/resources/global/en/agents/default/planner.md +++ b/resources/global/en/agents/default/planner.md @@ -59,23 +59,8 @@ Determine the implementation direction: ``` [PLANNER:DONE] - -worktree: - baseBranch: {base branch name} - branchName: {new branch name} ``` -**baseBranch criteria:** -- New feature: `main` or `master` -- Existing feature modification: related feature branch (use `main` if unknown) -- Bug fix: relevant branch (use `main` if unknown) - -**branchName naming convention:** -- Feature addition: `add-{feature-name}` (e.g., `add-user-authentication`) -- Fix: `fix-{issue}` (e.g., `fix-login-error`) -- Refactor: `refactor-{target}` (e.g., `refactor-api-client`) -- Use lowercase English with hyphens - ### BLOCKED Output Structure ``` @@ -86,8 +71,6 @@ Clarifications needed: - {Question 2} ``` -**Note:** Do not output worktree settings when BLOCKED. - ## Important **Keep analysis simple.** Overly detailed plans are unnecessary. Provide enough direction for Coder to proceed with implementation. diff --git a/resources/global/ja/agents/default/planner.md b/resources/global/ja/agents/default/planner.md index bcd911a..990edea 100644 --- a/resources/global/ja/agents/default/planner.md +++ b/resources/global/ja/agents/default/planner.md @@ -59,23 +59,8 @@ ``` [PLANNER:DONE] - -worktree: - baseBranch: {元ブランチ名} - branchName: {新ブランチ名} ``` -**baseBranch判断基準:** -- 新機能開発: `main` または `master` -- 既存機能の修正: 関連するfeatureブランチ(不明な場合は `main`) -- バグ修正: 該当するブランチ(不明な場合は `main`) - -**branchName命名規則:** -- 機能追加: `add-{feature-name}` (例: `add-user-authentication`) -- 修正: `fix-{issue}` (例: `fix-login-error`) -- リファクタ: `refactor-{target}` (例: `refactor-api-client`) -- 英語・小文字・ハイフン区切りで記述 - ### BLOCKED時の出力構造 ``` @@ -86,8 +71,6 @@ worktree: - {質問2} ``` -**注意:** BLOCKEDの場合、worktree設定は出力しない。 - ## 重要 **シンプルに分析する。** 過度に詳細な計画は不要。Coderが実装を進められる程度の方向性を示す。 diff --git a/src/commands/workflowExecution.ts b/src/commands/workflowExecution.ts index cb7a67c..9b8c52d 100644 --- a/src/commands/workflowExecution.ts +++ b/src/commands/workflowExecution.ts @@ -22,7 +22,6 @@ import { } from '../utils/session.js'; import { createLogger } from '../utils/debug.js'; import { notifySuccess, notifyError } from '../utils/notification.js'; -import { createWorktree, type WorktreeInfo, type WorktreeConfig } from '../utils/worktree.js'; const log = createLogger('workflow'); @@ -30,8 +29,6 @@ const log = createLogger('workflow'); export interface WorkflowExecutionResult { success: boolean; reason?: string; - /** Worktree information if worktree mode was used */ - worktree?: WorktreeInfo; } /** Options for workflow execution */ @@ -44,10 +41,6 @@ export interface WorkflowExecutionOptions { /** * Execute a workflow and handle all events - * - * Worktree creation is determined by Planner: - * - If Planner outputs [PLANNER:DONE] with worktree config, a worktree is created - * - If Planner outputs [PLANNER:BLOCKED], no worktree is created */ export async function executeWorkflow( workflowConfig: WorkflowConfig, @@ -60,9 +53,6 @@ export async function executeWorkflow( headerPrefix = 'Running Workflow:', } = options; - // Worktree info will be set when Planner emits worktree config - let worktreeInfo: WorktreeInfo | undefined; - // Clear previous sessions if not resuming if (!resumeSession) { log.debug('Starting fresh session (clearing previous agent sessions)'); @@ -92,7 +82,6 @@ export async function executeWorkflow( const savedSessions = loadAgentSessions(cwd); // Session update handler - persist session IDs when they change - // Always use original cwd for .takt data (案C: worktreeはコード作業専用) const sessionUpdateHandler = (agentName: string, agentSessionId: string): void => { updateAgentSession(cwd, agentName, agentSessionId); }; @@ -126,25 +115,6 @@ export async function executeWorkflow( addToSessionLog(sessionLog, step.name, response); }); - // Handle worktree config from Planner - engine.on('planner:worktree_config', (config: WorktreeConfig) => { - log.info('Planner provided worktree config', config); - try { - info(`Creating worktree for branch: ${config.branchName}`); - worktreeInfo = createWorktree(cwd, config.branchName, config.baseBranch); - success(`Worktree created: ${worktreeInfo.path}`); - info(`Base branch: ${worktreeInfo.baseBranch}`); - info(`Working in worktree: ${worktreeInfo.path}`); - - // Update engine's cwd to worktree path for remaining steps - engine.updateCwd(worktreeInfo.path); - } catch (e) { - const errorMessage = e instanceof Error ? e.message : String(e); - error(`Failed to create worktree: ${errorMessage}`); - // Continue without worktree - don't abort the workflow - } - }); - engine.on('workflow:complete', (state) => { log.info('Workflow completed successfully', { iterations: state.iteration }); finalizeSessionLog(sessionLog, 'completed'); @@ -152,10 +122,6 @@ export async function executeWorkflow( const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd); success(`Workflow completed (${state.iteration} iterations)`); info(`Session log: ${logPath}`); - if (worktreeInfo) { - info(`Worktree preserved at: ${worktreeInfo.path}`); - info(`Branch: ${worktreeInfo.branch}`); - } notifySuccess('TAKT', `ワークフロー完了 (${state.iteration} iterations)`); }); @@ -171,10 +137,6 @@ export async function executeWorkflow( const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd); error(`Workflow aborted after ${state.iteration} iterations: ${reason}`); info(`Session log: ${logPath}`); - if (worktreeInfo) { - info(`Worktree preserved at: ${worktreeInfo.path}`); - info(`Branch: ${worktreeInfo.branch}`); - } notifyError('TAKT', `中断: ${reason}`); }); @@ -183,6 +145,5 @@ export async function executeWorkflow( return { success: finalState.status === 'completed', reason: abortReason, - worktree: worktreeInfo, }; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 5cd2b56..fc41310 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,4 +5,3 @@ export * from './ui.js'; export * from './session.js'; export * from './debug.js'; -export * from './worktree.js'; diff --git a/src/utils/worktree.ts b/src/utils/worktree.ts deleted file mode 100644 index a1ba9f9..0000000 --- a/src/utils/worktree.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Git worktree management utilities for takt - */ - -import { execFileSync } from 'node:child_process'; -import { join, resolve } from 'node:path'; -import { existsSync, mkdirSync } from 'node:fs'; -import { createLogger } from './debug.js'; - -const log = createLogger('worktree'); - -export interface WorktreeInfo { - path: string; - branch: string; - baseBranch: string; -} - -/** Worktree configuration from Planner output */ -export interface WorktreeConfig { - baseBranch: string; - branchName: string; -} - -/** - * Parse worktree configuration from Planner output - */ -export function parseWorktreeConfig(content: string): WorktreeConfig | null { - // Match worktree: block with baseBranch and branchName - const worktreeMatch = content.match(/worktree:\s*\n\s*baseBranch:\s*(\S+)\s*\n\s*branchName:\s*(\S+)/); - if (worktreeMatch && worktreeMatch[1] && worktreeMatch[2]) { - return { - baseBranch: worktreeMatch[1], - branchName: worktreeMatch[2], - }; - } - return null; -} - -/** - * Generate a timestamp string for worktree directory - */ -export function generateTimestamp(): string { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); - return `${year}${month}${day}-${hours}${minutes}${seconds}`; -} - -/** - * Sanitize branch name for use in directory name - */ -export function sanitizeBranchName(branchName: string): string { - return branchName - .toLowerCase() - .replace(/[^a-z0-9-]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 50); -} - -/** - * Get the worktrees directory path - */ -export function getWorktreesDir(cwd: string): string { - return join(resolve(cwd), '.takt', 'worktrees'); -} - -/** - * Generate worktree path - */ -export function getWorktreePath(cwd: string, timestamp: string, branchName: string): string { - const sanitizedBranch = sanitizeBranchName(branchName); - return join(getWorktreesDir(cwd), `${timestamp}-${sanitizedBranch}`); -} - -/** - * Create a new git worktree with a new branch - * @param cwd - Current working directory - * @param branchName - Name of the new branch to create - * @param baseBranch - Base branch to create the worktree from (required, determined by Planner) - */ -export function createWorktree( - cwd: string, - branchName: string, - baseBranch: string -): WorktreeInfo { - const timestamp = generateTimestamp(); - const worktreePath = getWorktreePath(cwd, timestamp, branchName); - - // Ensure worktrees directory exists - const worktreesDir = getWorktreesDir(cwd); - if (!existsSync(worktreesDir)) { - mkdirSync(worktreesDir, { recursive: true }); - } - - log.info('Creating worktree', { path: worktreePath, branch: branchName, baseBranch }); - - // Fetch latest from origin - try { - execFileSync('git', ['fetch', 'origin'], { cwd, stdio: 'pipe' }); - } catch { - log.debug('Failed to fetch from origin, continuing with local state'); - } - - // Create worktree with new branch (using execFileSync to prevent command injection) - try { - const baseRef = `origin/${baseBranch}`; - execFileSync('git', ['worktree', 'add', '-b', branchName, worktreePath, baseRef], { - cwd, - stdio: 'pipe', - }); - } catch (e) { - // If origin/base doesn't exist, try local base - log.debug('Failed to create from origin, trying local branch', { error: e }); - execFileSync('git', ['worktree', 'add', '-b', branchName, worktreePath, baseBranch], { - cwd, - stdio: 'pipe', - }); - } - - log.info('Worktree created successfully', { path: worktreePath }); - - return { - path: worktreePath, - branch: branchName, - baseBranch, - }; -} - -/** - * Remove a worktree - */ -export function removeWorktree(cwd: string, worktreePath: string): void { - log.info('Removing worktree', { path: worktreePath }); - execFileSync('git', ['worktree', 'remove', worktreePath, '--force'], { cwd, stdio: 'pipe' }); -} - -/** - * List all worktrees - */ -export function listWorktrees(cwd: string): string[] { - const output = execFileSync('git', ['worktree', 'list', '--porcelain'], { cwd, encoding: 'utf-8' }); - const paths: string[] = []; - for (const line of output.split('\n')) { - if (line.startsWith('worktree ')) { - paths.push(line.slice('worktree '.length)); - } - } - return paths; -} diff --git a/src/workflow/engine.ts b/src/workflow/engine.ts index 4c40138..5dcbf43 100644 --- a/src/workflow/engine.ts +++ b/src/workflow/engine.ts @@ -23,7 +23,6 @@ import { addUserInput, getPreviousOutput, } from './state-manager.js'; -import { parseWorktreeConfig } from '../utils/worktree.js'; import { generateReportDir } from '../utils/session.js'; // Re-export types for backward compatibility @@ -35,7 +34,6 @@ export type { IterationLimitCallback, WorkflowEngineOptions, } from './types.js'; -export type { WorktreeConfig } from '../utils/worktree.js'; export { COMPLETE_STEP, ABORT_STEP } from './constants.js'; /** Workflow engine for orchestrating agent execution */ @@ -103,12 +101,12 @@ export class WorkflowEngine extends EventEmitter { addUserInput(this.state, input); } - /** Update working directory (used after worktree creation) */ + /** Update working directory */ updateCwd(newCwd: string): void { this.cwd = newCwd; } - /** Get current working directory (may be worktree path) */ + /** Get current working directory */ getCwd(): string { return this.cwd; } @@ -217,14 +215,6 @@ export class WorkflowEngine extends EventEmitter { const response = await this.runStep(step); this.emit('step:complete', step, response); - // Check for worktree config in Planner output (when DONE) - if (step.name === 'plan' && response.status === 'done') { - const worktreeConfig = parseWorktreeConfig(response.content); - if (worktreeConfig) { - this.emit('planner:worktree_config', worktreeConfig); - } - } - if (response.status === 'blocked') { this.emit('step:blocked', step, response); const result = await handleBlocked(step, response, this.options); diff --git a/src/workflow/types.ts b/src/workflow/types.ts index fc29b6d..baa4781 100644 --- a/src/workflow/types.ts +++ b/src/workflow/types.ts @@ -8,7 +8,6 @@ import type { WorkflowStep, AgentResponse, WorkflowState } from '../models/types.js'; import type { StreamCallback } from '../agents/runner.js'; import type { PermissionHandler, AskUserQuestionHandler } from '../claude/process.js'; -import type { WorktreeConfig } from '../utils/worktree.js'; /** Events emitted by workflow engine */ export interface WorkflowEvents { @@ -20,7 +19,6 @@ export interface WorkflowEvents { 'workflow:abort': (state: WorkflowState, reason: string) => void; 'iteration:limit': (iteration: number, maxIterations: number) => void; 'step:loop_detected': (step: WorkflowStep, consecutiveCount: number) => void; - 'planner:worktree_config': (config: WorktreeConfig) => void; } /** User input request for blocked state */