worktreeのバグフィックス

This commit is contained in:
nrslib 2026-01-29 20:26:27 +09:00
parent 73c2d6b381
commit ee0d846c5b
3 changed files with 29 additions and 36 deletions

View File

@ -77,7 +77,7 @@ describe('instruction-builder', () => {
});
describe('report_dir replacement', () => {
it('should replace .takt/reports/{report_dir} with full absolute path', () => {
it('should replace {report_dir} in paths keeping them relative', () => {
const step = createMinimalStep(
'- Report Directory: .takt/reports/{report_dir}/'
);
@ -89,31 +89,31 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).toContain(
'- Report Directory: /project/.takt/reports/20260128-test-report/'
'- Report Directory: .takt/reports/20260128-test-report/'
);
});
it('should use projectCwd for report path when cwd is a worktree', () => {
it('should not leak projectCwd absolute path into instruction', () => {
const step = createMinimalStep(
'- Report: .takt/reports/{report_dir}/00-plan.md'
);
const context = createMinimalContext({
cwd: '/project/.takt/worktrees/my-task',
cwd: '/clone/my-task',
projectCwd: '/project',
reportDir: '20260128-worktree-report',
});
const result = buildInstruction(step, context);
// Path should be relative, not absolute with projectCwd
expect(result).toContain(
'- Report: /project/.takt/reports/20260128-worktree-report/00-plan.md'
'- Report: .takt/reports/20260128-worktree-report/00-plan.md'
);
expect(result).toContain('Working Directory: /project/.takt/worktrees/my-task');
// Project Root should NOT be included in metadata (to avoid agent confusion)
expect(result).not.toContain('Project Root');
expect(result).not.toContain('/project/.takt/reports/');
expect(result).toContain('Working Directory: /clone/my-task');
});
it('should replace multiple .takt/reports/{report_dir} occurrences', () => {
it('should replace multiple {report_dir} occurrences', () => {
const step = createMinimalStep(
'- Scope: .takt/reports/{report_dir}/01-scope.md\n- Decisions: .takt/reports/{report_dir}/02-decisions.md'
);
@ -125,8 +125,9 @@ describe('instruction-builder', () => {
const result = buildInstruction(step, context);
expect(result).toContain('/project/.takt/reports/20260128-multi/01-scope.md');
expect(result).toContain('/project/.takt/reports/20260128-multi/02-decisions.md');
expect(result).toContain('.takt/reports/20260128-multi/01-scope.md');
expect(result).toContain('.takt/reports/20260128-multi/02-decisions.md');
expect(result).not.toContain('/project/.takt/reports/');
});
it('should replace standalone {report_dir} with directory name only', () => {
@ -141,22 +142,6 @@ describe('instruction-builder', () => {
expect(result).toContain('Report dir name: 20260128-standalone');
});
it('should fall back to cwd when projectCwd is not provided', () => {
const step = createMinimalStep(
'- Dir: .takt/reports/{report_dir}/'
);
const context = createMinimalContext({
cwd: '/fallback-project',
reportDir: '20260128-fallback',
});
const result = buildInstruction(step, context);
expect(result).toContain(
'- Dir: /fallback-project/.takt/reports/20260128-fallback/'
);
});
});
describe('buildExecutionMetadata', () => {

View File

@ -3,7 +3,7 @@
*/
import { EventEmitter } from 'node:events';
import { mkdirSync, existsSync } from 'node:fs';
import { mkdirSync, existsSync, symlinkSync } from 'node:fs';
import { join } from 'node:path';
import type {
WorkflowConfig,
@ -79,6 +79,18 @@ export class WorkflowEngine extends EventEmitter {
if (!existsSync(reportDirPath)) {
mkdirSync(reportDirPath, { recursive: true });
}
// Worktree mode: create symlink so agents can access reports via relative path
if (this.cwd !== this.projectCwd) {
const cwdReportsDir = join(this.cwd, '.takt', 'reports');
if (!existsSync(cwdReportsDir)) {
mkdirSync(join(this.cwd, '.takt'), { recursive: true });
symlinkSync(
join(this.projectCwd, '.takt', 'reports'),
cwdReportsDir,
);
}
}
}
/** Validate workflow configuration at construction time */

View File

@ -5,7 +5,6 @@
* template placeholders with actual values.
*/
import { join } from 'node:path';
import type { WorkflowStep, AgentResponse, Language } from '../models/types.js';
import { getGitDiff } from '../agents/runner.js';
@ -180,14 +179,11 @@ export function buildInstruction(
escapeTemplateChars(userInputsStr)
);
// Replace .takt/reports/{report_dir} with absolute path first,
// then replace standalone {report_dir} with the directory name.
// This ensures agents always use the correct project root for reports,
// even when their cwd is a clone.
// Replace {report_dir} with the directory name, keeping paths relative.
// In worktree mode, a symlink from cwd/.takt/reports → projectCwd/.takt/reports
// ensures the relative path resolves correctly without embedding absolute paths
// that could cause agents to operate on the wrong repository.
if (context.reportDir) {
const projectRoot = context.projectCwd ?? context.cwd;
const reportDirFullPath = join(projectRoot, '.takt', 'reports', context.reportDir);
instruction = instruction.replace(/\.takt\/reports\/\{report_dir\}/g, reportDirFullPath);
instruction = instruction.replace(/\{report_dir\}/g, context.reportDir);
}