言語設定を追加
This commit is contained in:
parent
19ced26d00
commit
722c827cc4
@ -87,6 +87,17 @@ describe('instruction-builder', () => {
|
|||||||
expect(result).not.toContain('Project Root');
|
expect(result).not.toContain('Project Root');
|
||||||
expect(result).not.toContain('Mode:');
|
expect(result).not.toContain('Mode:');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should prepend metadata before the instruction body', () => {
|
||||||
|
const step = createMinimalStep('Do some work');
|
||||||
|
const context = createMinimalContext({ cwd: '/project' });
|
||||||
|
|
||||||
|
const result = buildInstruction(step, context);
|
||||||
|
const metadataIndex = result.indexOf('## Execution Context');
|
||||||
|
const bodyIndex = result.indexOf('Do some work');
|
||||||
|
|
||||||
|
expect(metadataIndex).toBeLessThan(bodyIndex);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('report_dir replacement', () => {
|
describe('report_dir replacement', () => {
|
||||||
@ -210,11 +221,25 @@ describe('instruction-builder', () => {
|
|||||||
expect(metadata.workingDirectory).toBe('/same-path');
|
expect(metadata.workingDirectory).toBe('/same-path');
|
||||||
expect(metadata.projectRoot).toBeUndefined();
|
expect(metadata.projectRoot).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should default language to en when not specified', () => {
|
||||||
|
const context = createMinimalContext({ cwd: '/project' });
|
||||||
|
const metadata = buildExecutionMetadata(context);
|
||||||
|
|
||||||
|
expect(metadata.language).toBe('en');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should propagate language from context', () => {
|
||||||
|
const context = createMinimalContext({ cwd: '/project', language: 'ja' });
|
||||||
|
const metadata = buildExecutionMetadata(context);
|
||||||
|
|
||||||
|
expect(metadata.language).toBe('ja');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('renderExecutionMetadata', () => {
|
describe('renderExecutionMetadata', () => {
|
||||||
it('should render normal mode without Project Root or Mode', () => {
|
it('should render normal mode without Project Root or Mode', () => {
|
||||||
const rendered = renderExecutionMetadata({ workingDirectory: '/project' });
|
const rendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'en' });
|
||||||
|
|
||||||
expect(rendered).toContain('## Execution Context');
|
expect(rendered).toContain('## Execution Context');
|
||||||
expect(rendered).toContain('- Working Directory: /project');
|
expect(rendered).toContain('- Working Directory: /project');
|
||||||
@ -226,6 +251,7 @@ describe('instruction-builder', () => {
|
|||||||
const rendered = renderExecutionMetadata({
|
const rendered = renderExecutionMetadata({
|
||||||
workingDirectory: '/worktree',
|
workingDirectory: '/worktree',
|
||||||
projectRoot: '/project',
|
projectRoot: '/project',
|
||||||
|
language: 'en',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(rendered).toContain('## Execution Context');
|
expect(rendered).toContain('## Execution Context');
|
||||||
@ -235,10 +261,38 @@ describe('instruction-builder', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should end with a trailing empty line', () => {
|
it('should end with a trailing empty line', () => {
|
||||||
const rendered = renderExecutionMetadata({ workingDirectory: '/project' });
|
const rendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'en' });
|
||||||
|
|
||||||
expect(rendered).toMatch(/\n$/);
|
expect(rendered).toMatch(/\n$/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render in Japanese when language is ja', () => {
|
||||||
|
const rendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'ja' });
|
||||||
|
|
||||||
|
expect(rendered).toContain('## 実行コンテキスト');
|
||||||
|
expect(rendered).toContain('- 作業ディレクトリ: /project');
|
||||||
|
expect(rendered).not.toContain('Execution Context');
|
||||||
|
expect(rendered).not.toContain('Working Directory');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render worktree mode in Japanese', () => {
|
||||||
|
const rendered = renderExecutionMetadata({
|
||||||
|
workingDirectory: '/worktree',
|
||||||
|
projectRoot: '/project',
|
||||||
|
language: 'ja',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(rendered).toContain('- プロジェクトルート: /project');
|
||||||
|
expect(rendered).toContain('モード: worktree');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include English note only for en, not for ja', () => {
|
||||||
|
const enRendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'en' });
|
||||||
|
const jaRendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'ja' });
|
||||||
|
|
||||||
|
expect(enRendered).toContain('Note:');
|
||||||
|
expect(jaRendered).not.toContain('Note:');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('basic placeholder replacement', () => {
|
describe('basic placeholder replacement', () => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
* Task execution logic
|
* Task execution logic
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { loadWorkflow } from '../config/index.js';
|
import { loadWorkflow, loadGlobalConfig } from '../config/index.js';
|
||||||
import { TaskRunner, type TaskInfo } from '../task/index.js';
|
import { TaskRunner, type TaskInfo } from '../task/index.js';
|
||||||
import { createWorktree } from '../task/worktree.js';
|
import { createWorktree } from '../task/worktree.js';
|
||||||
import { autoCommitWorktree } from '../task/autoCommit.js';
|
import { autoCommitWorktree } from '../task/autoCommit.js';
|
||||||
@ -47,8 +47,10 @@ export async function executeTask(
|
|||||||
steps: workflowConfig.steps.map(s => s.name),
|
steps: workflowConfig.steps.map(s => s.name),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const globalConfig = loadGlobalConfig();
|
||||||
const result = await executeWorkflow(workflowConfig, task, cwd, {
|
const result = await executeWorkflow(workflowConfig, task, cwd, {
|
||||||
projectCwd,
|
projectCwd,
|
||||||
|
language: globalConfig.language,
|
||||||
});
|
});
|
||||||
return result.success;
|
return result.success;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { WorkflowEngine } from '../workflow/engine.js';
|
import { WorkflowEngine } from '../workflow/engine.js';
|
||||||
import type { WorkflowConfig } from '../models/types.js';
|
import type { WorkflowConfig, Language } from '../models/types.js';
|
||||||
import type { IterationLimitRequest } from '../workflow/types.js';
|
import type { IterationLimitRequest } from '../workflow/types.js';
|
||||||
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
import { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
||||||
import {
|
import {
|
||||||
@ -59,6 +59,8 @@ export interface WorkflowExecutionOptions {
|
|||||||
headerPrefix?: string;
|
headerPrefix?: string;
|
||||||
/** Project root directory (where .takt/ lives). Defaults to cwd. */
|
/** Project root directory (where .takt/ lives). Defaults to cwd. */
|
||||||
projectCwd?: string;
|
projectCwd?: string;
|
||||||
|
/** Language for instruction metadata */
|
||||||
|
language?: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,6 +167,7 @@ export async function executeWorkflow(
|
|||||||
onSessionUpdate: sessionUpdateHandler,
|
onSessionUpdate: sessionUpdateHandler,
|
||||||
onIterationLimit: iterationLimitHandler,
|
onIterationLimit: iterationLimitHandler,
|
||||||
projectCwd,
|
projectCwd,
|
||||||
|
language: options.language,
|
||||||
});
|
});
|
||||||
|
|
||||||
let abortReason: string | undefined;
|
let abortReason: string | undefined;
|
||||||
|
|||||||
@ -49,6 +49,7 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
private task: string;
|
private task: string;
|
||||||
private options: WorkflowEngineOptions;
|
private options: WorkflowEngineOptions;
|
||||||
private loopDetector: LoopDetector;
|
private loopDetector: LoopDetector;
|
||||||
|
private language: WorkflowEngineOptions['language'];
|
||||||
private reportDir: string;
|
private reportDir: string;
|
||||||
|
|
||||||
constructor(config: WorkflowConfig, cwd: string, task: string, options: WorkflowEngineOptions = {}) {
|
constructor(config: WorkflowConfig, cwd: string, task: string, options: WorkflowEngineOptions = {}) {
|
||||||
@ -58,6 +59,7 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
this.cwd = cwd;
|
this.cwd = cwd;
|
||||||
this.task = task;
|
this.task = task;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.language = options.language;
|
||||||
this.loopDetector = new LoopDetector(config.loopDetection);
|
this.loopDetector = new LoopDetector(config.loopDetection);
|
||||||
this.reportDir = generateReportDir(task);
|
this.reportDir = generateReportDir(task);
|
||||||
this.ensureReportDirExists();
|
this.ensureReportDirExists();
|
||||||
@ -138,6 +140,7 @@ export class WorkflowEngine extends EventEmitter {
|
|||||||
userInputs: this.state.userInputs,
|
userInputs: this.state.userInputs,
|
||||||
previousOutput: getPreviousOutput(this.state),
|
previousOutput: getPreviousOutput(this.state),
|
||||||
reportDir: this.reportDir,
|
reportDir: this.reportDir,
|
||||||
|
language: this.language,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import type { WorkflowStep, AgentResponse } from '../models/types.js';
|
import type { WorkflowStep, AgentResponse, Language } from '../models/types.js';
|
||||||
import { getGitDiff } from '../agents/runner.js';
|
import { getGitDiff } from '../agents/runner.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,6 +31,8 @@ export interface InstructionContext {
|
|||||||
previousOutput?: AgentResponse;
|
previousOutput?: AgentResponse;
|
||||||
/** Report directory path */
|
/** Report directory path */
|
||||||
reportDir?: string;
|
reportDir?: string;
|
||||||
|
/** Language for metadata rendering. Defaults to 'en'. */
|
||||||
|
language?: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Execution environment metadata prepended to agent instructions */
|
/** Execution environment metadata prepended to agent instructions */
|
||||||
@ -39,6 +41,8 @@ export interface ExecutionMetadata {
|
|||||||
readonly workingDirectory: string;
|
readonly workingDirectory: string;
|
||||||
/** Project root where .takt/ lives. Present only in worktree mode. */
|
/** Project root where .takt/ lives. Present only in worktree mode. */
|
||||||
readonly projectRoot?: string;
|
readonly projectRoot?: string;
|
||||||
|
/** Language for metadata rendering */
|
||||||
|
readonly language: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,27 +58,50 @@ export function buildExecutionMetadata(context: InstructionContext): ExecutionMe
|
|||||||
return {
|
return {
|
||||||
workingDirectory: context.cwd,
|
workingDirectory: context.cwd,
|
||||||
...(isWorktree ? { projectRoot } : {}),
|
...(isWorktree ? { projectRoot } : {}),
|
||||||
|
language: context.language ?? 'en',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Localized strings for execution metadata rendering */
|
||||||
|
const METADATA_STRINGS = {
|
||||||
|
en: {
|
||||||
|
heading: '## Execution Context',
|
||||||
|
workingDirectory: 'Working Directory',
|
||||||
|
projectRoot: 'Project Root',
|
||||||
|
mode: 'Mode: worktree (source edits in Working Directory, reports in Project Root)',
|
||||||
|
note: 'Note: This section is metadata. Follow the language used in the rest of the prompt.',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
heading: '## 実行コンテキスト',
|
||||||
|
workingDirectory: '作業ディレクトリ',
|
||||||
|
projectRoot: 'プロジェクトルート',
|
||||||
|
mode: 'モード: worktree(ソース編集は作業ディレクトリ、レポートはプロジェクトルート)',
|
||||||
|
note: '',
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render execution metadata as a markdown string.
|
* Render execution metadata as a markdown string.
|
||||||
*
|
*
|
||||||
* Pure function: ExecutionMetadata → string.
|
* Pure function: ExecutionMetadata → string.
|
||||||
* Always includes `## Execution Context` + `Working Directory`.
|
* Always includes heading + Working Directory.
|
||||||
* Adds `Project Root` and `Mode` only in worktree mode (when projectRoot is present).
|
* Adds Project Root and Mode only in worktree mode (when projectRoot is present).
|
||||||
|
* Language determines the output language; 'en' includes a note about language consistency.
|
||||||
*/
|
*/
|
||||||
export function renderExecutionMetadata(metadata: ExecutionMetadata): string {
|
export function renderExecutionMetadata(metadata: ExecutionMetadata): string {
|
||||||
|
const strings = METADATA_STRINGS[metadata.language];
|
||||||
const lines = [
|
const lines = [
|
||||||
'## Execution Context',
|
strings.heading,
|
||||||
`- Working Directory: ${metadata.workingDirectory}`,
|
`- ${strings.workingDirectory}: ${metadata.workingDirectory}`,
|
||||||
];
|
];
|
||||||
if (metadata.projectRoot !== undefined) {
|
if (metadata.projectRoot !== undefined) {
|
||||||
lines.push(`- Project Root: ${metadata.projectRoot}`);
|
lines.push(`- ${strings.projectRoot}: ${metadata.projectRoot}`);
|
||||||
lines.push('- Mode: worktree (source edits in Working Directory, reports in Project Root)');
|
lines.push(`- ${strings.mode}`);
|
||||||
}
|
}
|
||||||
|
if (strings.note) {
|
||||||
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(strings.note);
|
||||||
|
}
|
||||||
lines.push('');
|
lines.push('');
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
@ -152,10 +179,10 @@ export function buildInstruction(
|
|||||||
instruction = `${instruction}\n\n${step.statusRulesPrompt}`;
|
instruction = `${instruction}\n\n${step.statusRulesPrompt}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append execution context metadata at the end so the agent's language
|
// Prepend execution context metadata so agents see it first.
|
||||||
// is not influenced by this English-only section.
|
// Now language-aware, so no need to hide it at the end.
|
||||||
const metadata = buildExecutionMetadata(context);
|
const metadata = buildExecutionMetadata(context);
|
||||||
instruction = `${instruction}\n\n${renderExecutionMetadata(metadata)}`;
|
instruction = `${renderExecutionMetadata(metadata)}\n${instruction}`;
|
||||||
|
|
||||||
return instruction;
|
return instruction;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
* used by the workflow execution engine.
|
* used by the workflow execution engine.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { WorkflowStep, AgentResponse, WorkflowState } from '../models/types.js';
|
import type { WorkflowStep, AgentResponse, WorkflowState, Language } 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';
|
||||||
|
|
||||||
@ -72,6 +72,8 @@ export interface WorkflowEngineOptions {
|
|||||||
bypassPermissions?: boolean;
|
bypassPermissions?: boolean;
|
||||||
/** Project root directory (where .takt/ lives). Defaults to cwd if not specified. */
|
/** Project root directory (where .takt/ lives). Defaults to cwd if not specified. */
|
||||||
projectCwd?: string;
|
projectCwd?: string;
|
||||||
|
/** Language for instruction metadata. Defaults to 'en'. */
|
||||||
|
language?: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loop detection result */
|
/** Loop detection result */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user