worktreeは一時Omit
This commit is contained in:
parent
bed5662097
commit
65a9553bb9
@ -59,23 +59,8 @@ Determine the implementation direction:
|
|||||||
|
|
||||||
```
|
```
|
||||||
[PLANNER:DONE]
|
[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
|
### BLOCKED Output Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -86,8 +71,6 @@ Clarifications needed:
|
|||||||
- {Question 2}
|
- {Question 2}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Do not output worktree settings when BLOCKED.
|
|
||||||
|
|
||||||
## Important
|
## Important
|
||||||
|
|
||||||
**Keep analysis simple.** Overly detailed plans are unnecessary. Provide enough direction for Coder to proceed with implementation.
|
**Keep analysis simple.** Overly detailed plans are unnecessary. Provide enough direction for Coder to proceed with implementation.
|
||||||
|
|||||||
@ -59,23 +59,8 @@
|
|||||||
|
|
||||||
```
|
```
|
||||||
[PLANNER:DONE]
|
[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時の出力構造
|
### BLOCKED時の出力構造
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -86,8 +71,6 @@ worktree:
|
|||||||
- {質問2}
|
- {質問2}
|
||||||
```
|
```
|
||||||
|
|
||||||
**注意:** BLOCKEDの場合、worktree設定は出力しない。
|
|
||||||
|
|
||||||
## 重要
|
## 重要
|
||||||
|
|
||||||
**シンプルに分析する。** 過度に詳細な計画は不要。Coderが実装を進められる程度の方向性を示す。
|
**シンプルに分析する。** 過度に詳細な計画は不要。Coderが実装を進められる程度の方向性を示す。
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
} from '../utils/session.js';
|
} from '../utils/session.js';
|
||||||
import { createLogger } from '../utils/debug.js';
|
import { createLogger } from '../utils/debug.js';
|
||||||
import { notifySuccess, notifyError } from '../utils/notification.js';
|
import { notifySuccess, notifyError } from '../utils/notification.js';
|
||||||
import { createWorktree, type WorktreeInfo, type WorktreeConfig } from '../utils/worktree.js';
|
|
||||||
|
|
||||||
const log = createLogger('workflow');
|
const log = createLogger('workflow');
|
||||||
|
|
||||||
@ -30,8 +29,6 @@ const log = createLogger('workflow');
|
|||||||
export interface WorkflowExecutionResult {
|
export interface WorkflowExecutionResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
reason?: string;
|
reason?: string;
|
||||||
/** Worktree information if worktree mode was used */
|
|
||||||
worktree?: WorktreeInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Options for workflow execution */
|
/** Options for workflow execution */
|
||||||
@ -44,10 +41,6 @@ export interface WorkflowExecutionOptions {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a workflow and handle all events
|
* 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(
|
export async function executeWorkflow(
|
||||||
workflowConfig: WorkflowConfig,
|
workflowConfig: WorkflowConfig,
|
||||||
@ -60,9 +53,6 @@ export async function executeWorkflow(
|
|||||||
headerPrefix = 'Running Workflow:',
|
headerPrefix = 'Running Workflow:',
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Worktree info will be set when Planner emits worktree config
|
|
||||||
let worktreeInfo: WorktreeInfo | undefined;
|
|
||||||
|
|
||||||
// Clear previous sessions if not resuming
|
// Clear previous sessions if not resuming
|
||||||
if (!resumeSession) {
|
if (!resumeSession) {
|
||||||
log.debug('Starting fresh session (clearing previous agent sessions)');
|
log.debug('Starting fresh session (clearing previous agent sessions)');
|
||||||
@ -92,7 +82,6 @@ export async function executeWorkflow(
|
|||||||
const savedSessions = loadAgentSessions(cwd);
|
const savedSessions = loadAgentSessions(cwd);
|
||||||
|
|
||||||
// Session update handler - persist session IDs when they change
|
// 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 => {
|
const sessionUpdateHandler = (agentName: string, agentSessionId: string): void => {
|
||||||
updateAgentSession(cwd, agentName, agentSessionId);
|
updateAgentSession(cwd, agentName, agentSessionId);
|
||||||
};
|
};
|
||||||
@ -126,25 +115,6 @@ export async function executeWorkflow(
|
|||||||
addToSessionLog(sessionLog, step.name, response);
|
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) => {
|
engine.on('workflow:complete', (state) => {
|
||||||
log.info('Workflow completed successfully', { iterations: state.iteration });
|
log.info('Workflow completed successfully', { iterations: state.iteration });
|
||||||
finalizeSessionLog(sessionLog, 'completed');
|
finalizeSessionLog(sessionLog, 'completed');
|
||||||
@ -152,10 +122,6 @@ export async function executeWorkflow(
|
|||||||
const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd);
|
const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd);
|
||||||
success(`Workflow completed (${state.iteration} iterations)`);
|
success(`Workflow completed (${state.iteration} iterations)`);
|
||||||
info(`Session log: ${logPath}`);
|
info(`Session log: ${logPath}`);
|
||||||
if (worktreeInfo) {
|
|
||||||
info(`Worktree preserved at: ${worktreeInfo.path}`);
|
|
||||||
info(`Branch: ${worktreeInfo.branch}`);
|
|
||||||
}
|
|
||||||
notifySuccess('TAKT', `ワークフロー完了 (${state.iteration} iterations)`);
|
notifySuccess('TAKT', `ワークフロー完了 (${state.iteration} iterations)`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -171,10 +137,6 @@ export async function executeWorkflow(
|
|||||||
const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd);
|
const logPath = saveSessionLog(sessionLog, workflowSessionId, cwd);
|
||||||
error(`Workflow aborted after ${state.iteration} iterations: ${reason}`);
|
error(`Workflow aborted after ${state.iteration} iterations: ${reason}`);
|
||||||
info(`Session log: ${logPath}`);
|
info(`Session log: ${logPath}`);
|
||||||
if (worktreeInfo) {
|
|
||||||
info(`Worktree preserved at: ${worktreeInfo.path}`);
|
|
||||||
info(`Branch: ${worktreeInfo.branch}`);
|
|
||||||
}
|
|
||||||
notifyError('TAKT', `中断: ${reason}`);
|
notifyError('TAKT', `中断: ${reason}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -183,6 +145,5 @@ export async function executeWorkflow(
|
|||||||
return {
|
return {
|
||||||
success: finalState.status === 'completed',
|
success: finalState.status === 'completed',
|
||||||
reason: abortReason,
|
reason: abortReason,
|
||||||
worktree: worktreeInfo,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,4 +5,3 @@
|
|||||||
export * from './ui.js';
|
export * from './ui.js';
|
||||||
export * from './session.js';
|
export * from './session.js';
|
||||||
export * from './debug.js';
|
export * from './debug.js';
|
||||||
export * from './worktree.js';
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -23,7 +23,6 @@ import {
|
|||||||
addUserInput,
|
addUserInput,
|
||||||
getPreviousOutput,
|
getPreviousOutput,
|
||||||
} from './state-manager.js';
|
} from './state-manager.js';
|
||||||
import { parseWorktreeConfig } from '../utils/worktree.js';
|
|
||||||
import { generateReportDir } from '../utils/session.js';
|
import { generateReportDir } from '../utils/session.js';
|
||||||
|
|
||||||
// Re-export types for backward compatibility
|
// Re-export types for backward compatibility
|
||||||
@ -35,7 +34,6 @@ export type {
|
|||||||
IterationLimitCallback,
|
IterationLimitCallback,
|
||||||
WorkflowEngineOptions,
|
WorkflowEngineOptions,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
export type { WorktreeConfig } from '../utils/worktree.js';
|
|
||||||
export { COMPLETE_STEP, ABORT_STEP } from './constants.js';
|
export { COMPLETE_STEP, ABORT_STEP } from './constants.js';
|
||||||
|
|
||||||
/** Workflow engine for orchestrating agent execution */
|
/** Workflow engine for orchestrating agent execution */
|
||||||
@ -103,12 +101,12 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
addUserInput(this.state, input);
|
addUserInput(this.state, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update working directory (used after worktree creation) */
|
/** Update working directory */
|
||||||
updateCwd(newCwd: string): void {
|
updateCwd(newCwd: string): void {
|
||||||
this.cwd = newCwd;
|
this.cwd = newCwd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get current working directory (may be worktree path) */
|
/** Get current working directory */
|
||||||
getCwd(): string {
|
getCwd(): string {
|
||||||
return this.cwd;
|
return this.cwd;
|
||||||
}
|
}
|
||||||
@ -217,14 +215,6 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
const response = await this.runStep(step);
|
const response = await this.runStep(step);
|
||||||
this.emit('step:complete', step, response);
|
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') {
|
if (response.status === 'blocked') {
|
||||||
this.emit('step:blocked', step, response);
|
this.emit('step:blocked', step, response);
|
||||||
const result = await handleBlocked(step, response, this.options);
|
const result = await handleBlocked(step, response, this.options);
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
import type { WorkflowStep, AgentResponse, WorkflowState } from '../models/types.js';
|
import type { WorkflowStep, AgentResponse, WorkflowState } from '../models/types.js';
|
||||||
import type { StreamCallback } from '../agents/runner.js';
|
import type { StreamCallback } from '../agents/runner.js';
|
||||||
import type { PermissionHandler, AskUserQuestionHandler } from '../claude/process.js';
|
import type { PermissionHandler, AskUserQuestionHandler } from '../claude/process.js';
|
||||||
import type { WorktreeConfig } from '../utils/worktree.js';
|
|
||||||
|
|
||||||
/** Events emitted by workflow engine */
|
/** Events emitted by workflow engine */
|
||||||
export interface WorkflowEvents {
|
export interface WorkflowEvents {
|
||||||
@ -20,7 +19,6 @@ export interface WorkflowEvents {
|
|||||||
'workflow:abort': (state: WorkflowState, reason: string) => void;
|
'workflow:abort': (state: WorkflowState, reason: string) => void;
|
||||||
'iteration:limit': (iteration: number, maxIterations: number) => void;
|
'iteration:limit': (iteration: number, maxIterations: number) => void;
|
||||||
'step:loop_detected': (step: WorkflowStep, consecutiveCount: number) => void;
|
'step:loop_detected': (step: WorkflowStep, consecutiveCount: number) => void;
|
||||||
'planner:worktree_config': (config: WorktreeConfig) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** User input request for blocked state */
|
/** User input request for blocked state */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user