言語設定を追加
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('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', () => {
|
||||
@ -210,11 +221,25 @@ describe('instruction-builder', () => {
|
||||
expect(metadata.workingDirectory).toBe('/same-path');
|
||||
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', () => {
|
||||
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('- Working Directory: /project');
|
||||
@ -226,6 +251,7 @@ describe('instruction-builder', () => {
|
||||
const rendered = renderExecutionMetadata({
|
||||
workingDirectory: '/worktree',
|
||||
projectRoot: '/project',
|
||||
language: 'en',
|
||||
});
|
||||
|
||||
expect(rendered).toContain('## Execution Context');
|
||||
@ -235,10 +261,38 @@ describe('instruction-builder', () => {
|
||||
});
|
||||
|
||||
it('should end with a trailing empty line', () => {
|
||||
const rendered = renderExecutionMetadata({ workingDirectory: '/project' });
|
||||
const rendered = renderExecutionMetadata({ workingDirectory: '/project', language: 'en' });
|
||||
|
||||
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', () => {
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
* 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 { createWorktree } from '../task/worktree.js';
|
||||
import { autoCommitWorktree } from '../task/autoCommit.js';
|
||||
@ -47,8 +47,10 @@ export async function executeTask(
|
||||
steps: workflowConfig.steps.map(s => s.name),
|
||||
});
|
||||
|
||||
const globalConfig = loadGlobalConfig();
|
||||
const result = await executeWorkflow(workflowConfig, task, cwd, {
|
||||
projectCwd,
|
||||
language: globalConfig.language,
|
||||
});
|
||||
return result.success;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
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 { loadAgentSessions, updateAgentSession } from '../config/paths.js';
|
||||
import {
|
||||
@ -59,6 +59,8 @@ export interface WorkflowExecutionOptions {
|
||||
headerPrefix?: string;
|
||||
/** Project root directory (where .takt/ lives). Defaults to cwd. */
|
||||
projectCwd?: string;
|
||||
/** Language for instruction metadata */
|
||||
language?: Language;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,6 +167,7 @@ export async function executeWorkflow(
|
||||
onSessionUpdate: sessionUpdateHandler,
|
||||
onIterationLimit: iterationLimitHandler,
|
||||
projectCwd,
|
||||
language: options.language,
|
||||
});
|
||||
|
||||
let abortReason: string | undefined;
|
||||
|
||||
@ -49,6 +49,7 @@ export class WorkflowEngine extends EventEmitter {
|
||||
private task: string;
|
||||
private options: WorkflowEngineOptions;
|
||||
private loopDetector: LoopDetector;
|
||||
private language: WorkflowEngineOptions['language'];
|
||||
private reportDir: string;
|
||||
|
||||
constructor(config: WorkflowConfig, cwd: string, task: string, options: WorkflowEngineOptions = {}) {
|
||||
@ -58,6 +59,7 @@ export class WorkflowEngine extends EventEmitter {
|
||||
this.cwd = cwd;
|
||||
this.task = task;
|
||||
this.options = options;
|
||||
this.language = options.language;
|
||||
this.loopDetector = new LoopDetector(config.loopDetection);
|
||||
this.reportDir = generateReportDir(task);
|
||||
this.ensureReportDirExists();
|
||||
@ -138,6 +140,7 @@ export class WorkflowEngine extends EventEmitter {
|
||||
userInputs: this.state.userInputs,
|
||||
previousOutput: getPreviousOutput(this.state),
|
||||
reportDir: this.reportDir,
|
||||
language: this.language,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
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';
|
||||
|
||||
/**
|
||||
@ -31,6 +31,8 @@ export interface InstructionContext {
|
||||
previousOutput?: AgentResponse;
|
||||
/** Report directory path */
|
||||
reportDir?: string;
|
||||
/** Language for metadata rendering. Defaults to 'en'. */
|
||||
language?: Language;
|
||||
}
|
||||
|
||||
/** Execution environment metadata prepended to agent instructions */
|
||||
@ -39,6 +41,8 @@ export interface ExecutionMetadata {
|
||||
readonly workingDirectory: string;
|
||||
/** Project root where .takt/ lives. Present only in worktree mode. */
|
||||
readonly projectRoot?: string;
|
||||
/** Language for metadata rendering */
|
||||
readonly language: Language;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,27 +58,50 @@ export function buildExecutionMetadata(context: InstructionContext): ExecutionMe
|
||||
return {
|
||||
workingDirectory: context.cwd,
|
||||
...(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.
|
||||
*
|
||||
* Pure function: ExecutionMetadata → string.
|
||||
* Always includes `## Execution Context` + `Working Directory`.
|
||||
* Adds `Project Root` and `Mode` only in worktree mode (when projectRoot is present).
|
||||
* Always includes heading + Working Directory.
|
||||
* 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 {
|
||||
const strings = METADATA_STRINGS[metadata.language];
|
||||
const lines = [
|
||||
'## Execution Context',
|
||||
`- Working Directory: ${metadata.workingDirectory}`,
|
||||
strings.heading,
|
||||
`- ${strings.workingDirectory}: ${metadata.workingDirectory}`,
|
||||
];
|
||||
if (metadata.projectRoot !== undefined) {
|
||||
lines.push(`- Project Root: ${metadata.projectRoot}`);
|
||||
lines.push('- Mode: worktree (source edits in Working Directory, reports in Project Root)');
|
||||
lines.push(`- ${strings.projectRoot}: ${metadata.projectRoot}`);
|
||||
lines.push(`- ${strings.mode}`);
|
||||
}
|
||||
if (strings.note) {
|
||||
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('');
|
||||
return lines.join('\n');
|
||||
}
|
||||
@ -152,10 +179,10 @@ export function buildInstruction(
|
||||
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.
|
||||
// Prepend execution context metadata so agents see it first.
|
||||
// Now language-aware, so no need to hide it at the end.
|
||||
const metadata = buildExecutionMetadata(context);
|
||||
instruction = `${instruction}\n\n${renderExecutionMetadata(metadata)}`;
|
||||
instruction = `${renderExecutionMetadata(metadata)}\n${instruction}`;
|
||||
|
||||
return instruction;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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 { PermissionHandler, AskUserQuestionHandler } from '../claude/process.js';
|
||||
|
||||
@ -72,6 +72,8 @@ export interface WorkflowEngineOptions {
|
||||
bypassPermissions?: boolean;
|
||||
/** Project root directory (where .takt/ lives). Defaults to cwd if not specified. */
|
||||
projectCwd?: string;
|
||||
/** Language for instruction metadata. Defaults to 'en'. */
|
||||
language?: Language;
|
||||
}
|
||||
|
||||
/** Loop detection result */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user