takt: instruct
This commit is contained in:
parent
faf6ebf063
commit
54001b5122
@ -149,9 +149,10 @@ describe('runInstructMode', () => {
|
|||||||
expect(result.action).toBe('cancel');
|
expect(result.action).toBe('cancel');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use custom action selector without create_issue option', async () => {
|
it('should exclude execute from action selector options', async () => {
|
||||||
setupRawStdin(toRawInputs(['task', '/go']));
|
setupRawStdin(toRawInputs(['task', '/go']));
|
||||||
setupMockProvider(['response', 'Task summary.']);
|
setupMockProvider(['response', 'Task summary.']);
|
||||||
|
mockSelectOption.mockResolvedValue('save_task');
|
||||||
|
|
||||||
await runInstructMode('/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '');
|
await runInstructMode('/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '');
|
||||||
|
|
||||||
@ -161,7 +162,7 @@ describe('runInstructMode', () => {
|
|||||||
expect(selectCall).toBeDefined();
|
expect(selectCall).toBeDefined();
|
||||||
const options = selectCall![1] as Array<{ value: string }>;
|
const options = selectCall![1] as Array<{ value: string }>;
|
||||||
const values = options.map((o) => o.value);
|
const values = options.map((o) => o.value);
|
||||||
expect(values).toContain('execute');
|
expect(values).not.toContain('execute');
|
||||||
expect(values).toContain('save_task');
|
expect(values).toContain('save_task');
|
||||||
expect(values).toContain('continue');
|
expect(values).toContain('continue');
|
||||||
expect(values).not.toContain('create_issue');
|
expect(values).not.toContain('create_issue');
|
||||||
@ -215,4 +216,63 @@ describe('runInstructMode', () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should inject previousOrderContent into template variables when provided', async () => {
|
||||||
|
setupRawStdin(toRawInputs(['/cancel']));
|
||||||
|
setupMockProvider([]);
|
||||||
|
|
||||||
|
await runInstructMode('/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '', undefined, undefined, '# Previous Order\nDo the thing');
|
||||||
|
|
||||||
|
expect(mockLoadTemplate).toHaveBeenCalledWith(
|
||||||
|
'score_instruct_system_prompt',
|
||||||
|
'en',
|
||||||
|
expect.objectContaining({
|
||||||
|
hasOrderContent: true,
|
||||||
|
orderContent: '# Previous Order\nDo the thing',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set hasOrderContent=false when previousOrderContent is null', async () => {
|
||||||
|
setupRawStdin(toRawInputs(['/cancel']));
|
||||||
|
setupMockProvider([]);
|
||||||
|
|
||||||
|
await runInstructMode('/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '', undefined, undefined, null);
|
||||||
|
|
||||||
|
expect(mockLoadTemplate).toHaveBeenCalledWith(
|
||||||
|
'score_instruct_system_prompt',
|
||||||
|
'en',
|
||||||
|
expect.objectContaining({
|
||||||
|
hasOrderContent: false,
|
||||||
|
orderContent: '',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return execute with previous order content on /replay when previousOrderContent is set', async () => {
|
||||||
|
setupRawStdin(toRawInputs(['/replay']));
|
||||||
|
setupMockProvider([]);
|
||||||
|
|
||||||
|
const previousOrder = '# Previous Order\nDo the thing';
|
||||||
|
const result = await runInstructMode(
|
||||||
|
'/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '',
|
||||||
|
undefined, undefined, previousOrder,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.action).toBe('execute');
|
||||||
|
expect(result.task).toBe(previousOrder);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show error and continue when /replay is used without previousOrderContent', async () => {
|
||||||
|
setupRawStdin(toRawInputs(['/replay', '/cancel']));
|
||||||
|
setupMockProvider([]);
|
||||||
|
|
||||||
|
const result = await runInstructMode(
|
||||||
|
'/project', 'branch context', 'feature-branch', 'my-task', 'Do something', '',
|
||||||
|
undefined, undefined, null,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.action).toBe('cancel');
|
||||||
|
expect(mockInfo).toHaveBeenCalledWith('Mock label');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,8 +6,10 @@ import { describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
buildSummaryPrompt,
|
buildSummaryPrompt,
|
||||||
|
buildSummaryActionOptions,
|
||||||
formatTaskHistorySummary,
|
formatTaskHistorySummary,
|
||||||
type PieceContext,
|
type PieceContext,
|
||||||
|
type SummaryActionLabels,
|
||||||
type TaskHistorySummaryItem,
|
type TaskHistorySummaryItem,
|
||||||
} from '../features/interactive/interactive.js';
|
} from '../features/interactive/interactive.js';
|
||||||
|
|
||||||
@ -100,3 +102,54 @@ describe('buildSummaryPrompt', () => {
|
|||||||
expect(summary).toContain('User: Improve parser');
|
expect(summary).toContain('User: Improve parser');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('buildSummaryActionOptions', () => {
|
||||||
|
const labels: SummaryActionLabels = {
|
||||||
|
execute: 'Execute now',
|
||||||
|
saveTask: 'Save as Task',
|
||||||
|
continue: 'Continue editing',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should include all base actions when no exclude is given', () => {
|
||||||
|
const options = buildSummaryActionOptions(labels);
|
||||||
|
const values = options.map((o) => o.value);
|
||||||
|
|
||||||
|
expect(values).toEqual(['execute', 'save_task', 'continue']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exclude specified actions', () => {
|
||||||
|
const options = buildSummaryActionOptions(labels, [], ['execute']);
|
||||||
|
const values = options.map((o) => o.value);
|
||||||
|
|
||||||
|
expect(values).toEqual(['save_task', 'continue']);
|
||||||
|
expect(values).not.toContain('execute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should exclude multiple actions', () => {
|
||||||
|
const options = buildSummaryActionOptions(labels, [], ['execute', 'continue']);
|
||||||
|
const values = options.map((o) => o.value);
|
||||||
|
|
||||||
|
expect(values).toEqual(['save_task']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle append and exclude together', () => {
|
||||||
|
const labelsWithIssue: SummaryActionLabels = {
|
||||||
|
...labels,
|
||||||
|
createIssue: 'Create Issue',
|
||||||
|
};
|
||||||
|
const options = buildSummaryActionOptions(labelsWithIssue, ['create_issue'], ['execute']);
|
||||||
|
const values = options.map((o) => o.value);
|
||||||
|
|
||||||
|
expect(values).toEqual(['save_task', 'continue', 'create_issue']);
|
||||||
|
expect(values).not.toContain('execute');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty exclude by default (backward compatible)', () => {
|
||||||
|
const options = buildSummaryActionOptions(labels, []);
|
||||||
|
const values = options.map((o) => o.value);
|
||||||
|
|
||||||
|
expect(values).toContain('execute');
|
||||||
|
expect(values).toContain('save_task');
|
||||||
|
expect(values).toContain('continue');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -191,6 +191,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
const retryContext: RetryContext = {
|
const retryContext: RetryContext = {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'implement-auth',
|
taskName: 'implement-auth',
|
||||||
|
taskContent: 'Implement authentication feature',
|
||||||
createdAt: '2026-02-15T10:00:00Z',
|
createdAt: '2026-02-15T10:00:00Z',
|
||||||
failedMovement: 'review',
|
failedMovement: 'review',
|
||||||
error: 'Timeout after 300s',
|
error: 'Timeout after 300s',
|
||||||
@ -207,7 +208,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
run: null,
|
run: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runRetryMode(tmpDir, retryContext);
|
const result = await runRetryMode(tmpDir, retryContext, null);
|
||||||
|
|
||||||
// Verify: system prompt contains failure information
|
// Verify: system prompt contains failure information
|
||||||
expect(capture.systemPrompts.length).toBeGreaterThan(0);
|
expect(capture.systemPrompts.length).toBeGreaterThan(0);
|
||||||
@ -252,6 +253,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
const retryContext: RetryContext = {
|
const retryContext: RetryContext = {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'build-login',
|
taskName: 'build-login',
|
||||||
|
taskContent: 'Build login page with OAuth2',
|
||||||
createdAt: '2026-02-15T14:00:00Z',
|
createdAt: '2026-02-15T14:00:00Z',
|
||||||
failedMovement: 'implement',
|
failedMovement: 'implement',
|
||||||
error: 'CSS compilation failed',
|
error: 'CSS compilation failed',
|
||||||
@ -276,7 +278,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runRetryMode(tmpDir, retryContext);
|
const result = await runRetryMode(tmpDir, retryContext, null);
|
||||||
|
|
||||||
// Verify: system prompt contains BOTH failure info and run session data
|
// Verify: system prompt contains BOTH failure info and run session data
|
||||||
const systemPrompt = capture.systemPrompts[0]!;
|
const systemPrompt = capture.systemPrompts[0]!;
|
||||||
@ -314,6 +316,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
const retryContext: RetryContext = {
|
const retryContext: RetryContext = {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'fix-tests',
|
taskName: 'fix-tests',
|
||||||
|
taskContent: 'Fix failing test suite',
|
||||||
createdAt: '2026-02-15T16:00:00Z',
|
createdAt: '2026-02-15T16:00:00Z',
|
||||||
failedMovement: '',
|
failedMovement: '',
|
||||||
error: 'Test suite failed',
|
error: 'Test suite failed',
|
||||||
@ -330,7 +333,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
run: null,
|
run: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await runRetryMode(tmpDir, retryContext);
|
await runRetryMode(tmpDir, retryContext, null);
|
||||||
|
|
||||||
const systemPrompt = capture.systemPrompts[0]!;
|
const systemPrompt = capture.systemPrompts[0]!;
|
||||||
expect(systemPrompt).toContain('Existing Retry Note');
|
expect(systemPrompt).toContain('Existing Retry Note');
|
||||||
@ -348,6 +351,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
const retryContext: RetryContext = {
|
const retryContext: RetryContext = {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'some-task',
|
taskName: 'some-task',
|
||||||
|
taskContent: 'Complete some task',
|
||||||
createdAt: '2026-02-15T12:00:00Z',
|
createdAt: '2026-02-15T12:00:00Z',
|
||||||
failedMovement: 'plan',
|
failedMovement: 'plan',
|
||||||
error: 'Unknown error',
|
error: 'Unknown error',
|
||||||
@ -364,7 +368,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
run: null,
|
run: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runRetryMode(tmpDir, retryContext);
|
const result = await runRetryMode(tmpDir, retryContext, null);
|
||||||
|
|
||||||
expect(result.action).toBe('cancel');
|
expect(result.action).toBe('cancel');
|
||||||
expect(result.task).toBe('');
|
expect(result.task).toBe('');
|
||||||
@ -385,6 +389,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
const retryContext: RetryContext = {
|
const retryContext: RetryContext = {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'optimize-review',
|
taskName: 'optimize-review',
|
||||||
|
taskContent: 'Optimize the review step',
|
||||||
createdAt: '2026-02-15T18:00:00Z',
|
createdAt: '2026-02-15T18:00:00Z',
|
||||||
failedMovement: 'review',
|
failedMovement: 'review',
|
||||||
error: 'Timeout',
|
error: 'Timeout',
|
||||||
@ -401,7 +406,7 @@ describe('E2E: Retry mode with failure context injection', () => {
|
|||||||
run: null,
|
run: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runRetryMode(tmpDir, retryContext);
|
const result = await runRetryMode(tmpDir, retryContext, null);
|
||||||
|
|
||||||
expect(result.action).toBe('execute');
|
expect(result.action).toBe('execute');
|
||||||
expect(result.task).toBe('Increase review timeout to 600s and add retry logic.');
|
expect(result.task).toBe('Increase review timeout to 600s and add retry logic.');
|
||||||
|
|||||||
104
src/__tests__/orderReader.test.ts
Normal file
104
src/__tests__/orderReader.test.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* Unit tests for orderReader: findPreviousOrderContent
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { findPreviousOrderContent } from '../features/interactive/orderReader.js';
|
||||||
|
|
||||||
|
const TEST_DIR = join(process.cwd(), 'tmp-test-order-reader');
|
||||||
|
|
||||||
|
function createRunWithOrder(slug: string, content: string): void {
|
||||||
|
const orderDir = join(TEST_DIR, '.takt', 'runs', slug, 'context', 'task');
|
||||||
|
mkdirSync(orderDir, { recursive: true });
|
||||||
|
writeFileSync(join(orderDir, 'order.md'), content, 'utf-8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRunWithoutOrder(slug: string): void {
|
||||||
|
const runDir = join(TEST_DIR, '.takt', 'runs', slug);
|
||||||
|
mkdirSync(runDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mkdirSync(TEST_DIR, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findPreviousOrderContent', () => {
|
||||||
|
it('should return order content when slug is specified and order.md exists', () => {
|
||||||
|
createRunWithOrder('20260218-run1', '# Task Order\nDo something');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, '20260218-run1');
|
||||||
|
|
||||||
|
expect(result).toBe('# Task Order\nDo something');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when slug is specified but order.md does not exist', () => {
|
||||||
|
createRunWithoutOrder('20260218-run1');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, '20260218-run1');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when slug is specified but run directory does not exist', () => {
|
||||||
|
mkdirSync(join(TEST_DIR, '.takt', 'runs'), { recursive: true });
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, 'nonexistent-slug');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for empty order.md content', () => {
|
||||||
|
createRunWithOrder('20260218-run1', '');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, '20260218-run1');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null for whitespace-only order.md content', () => {
|
||||||
|
createRunWithOrder('20260218-run1', ' \n ');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, '20260218-run1');
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should find order from latest run when slug is null', () => {
|
||||||
|
createRunWithOrder('20260218-run-a', 'First order');
|
||||||
|
createRunWithOrder('20260219-run-b', 'Second order');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, null);
|
||||||
|
|
||||||
|
expect(result).toBe('Second order');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip runs without order.md when searching latest', () => {
|
||||||
|
createRunWithOrder('20260218-run-a', 'First order');
|
||||||
|
createRunWithoutOrder('20260219-run-b');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, null);
|
||||||
|
|
||||||
|
expect(result).toBe('First order');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when no runs have order.md', () => {
|
||||||
|
createRunWithoutOrder('20260218-run-a');
|
||||||
|
createRunWithoutOrder('20260219-run-b');
|
||||||
|
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when .takt/runs directory does not exist', () => {
|
||||||
|
const result = findPreviousOrderContent(TEST_DIR, null);
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -9,6 +9,7 @@ function createRetryContext(overrides?: Partial<RetryContext>): RetryContext {
|
|||||||
return {
|
return {
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'my-task',
|
taskName: 'my-task',
|
||||||
|
taskContent: 'Do something',
|
||||||
createdAt: '2026-02-15T10:00:00Z',
|
createdAt: '2026-02-15T10:00:00Z',
|
||||||
failedMovement: 'review',
|
failedMovement: 'review',
|
||||||
error: 'Timeout',
|
error: 'Timeout',
|
||||||
@ -44,6 +45,7 @@ describe('buildRetryTemplateVars', () => {
|
|||||||
const ctx = createRetryContext({
|
const ctx = createRetryContext({
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'task',
|
taskName: 'task',
|
||||||
|
taskContent: 'Do something',
|
||||||
createdAt: '2026-01-01T00:00:00Z',
|
createdAt: '2026-01-01T00:00:00Z',
|
||||||
failedMovement: '',
|
failedMovement: '',
|
||||||
error: 'Error',
|
error: 'Error',
|
||||||
@ -133,6 +135,7 @@ describe('buildRetryTemplateVars', () => {
|
|||||||
const ctx = createRetryContext({
|
const ctx = createRetryContext({
|
||||||
failure: {
|
failure: {
|
||||||
taskName: 'task',
|
taskName: 'task',
|
||||||
|
taskContent: 'Do something',
|
||||||
createdAt: '2026-01-01T00:00:00Z',
|
createdAt: '2026-01-01T00:00:00Z',
|
||||||
failedMovement: '',
|
failedMovement: '',
|
||||||
error: 'Error',
|
error: 'Error',
|
||||||
@ -144,4 +147,28 @@ describe('buildRetryTemplateVars', () => {
|
|||||||
|
|
||||||
expect(vars.retryNote).toBe('Added more specific error handling');
|
expect(vars.retryNote).toBe('Added more specific error handling');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set hasOrderContent=false when previousOrderContent is null', () => {
|
||||||
|
const ctx = createRetryContext();
|
||||||
|
const vars = buildRetryTemplateVars(ctx, 'en', null);
|
||||||
|
|
||||||
|
expect(vars.hasOrderContent).toBe(false);
|
||||||
|
expect(vars.orderContent).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set hasOrderContent=true and populate orderContent when provided', () => {
|
||||||
|
const ctx = createRetryContext();
|
||||||
|
const vars = buildRetryTemplateVars(ctx, 'en', '# Previous Order\nDo the thing');
|
||||||
|
|
||||||
|
expect(vars.hasOrderContent).toBe(true);
|
||||||
|
expect(vars.orderContent).toBe('# Previous Order\nDo the thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default hasOrderContent to false when previousOrderContent is omitted', () => {
|
||||||
|
const ctx = createRetryContext();
|
||||||
|
const vars = buildRetryTemplateVars(ctx, 'en');
|
||||||
|
|
||||||
|
expect(vars.hasOrderContent).toBe(false);
|
||||||
|
expect(vars.orderContent).toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -82,6 +82,8 @@ vi.mock('../features/interactive/index.js', () => ({
|
|||||||
listRecentRuns: (...args: unknown[]) => mockListRecentRuns(...args),
|
listRecentRuns: (...args: unknown[]) => mockListRecentRuns(...args),
|
||||||
selectRun: (...args: unknown[]) => mockSelectRun(...args),
|
selectRun: (...args: unknown[]) => mockSelectRun(...args),
|
||||||
loadRunSessionContext: (...args: unknown[]) => mockLoadRunSessionContext(...args),
|
loadRunSessionContext: (...args: unknown[]) => mockLoadRunSessionContext(...args),
|
||||||
|
findRunForTask: vi.fn(() => null),
|
||||||
|
findPreviousOrderContent: vi.fn(() => null),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../features/tasks/execute/taskExecution.js', () => ({
|
vi.mock('../features/tasks/execute/taskExecution.js', () => ({
|
||||||
@ -191,6 +193,7 @@ describe('instructBranch direct execution flow', () => {
|
|||||||
'',
|
'',
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
undefined,
|
undefined,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -227,6 +230,7 @@ describe('instructBranch direct execution flow', () => {
|
|||||||
'',
|
'',
|
||||||
expect.anything(),
|
expect.anything(),
|
||||||
runContext,
|
runContext,
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -73,6 +73,7 @@ vi.mock('../features/interactive/index.js', () => ({
|
|||||||
runTask: '', runPiece: '', runStatus: '', runMovementLogs: '', runReports: '',
|
runTask: '', runPiece: '', runStatus: '', runMovementLogs: '', runReports: '',
|
||||||
})),
|
})),
|
||||||
runRetryMode: (...args: unknown[]) => mockRunRetryMode(...args),
|
runRetryMode: (...args: unknown[]) => mockRunRetryMode(...args),
|
||||||
|
findPreviousOrderContent: vi.fn(() => null),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../infra/task/index.js', () => ({
|
vi.mock('../infra/task/index.js', () => ({
|
||||||
@ -151,6 +152,7 @@ describe('retryFailedTask', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
failure: expect.objectContaining({ taskName: 'my-task', taskContent: 'Do something' }),
|
failure: expect.objectContaining({ taskName: 'my-task', taskContent: 'Do something' }),
|
||||||
}),
|
}),
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
expect(mockStartReExecution).toHaveBeenCalledWith('my-task', ['failed'], undefined, '追加指示A');
|
expect(mockStartReExecution).toHaveBeenCalledWith('my-task', ['failed'], undefined, '追加指示A');
|
||||||
expect(mockExecuteAndCompleteTask).toHaveBeenCalled();
|
expect(mockExecuteAndCompleteTask).toHaveBeenCalled();
|
||||||
|
|||||||
@ -186,6 +186,8 @@ export interface ConversationStrategy {
|
|||||||
introMessage: string;
|
introMessage: string;
|
||||||
/** Custom action selector (optional). If not provided, uses default selectPostSummaryAction. */
|
/** Custom action selector (optional). If not provided, uses default selectPostSummaryAction. */
|
||||||
selectAction?: (task: string, lang: 'en' | 'ja') => Promise<PostSummaryAction | null>;
|
selectAction?: (task: string, lang: 'en' | 'ja') => Promise<PostSummaryAction | null>;
|
||||||
|
/** Previous order.md content for /replay command (retry/instruct only) */
|
||||||
|
previousOrderContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -300,6 +302,16 @@ export async function runConversationLoop(
|
|||||||
return { action: selectedAction, task };
|
return { action: selectedAction, task };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trimmed === '/replay') {
|
||||||
|
if (!strategy.previousOrderContent) {
|
||||||
|
const replayNoOrder = getLabel('instruct.ui.replayNoOrder', ctx.lang);
|
||||||
|
info(replayNoOrder);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.info('Replay command');
|
||||||
|
return { action: 'execute', task: strategy.previousOrderContent };
|
||||||
|
}
|
||||||
|
|
||||||
if (trimmed === '/cancel') {
|
if (trimmed === '/cancel') {
|
||||||
info(ui.cancelled);
|
info(ui.cancelled);
|
||||||
return { action: 'cancel', task: '' };
|
return { action: 'cancel', task: '' };
|
||||||
|
|||||||
@ -25,3 +25,4 @@ export { selectRun } from './runSelector.js';
|
|||||||
export { listRecentRuns, findRunForTask, loadRunSessionContext, formatRunSessionForPrompt, getRunPaths, type RunSessionContext, type RunPaths } from './runSessionReader.js';
|
export { listRecentRuns, findRunForTask, loadRunSessionContext, formatRunSessionForPrompt, getRunPaths, type RunSessionContext, type RunPaths } from './runSessionReader.js';
|
||||||
export { runRetryMode, buildRetryTemplateVars, type RetryContext, type RetryFailureInfo, type RetryRunInfo } from './retryMode.js';
|
export { runRetryMode, buildRetryTemplateVars, type RetryContext, type RetryFailureInfo, type RetryRunInfo } from './retryMode.js';
|
||||||
export { dispatchConversationAction, type ConversationActionResult } from './actionDispatcher.js';
|
export { dispatchConversationAction, type ConversationActionResult } from './actionDispatcher.js';
|
||||||
|
export { findPreviousOrderContent } from './orderReader.js';
|
||||||
|
|||||||
@ -197,13 +197,15 @@ export interface InteractiveSummaryUIText {
|
|||||||
export function buildSummaryActionOptions(
|
export function buildSummaryActionOptions(
|
||||||
labels: SummaryActionLabels,
|
labels: SummaryActionLabels,
|
||||||
append: readonly SummaryActionValue[] = [],
|
append: readonly SummaryActionValue[] = [],
|
||||||
|
exclude: readonly SummaryActionValue[] = [],
|
||||||
): SummaryActionOption[] {
|
): SummaryActionOption[] {
|
||||||
const order = [...BASE_SUMMARY_ACTIONS, ...append];
|
const order = [...BASE_SUMMARY_ACTIONS, ...append];
|
||||||
|
const excluded = new Set(exclude);
|
||||||
const seen = new Set<SummaryActionValue>();
|
const seen = new Set<SummaryActionValue>();
|
||||||
const options: SummaryActionOption[] = [];
|
const options: SummaryActionOption[] = [];
|
||||||
|
|
||||||
for (const action of order) {
|
for (const action of order) {
|
||||||
if (seen.has(action)) {
|
if (seen.has(action) || excluded.has(action)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
seen.add(action);
|
seen.add(action);
|
||||||
@ -261,3 +263,52 @@ export function selectPostSummaryAction(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the /replay command hint for intro messages.
|
||||||
|
*
|
||||||
|
* Returns a hint string when previous order content is available, empty string otherwise.
|
||||||
|
*/
|
||||||
|
export function buildReplayHint(lang: 'en' | 'ja', hasPreviousOrder: boolean): string {
|
||||||
|
if (!hasPreviousOrder) return '';
|
||||||
|
return lang === 'ja'
|
||||||
|
? ', /replay(前回の指示書を再投入)'
|
||||||
|
: ', /replay (resubmit previous order)';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** UI labels required by createSelectActionWithoutExecute */
|
||||||
|
export interface ActionWithoutExecuteUIText {
|
||||||
|
proposed: string;
|
||||||
|
actionPrompt: string;
|
||||||
|
actions: {
|
||||||
|
execute: string;
|
||||||
|
saveTask: string;
|
||||||
|
continue: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an action selector that excludes the 'execute' option.
|
||||||
|
*
|
||||||
|
* Used by retry and instruct modes where worktree execution is assumed.
|
||||||
|
*/
|
||||||
|
export function createSelectActionWithoutExecute(
|
||||||
|
ui: ActionWithoutExecuteUIText,
|
||||||
|
): (task: string, lang: 'en' | 'ja') => Promise<PostSummaryAction | null> {
|
||||||
|
return async (task: string, _lang: 'en' | 'ja'): Promise<PostSummaryAction | null> => {
|
||||||
|
return selectSummaryAction(
|
||||||
|
task,
|
||||||
|
ui.proposed,
|
||||||
|
ui.actionPrompt,
|
||||||
|
buildSummaryActionOptions(
|
||||||
|
{
|
||||||
|
execute: ui.actions.execute,
|
||||||
|
saveTask: ui.actions.saveTask,
|
||||||
|
continue: ui.actions.continue,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
['execute'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
57
src/features/interactive/orderReader.ts
Normal file
57
src/features/interactive/orderReader.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Order reader for retry/instruct modes.
|
||||||
|
*
|
||||||
|
* Reads the previous order.md from a run's context directory
|
||||||
|
* to inject into conversation system prompts.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and read the previous order.md content from a run directory.
|
||||||
|
*
|
||||||
|
* When runSlug is provided, reads directly from that run's context.
|
||||||
|
* When runSlug is null, scans .takt/runs/ directories in reverse order
|
||||||
|
* and returns the first order.md found.
|
||||||
|
*
|
||||||
|
* @returns The order.md content, or null if not found.
|
||||||
|
*/
|
||||||
|
export function findPreviousOrderContent(worktreeCwd: string, runSlug: string | null): string | null {
|
||||||
|
if (runSlug) {
|
||||||
|
return readOrderFromRun(worktreeCwd, runSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
return findOrderFromLatestRun(worktreeCwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readOrderFromRun(worktreeCwd: string, slug: string): string | null {
|
||||||
|
const orderPath = join(worktreeCwd, '.takt', 'runs', slug, 'context', 'task', 'order.md');
|
||||||
|
if (!existsSync(orderPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const content = readFileSync(orderPath, 'utf-8').trim();
|
||||||
|
return content || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findOrderFromLatestRun(worktreeCwd: string): string | null {
|
||||||
|
const runsDir = join(worktreeCwd, '.takt', 'runs');
|
||||||
|
if (!existsSync(runsDir)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = readdirSync(runsDir, { withFileTypes: true })
|
||||||
|
.filter((e) => e.isDirectory())
|
||||||
|
.map((e) => e.name)
|
||||||
|
.sort()
|
||||||
|
.reverse();
|
||||||
|
|
||||||
|
for (const slug of entries) {
|
||||||
|
const content = readOrderFromRun(worktreeCwd, slug);
|
||||||
|
if (content) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -11,11 +11,10 @@ import {
|
|||||||
runConversationLoop,
|
runConversationLoop,
|
||||||
type SessionContext,
|
type SessionContext,
|
||||||
type ConversationStrategy,
|
type ConversationStrategy,
|
||||||
type PostSummaryAction,
|
|
||||||
} from './conversationLoop.js';
|
} from './conversationLoop.js';
|
||||||
import {
|
import {
|
||||||
buildSummaryActionOptions,
|
createSelectActionWithoutExecute,
|
||||||
selectSummaryAction,
|
buildReplayHint,
|
||||||
formatMovementPreviews,
|
formatMovementPreviews,
|
||||||
type PieceContext,
|
type PieceContext,
|
||||||
} from './interactive-summary.js';
|
} from './interactive-summary.js';
|
||||||
@ -60,7 +59,7 @@ const RETRY_TOOLS = ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
|||||||
/**
|
/**
|
||||||
* Convert RetryContext into template variable map.
|
* Convert RetryContext into template variable map.
|
||||||
*/
|
*/
|
||||||
export function buildRetryTemplateVars(ctx: RetryContext, lang: 'en' | 'ja'): Record<string, string | boolean> {
|
export function buildRetryTemplateVars(ctx: RetryContext, lang: 'en' | 'ja', previousOrderContent: string | null = null): Record<string, string | boolean> {
|
||||||
const hasPiecePreview = !!ctx.pieceContext.movementPreviews?.length;
|
const hasPiecePreview = !!ctx.pieceContext.movementPreviews?.length;
|
||||||
const movementDetails = hasPiecePreview
|
const movementDetails = hasPiecePreview
|
||||||
? formatMovementPreviews(ctx.pieceContext.movementPreviews!, lang)
|
? formatMovementPreviews(ctx.pieceContext.movementPreviews!, lang)
|
||||||
@ -88,21 +87,8 @@ export function buildRetryTemplateVars(ctx: RetryContext, lang: 'en' | 'ja'): Re
|
|||||||
runStatus: hasRun ? ctx.run!.status : '',
|
runStatus: hasRun ? ctx.run!.status : '',
|
||||||
runMovementLogs: hasRun ? ctx.run!.movementLogs : '',
|
runMovementLogs: hasRun ? ctx.run!.movementLogs : '',
|
||||||
runReports: hasRun ? ctx.run!.reports : '',
|
runReports: hasRun ? ctx.run!.reports : '',
|
||||||
};
|
hasOrderContent: previousOrderContent !== null,
|
||||||
}
|
orderContent: previousOrderContent ?? '',
|
||||||
|
|
||||||
function createSelectRetryAction(ui: InstructUIText): (task: string, lang: 'en' | 'ja') => Promise<PostSummaryAction | null> {
|
|
||||||
return async (task: string, _lang: 'en' | 'ja'): Promise<PostSummaryAction | null> => {
|
|
||||||
return selectSummaryAction(
|
|
||||||
task,
|
|
||||||
ui.proposed,
|
|
||||||
ui.actionPrompt,
|
|
||||||
buildSummaryActionOptions({
|
|
||||||
execute: ui.actions.execute,
|
|
||||||
saveTask: ui.actions.saveTask,
|
|
||||||
continue: ui.actions.continue,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +101,7 @@ function createSelectRetryAction(ui: InstructUIText): (task: string, lang: 'en'
|
|||||||
export async function runRetryMode(
|
export async function runRetryMode(
|
||||||
cwd: string,
|
cwd: string,
|
||||||
retryContext: RetryContext,
|
retryContext: RetryContext,
|
||||||
|
previousOrderContent: string | null,
|
||||||
): Promise<InstructModeResult> {
|
): Promise<InstructModeResult> {
|
||||||
const globalConfig = loadGlobalConfig();
|
const globalConfig = loadGlobalConfig();
|
||||||
const lang = resolveLanguage(globalConfig.language);
|
const lang = resolveLanguage(globalConfig.language);
|
||||||
@ -130,12 +117,13 @@ export async function runRetryMode(
|
|||||||
|
|
||||||
const ui = getLabelObject<InstructUIText>('instruct.ui', ctx.lang);
|
const ui = getLabelObject<InstructUIText>('instruct.ui', ctx.lang);
|
||||||
|
|
||||||
const templateVars = buildRetryTemplateVars(retryContext, lang);
|
const templateVars = buildRetryTemplateVars(retryContext, lang, previousOrderContent);
|
||||||
const systemPrompt = loadTemplate('score_retry_system_prompt', ctx.lang, templateVars);
|
const systemPrompt = loadTemplate('score_retry_system_prompt', ctx.lang, templateVars);
|
||||||
|
|
||||||
|
const replayHint = buildReplayHint(ctx.lang, previousOrderContent !== null);
|
||||||
const introLabel = ctx.lang === 'ja'
|
const introLabel = ctx.lang === 'ja'
|
||||||
? `## リトライ: ${retryContext.failure.taskName}\n\nブランチ: ${retryContext.branchName}\n\n${ui.intro}`
|
? `## リトライ: ${retryContext.failure.taskName}\n\nブランチ: ${retryContext.branchName}\n\n${ui.intro}${replayHint}`
|
||||||
: `## Retry: ${retryContext.failure.taskName}\n\nBranch: ${retryContext.branchName}\n\n${ui.intro}`;
|
: `## Retry: ${retryContext.failure.taskName}\n\nBranch: ${retryContext.branchName}\n\n${ui.intro}${replayHint}`;
|
||||||
|
|
||||||
const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {});
|
const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {});
|
||||||
|
|
||||||
@ -154,7 +142,8 @@ export async function runRetryMode(
|
|||||||
allowedTools: RETRY_TOOLS,
|
allowedTools: RETRY_TOOLS,
|
||||||
transformPrompt: injectPolicy,
|
transformPrompt: injectPolicy,
|
||||||
introMessage: introLabel,
|
introMessage: introLabel,
|
||||||
selectAction: createSelectRetryAction(ui),
|
selectAction: createSelectActionWithoutExecute(ui),
|
||||||
|
previousOrderContent: previousOrderContent ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runConversationLoop(cwd, ctx, strategy, retryContext.pieceContext, undefined);
|
const result = await runConversationLoop(cwd, ctx, strategy, retryContext.pieceContext, undefined);
|
||||||
|
|||||||
@ -11,15 +11,13 @@ import {
|
|||||||
runConversationLoop,
|
runConversationLoop,
|
||||||
type SessionContext,
|
type SessionContext,
|
||||||
type ConversationStrategy,
|
type ConversationStrategy,
|
||||||
type PostSummaryAction,
|
|
||||||
} from '../../interactive/conversationLoop.js';
|
} from '../../interactive/conversationLoop.js';
|
||||||
import {
|
import {
|
||||||
resolveLanguage,
|
resolveLanguage,
|
||||||
buildSummaryActionOptions,
|
|
||||||
selectSummaryAction,
|
|
||||||
formatMovementPreviews,
|
formatMovementPreviews,
|
||||||
type PieceContext,
|
type PieceContext,
|
||||||
} from '../../interactive/interactive.js';
|
} from '../../interactive/interactive.js';
|
||||||
|
import { createSelectActionWithoutExecute, buildReplayHint } from '../../interactive/interactive-summary.js';
|
||||||
import { type RunSessionContext, formatRunSessionForPrompt } from '../../interactive/runSessionReader.js';
|
import { type RunSessionContext, formatRunSessionForPrompt } from '../../interactive/runSessionReader.js';
|
||||||
import { loadTemplate } from '../../../shared/prompts/index.js';
|
import { loadTemplate } from '../../../shared/prompts/index.js';
|
||||||
import { getLabelObject } from '../../../shared/i18n/index.js';
|
import { getLabelObject } from '../../../shared/i18n/index.js';
|
||||||
@ -50,21 +48,6 @@ export interface InstructUIText {
|
|||||||
|
|
||||||
const INSTRUCT_TOOLS = ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
const INSTRUCT_TOOLS = ['Read', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
||||||
|
|
||||||
function createSelectInstructAction(ui: InstructUIText): (task: string, lang: 'en' | 'ja') => Promise<PostSummaryAction | null> {
|
|
||||||
return async (task: string, _lang: 'en' | 'ja'): Promise<PostSummaryAction | null> => {
|
|
||||||
return selectSummaryAction(
|
|
||||||
task,
|
|
||||||
ui.proposed,
|
|
||||||
ui.actionPrompt,
|
|
||||||
buildSummaryActionOptions({
|
|
||||||
execute: ui.actions.execute,
|
|
||||||
saveTask: ui.actions.saveTask,
|
|
||||||
continue: ui.actions.continue,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildInstructTemplateVars(
|
function buildInstructTemplateVars(
|
||||||
branchContext: string,
|
branchContext: string,
|
||||||
branchName: string,
|
branchName: string,
|
||||||
@ -74,6 +57,7 @@ function buildInstructTemplateVars(
|
|||||||
lang: 'en' | 'ja',
|
lang: 'en' | 'ja',
|
||||||
pieceContext?: PieceContext,
|
pieceContext?: PieceContext,
|
||||||
runSessionContext?: RunSessionContext,
|
runSessionContext?: RunSessionContext,
|
||||||
|
previousOrderContent?: string | null,
|
||||||
): Record<string, string | boolean> {
|
): Record<string, string | boolean> {
|
||||||
const hasPiecePreview = !!pieceContext?.movementPreviews?.length;
|
const hasPiecePreview = !!pieceContext?.movementPreviews?.length;
|
||||||
const movementDetails = hasPiecePreview
|
const movementDetails = hasPiecePreview
|
||||||
@ -96,6 +80,8 @@ function buildInstructTemplateVars(
|
|||||||
movementDetails,
|
movementDetails,
|
||||||
hasRunSession,
|
hasRunSession,
|
||||||
...runPromptVars,
|
...runPromptVars,
|
||||||
|
hasOrderContent: !!previousOrderContent,
|
||||||
|
orderContent: previousOrderContent ?? '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,6 +94,7 @@ export async function runInstructMode(
|
|||||||
retryNote: string,
|
retryNote: string,
|
||||||
pieceContext?: PieceContext,
|
pieceContext?: PieceContext,
|
||||||
runSessionContext?: RunSessionContext,
|
runSessionContext?: RunSessionContext,
|
||||||
|
previousOrderContent?: string | null,
|
||||||
): Promise<InstructModeResult> {
|
): Promise<InstructModeResult> {
|
||||||
const globalConfig = loadGlobalConfig();
|
const globalConfig = loadGlobalConfig();
|
||||||
const lang = resolveLanguage(globalConfig.language);
|
const lang = resolveLanguage(globalConfig.language);
|
||||||
@ -125,10 +112,12 @@ export async function runInstructMode(
|
|||||||
|
|
||||||
const templateVars = buildInstructTemplateVars(
|
const templateVars = buildInstructTemplateVars(
|
||||||
branchContext, branchName, taskName, taskContent, retryNote, lang,
|
branchContext, branchName, taskName, taskContent, retryNote, lang,
|
||||||
pieceContext, runSessionContext,
|
pieceContext, runSessionContext, previousOrderContent,
|
||||||
);
|
);
|
||||||
const systemPrompt = loadTemplate('score_instruct_system_prompt', ctx.lang, templateVars);
|
const systemPrompt = loadTemplate('score_instruct_system_prompt', ctx.lang, templateVars);
|
||||||
|
|
||||||
|
const replayHint = buildReplayHint(ctx.lang, !!previousOrderContent);
|
||||||
|
|
||||||
const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {});
|
const policyContent = loadTemplate('score_interactive_policy', ctx.lang, {});
|
||||||
|
|
||||||
function injectPolicy(userMessage: string): string {
|
function injectPolicy(userMessage: string): string {
|
||||||
@ -145,8 +134,9 @@ export async function runInstructMode(
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
allowedTools: INSTRUCT_TOOLS,
|
allowedTools: INSTRUCT_TOOLS,
|
||||||
transformPrompt: injectPolicy,
|
transformPrompt: injectPolicy,
|
||||||
introMessage: ui.intro,
|
introMessage: `${ui.intro}${replayHint}`,
|
||||||
selectAction: createSelectInstructAction(ui),
|
selectAction: createSelectActionWithoutExecute(ui),
|
||||||
|
previousOrderContent: previousOrderContent ?? undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await runConversationLoop(cwd, ctx, strategy, pieceContext, undefined);
|
const result = await runConversationLoop(cwd, ctx, strategy, pieceContext, undefined);
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import { runInstructMode } from './instructMode.js';
|
|||||||
import { selectPiece } from '../../pieceSelection/index.js';
|
import { selectPiece } from '../../pieceSelection/index.js';
|
||||||
import { dispatchConversationAction } from '../../interactive/actionDispatcher.js';
|
import { dispatchConversationAction } from '../../interactive/actionDispatcher.js';
|
||||||
import type { PieceContext } from '../../interactive/interactive.js';
|
import type { PieceContext } from '../../interactive/interactive.js';
|
||||||
import { resolveLanguage } from '../../interactive/index.js';
|
import { resolveLanguage, findRunForTask, findPreviousOrderContent } from '../../interactive/index.js';
|
||||||
import { type BranchActionTarget, resolveTargetBranch } from './taskActionTarget.js';
|
import { type BranchActionTarget, resolveTargetBranch } from './taskActionTarget.js';
|
||||||
import { appendRetryNote, selectRunSessionContext } from './requeueHelpers.js';
|
import { appendRetryNote, selectRunSessionContext } from './requeueHelpers.js';
|
||||||
import { executeAndCompleteTask } from '../execute/taskExecution.js';
|
import { executeAndCompleteTask } from '../execute/taskExecution.js';
|
||||||
@ -105,13 +105,15 @@ export async function instructBranch(
|
|||||||
const lang = resolveLanguage(globalConfig.language);
|
const lang = resolveLanguage(globalConfig.language);
|
||||||
// Runs data lives in the worktree (written during previous execution)
|
// Runs data lives in the worktree (written during previous execution)
|
||||||
const runSessionContext = await selectRunSessionContext(worktreePath, lang);
|
const runSessionContext = await selectRunSessionContext(worktreePath, lang);
|
||||||
|
const matchedSlug = findRunForTask(worktreePath, target.content);
|
||||||
|
const previousOrderContent = findPreviousOrderContent(worktreePath, matchedSlug);
|
||||||
|
|
||||||
const branchContext = getBranchContext(projectDir, branch);
|
const branchContext = getBranchContext(projectDir, branch);
|
||||||
|
|
||||||
const result = await runInstructMode(
|
const result = await runInstructMode(
|
||||||
worktreePath, branchContext, branch,
|
worktreePath, branchContext, branch,
|
||||||
target.name, target.content, target.data?.retry_note ?? '',
|
target.name, target.content, target.data?.retry_note ?? '',
|
||||||
pieceContext, runSessionContext,
|
pieceContext, runSessionContext, previousOrderContent,
|
||||||
);
|
);
|
||||||
|
|
||||||
const executeWithInstruction = async (instruction: string): Promise<boolean> => {
|
const executeWithInstruction = async (instruction: string): Promise<boolean> => {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
getRunPaths,
|
getRunPaths,
|
||||||
formatRunSessionForPrompt,
|
formatRunSessionForPrompt,
|
||||||
runRetryMode,
|
runRetryMode,
|
||||||
|
findPreviousOrderContent,
|
||||||
type RetryContext,
|
type RetryContext,
|
||||||
type RetryFailureInfo,
|
type RetryFailureInfo,
|
||||||
type RetryRunInfo,
|
type RetryRunInfo,
|
||||||
@ -156,6 +157,7 @@ export async function retryFailedTask(
|
|||||||
// Runs data lives in the worktree (written during previous execution)
|
// Runs data lives in the worktree (written during previous execution)
|
||||||
const matchedSlug = findRunForTask(worktreePath, task.content);
|
const matchedSlug = findRunForTask(worktreePath, task.content);
|
||||||
const runInfo = matchedSlug ? buildRetryRunInfo(worktreePath, matchedSlug) : null;
|
const runInfo = matchedSlug ? buildRetryRunInfo(worktreePath, matchedSlug) : null;
|
||||||
|
const previousOrderContent = findPreviousOrderContent(worktreePath, matchedSlug);
|
||||||
|
|
||||||
blankLine();
|
blankLine();
|
||||||
const branchName = task.branch ?? task.name;
|
const branchName = task.branch ?? task.name;
|
||||||
@ -166,7 +168,7 @@ export async function retryFailedTask(
|
|||||||
run: runInfo,
|
run: runInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
const retryResult = await runRetryMode(worktreePath, retryContext);
|
const retryResult = await runRetryMode(worktreePath, retryContext, previousOrderContent);
|
||||||
if (retryResult.action === 'cancel') {
|
if (retryResult.action === 'cancel') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,7 @@ instruct:
|
|||||||
saveTask: "Save as Task"
|
saveTask: "Save as Task"
|
||||||
continue: "Continue editing"
|
continue: "Continue editing"
|
||||||
cancelled: "Cancelled"
|
cancelled: "Cancelled"
|
||||||
|
replayNoOrder: "Previous order (order.md) not found"
|
||||||
|
|
||||||
run:
|
run:
|
||||||
notifyComplete: "Run complete ({total} tasks)"
|
notifyComplete: "Run complete ({total} tasks)"
|
||||||
|
|||||||
@ -87,6 +87,7 @@ instruct:
|
|||||||
saveTask: "タスクにつむ"
|
saveTask: "タスクにつむ"
|
||||||
continue: "会話を続ける"
|
continue: "会話を続ける"
|
||||||
cancelled: "キャンセルしました"
|
cancelled: "キャンセルしました"
|
||||||
|
replayNoOrder: "前回の指示書(order.md)が見つかりません"
|
||||||
|
|
||||||
run:
|
run:
|
||||||
notifyComplete: "run完了 ({total} tasks)"
|
notifyComplete: "run完了 ({total} tasks)"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
template: score_instruct_system_prompt
|
template: score_instruct_system_prompt
|
||||||
role: system prompt for instruct assistant mode (completed/failed tasks)
|
role: system prompt for instruct assistant mode (completed/failed tasks)
|
||||||
vars: taskName, taskContent, branchName, branchContext, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRunSession, runTask, runPiece, runStatus, runMovementLogs, runReports
|
vars: taskName, taskContent, branchName, branchContext, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRunSession, runTask, runPiece, runStatus, runMovementLogs, runReports, hasOrderContent, orderContent
|
||||||
caller: features/tasks/list/instructMode
|
caller: features/tasks/list/instructMode
|
||||||
-->
|
-->
|
||||||
# Additional Instruction Assistant
|
# Additional Instruction Assistant
|
||||||
@ -85,3 +85,11 @@ The user has selected a previous run for reference. Use this information to help
|
|||||||
- Help the user identify what went wrong or what needs additional work
|
- Help the user identify what went wrong or what needs additional work
|
||||||
- Suggest concrete follow-up instructions based on the run results
|
- Suggest concrete follow-up instructions based on the run results
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasOrderContent}}
|
||||||
|
|
||||||
|
## Previous Order (order.md)
|
||||||
|
|
||||||
|
The instruction document used in the previous execution. Use it as a reference for re-execution.
|
||||||
|
|
||||||
|
{{orderContent}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
template: score_retry_system_prompt
|
template: score_retry_system_prompt
|
||||||
role: system prompt for retry assistant mode
|
role: system prompt for retry assistant mode
|
||||||
vars: taskName, taskContent, branchName, createdAt, failedMovement, failureError, failureLastMessage, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRun, runLogsDir, runReportsDir, runTask, runPiece, runStatus, runMovementLogs, runReports
|
vars: taskName, taskContent, branchName, createdAt, failedMovement, failureError, failureLastMessage, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRun, runLogsDir, runReportsDir, runTask, runPiece, runStatus, runMovementLogs, runReports, hasOrderContent, orderContent
|
||||||
caller: features/interactive/retryMode
|
caller: features/interactive/retryMode
|
||||||
-->
|
-->
|
||||||
# Retry Assistant
|
# Retry Assistant
|
||||||
@ -95,3 +95,11 @@ Logs and reports from the previous execution are available for reference. Use th
|
|||||||
- Cross-reference the plans and implementation recorded in reports with the actual failure point
|
- Cross-reference the plans and implementation recorded in reports with the actual failure point
|
||||||
- If the user wants more details, files in the directories above can be read using the Read tool
|
- If the user wants more details, files in the directories above can be read using the Read tool
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasOrderContent}}
|
||||||
|
|
||||||
|
## Previous Order (order.md)
|
||||||
|
|
||||||
|
The instruction document used in the previous execution. Use it as a reference for re-execution.
|
||||||
|
|
||||||
|
{{orderContent}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
template: score_instruct_system_prompt
|
template: score_instruct_system_prompt
|
||||||
role: system prompt for instruct assistant mode (completed/failed tasks)
|
role: system prompt for instruct assistant mode (completed/failed tasks)
|
||||||
vars: taskName, taskContent, branchName, branchContext, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRunSession, runTask, runPiece, runStatus, runMovementLogs, runReports
|
vars: taskName, taskContent, branchName, branchContext, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRunSession, runTask, runPiece, runStatus, runMovementLogs, runReports, hasOrderContent, orderContent
|
||||||
caller: features/tasks/list/instructMode
|
caller: features/tasks/list/instructMode
|
||||||
-->
|
-->
|
||||||
# 追加指示アシスタント
|
# 追加指示アシスタント
|
||||||
@ -85,3 +85,11 @@
|
|||||||
- 何がうまくいかなかったか、追加作業が必要な箇所をユーザーが特定できるよう支援してください
|
- 何がうまくいかなかったか、追加作業が必要な箇所をユーザーが特定できるよう支援してください
|
||||||
- 実行結果に基づいて、具体的なフォローアップ指示を提案してください
|
- 実行結果に基づいて、具体的なフォローアップ指示を提案してください
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasOrderContent}}
|
||||||
|
|
||||||
|
## 前回の指示書(order.md)
|
||||||
|
|
||||||
|
前回の実行時に使用された指示書です。再実行の参考にしてください。
|
||||||
|
|
||||||
|
{{orderContent}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<!--
|
<!--
|
||||||
template: score_retry_system_prompt
|
template: score_retry_system_prompt
|
||||||
role: system prompt for retry assistant mode
|
role: system prompt for retry assistant mode
|
||||||
vars: taskName, taskContent, branchName, createdAt, failedMovement, failureError, failureLastMessage, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRun, runLogsDir, runReportsDir, runTask, runPiece, runStatus, runMovementLogs, runReports
|
vars: taskName, taskContent, branchName, createdAt, failedMovement, failureError, failureLastMessage, retryNote, hasPiecePreview, pieceStructure, movementDetails, hasRun, runLogsDir, runReportsDir, runTask, runPiece, runStatus, runMovementLogs, runReports, hasOrderContent, orderContent
|
||||||
caller: features/interactive/retryMode
|
caller: features/interactive/retryMode
|
||||||
-->
|
-->
|
||||||
# リトライアシスタント
|
# リトライアシスタント
|
||||||
@ -95,3 +95,11 @@
|
|||||||
- レポートに記録された計画や実装内容と、実際の失敗箇所を照合してください
|
- レポートに記録された計画や実装内容と、実際の失敗箇所を照合してください
|
||||||
- ユーザーが詳細を知りたい場合は、上記ディレクトリのファイルを Read ツールで参照できます
|
- ユーザーが詳細を知りたい場合は、上記ディレクトリのファイルを Read ツールで参照できます
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if hasOrderContent}}
|
||||||
|
|
||||||
|
## 前回の指示書(order.md)
|
||||||
|
|
||||||
|
前回の実行時に使用された指示書です。再実行の参考にしてください。
|
||||||
|
|
||||||
|
{{orderContent}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user