takt: github-issue-188 (#190)

This commit is contained in:
nrs 2026-02-09 23:54:01 +09:00 committed by GitHub
parent c7305374d7
commit ec88b90632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 674 additions and 9 deletions

View File

@ -465,6 +465,7 @@ TAKT includes multiple builtin pieces:
| `review-only` | Read-only code review piece that makes no changes. |
| `structural-reform` | Full project review and structural reform: iterative codebase restructuring with staged file splits. |
| `unit-test` | Unit test focused piece: test analysis → test implementation → review → fix. |
| `e2e-test` | E2E test focused piece: E2E analysis → E2E implementation → review → fix (Vitest-based E2E flow). |
**Per-persona provider overrides:** Use `persona_providers` in config to route specific personas to different providers (e.g., coder on Codex, reviewers on Claude) without duplicating pieces.

View File

@ -0,0 +1,51 @@
Implement E2E tests according to the test plan.
Refer only to files within the Report Directory shown in the Piece Context. Do not search or reference other report directories.
**Actions:**
1. Review the test plan report
2. Implement or update tests following existing E2E layout (e.g., `e2e/specs/`)
3. Run E2E tests (minimum: `npm run test:e2e:mock`, and targeted spec runs when needed)
4. If tests fail, analyze root cause, fix test or code, and rerun
5. Confirm related existing tests are not broken
**Constraints:**
- Keep the current E2E framework (Vitest) unchanged
- Keep one scenario per test and make assertions explicit
- Reuse existing fixtures/helpers/mock strategy for external dependencies
**Scope output contract (create at the start of implementation):**
```markdown
# Change Scope Declaration
## Task
{One-line task summary}
## Planned changes
| Type | File |
|------|------|
| Create | `e2e/specs/example.e2e.ts` |
## Estimated size
Small / Medium / Large
## Impact area
- {Affected modules or features}
```
**Decisions output contract (at implementation completion, only if decisions were made):**
```markdown
# Decision Log
## 1. {Decision}
- **Context**: {Why the decision was needed}
- **Options considered**: {List of options}
- **Rationale**: {Reason for the choice}
```
**Required output (include headings)**
## Work results
- {Summary of actions taken}
## Changes made
- {Summary of changes}
## Test results
- {Command executed and results}

View File

@ -0,0 +1,11 @@
Analyze the target code and identify missing E2E tests.
**Note:** If a Previous Response exists, this is a replan due to rejection.
Revise the test plan taking that feedback into account.
**Actions:**
1. Read target features, implementation, and existing E2E specs (`e2e/specs/**/*.e2e.ts`) to understand behavior
2. Summarize current E2E coverage (happy path, failure path, regression points)
3. Identify missing E2E scenarios with expected outcomes and observability points
4. Specify execution commands (`npm run test:e2e:mock` and, when needed, `npx vitest run e2e/specs/<target>.e2e.ts`)
5. Provide concrete guidance for failure analysis → fix → rerun workflow

View File

@ -11,6 +11,7 @@ piece_categories:
- review-fix-minimal
- review-only
- unit-test
- e2e-test
🎨 Frontend: {}
⚙️ Backend: {}
🔧 Expert:

View File

@ -0,0 +1,236 @@
name: e2e-test
description: E2E test focused piece (E2E analysis → E2E implementation → review → fix)
max_iterations: 20
initial_movement: plan_test
loop_monitors:
- cycle:
- ai_review
- ai_fix
threshold: 3
judge:
persona: supervisor
instruction_template: |
The ai_review ↔ ai_fix loop has repeated {cycle_count} times.
Review the reports from each cycle and determine whether this loop
is healthy (making progress) or unproductive (repeating the same issues).
**Reports to reference:**
- AI Review results: {report:04-ai-review.md}
**Judgment criteria:**
- Are new issues being found/fixed in each cycle?
- Are the same findings being repeated?
- Are fixes actually being applied?
rules:
- condition: Healthy (making progress)
next: ai_review
- condition: Unproductive (no improvement)
next: review_test
movements:
- name: plan_test
edit: false
persona: test-planner
policy: testing
knowledge:
- architecture
- backend
allowed_tools:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
rules:
- condition: Test plan complete
next: implement_test
- condition: User is asking a question (not an E2E test task)
next: COMPLETE
- condition: Requirements unclear, insufficient info
next: ABORT
appendix: |
Clarifications needed:
- {Question 1}
- {Question 2}
instruction: plan-e2e-test
output_contracts:
report:
- name: 00-test-plan.md
format: test-plan
- name: implement_test
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: Test implementation complete
next: ai_review
- condition: No implementation (report only)
next: ai_review
- condition: Cannot proceed, insufficient info
next: ai_review
- condition: User input required
next: implement_test
requires_user_input: true
interactive_only: true
instruction: implement-e2e-test
output_contracts:
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
policy:
- review
- ai-antipattern
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: No AI-specific issues
next: review_test
- condition: AI-specific issues found
next: ai_fix
instruction: ai-review
output_contracts:
report:
- name: 04-ai-review.md
format: ai-review
- name: ai_fix
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: AI issues fixed
next: ai_review
- condition: No fix needed (verified target files/spec)
next: ai_no_fix
- condition: Cannot proceed, insufficient info
next: ai_no_fix
instruction: ai-fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
policy: review
allowed_tools:
- Read
- Glob
- Grep
rules:
- condition: ai_review's findings are valid (fix required)
next: ai_fix
- condition: ai_fix's judgment is valid (no fix needed)
next: review_test
instruction: arbitrate
- name: review_test
edit: false
persona: qa-reviewer
policy:
- review
- qa
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
next: supervise
- condition: needs_fix
next: fix
instruction: review-test
output_contracts:
report:
- name: 05-qa-review.md
format: qa-review
- name: fix
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: Fix complete
next: review_test
- condition: Cannot proceed, insufficient info
next: plan_test
instruction: fix
- name: supervise
edit: false
persona: supervisor
policy: review
allowed_tools:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
rules:
- condition: All checks passed
next: COMPLETE
- condition: Requirements unmet, tests failing, build errors
next: plan_test
instruction: supervise
output_contracts:
report:
- Validation: 06-supervisor-validation.md
- Summary: summary.md

View File

@ -0,0 +1,51 @@
テスト計画に従ってE2Eテストを実装してください。
Piece Contextに示されたReport Directory内のファイルのみ参照してください。他のレポートディレクトリは検索/参照しないでください。
**やること:**
1. テスト計画のレポートを確認する
2. `e2e/specs/` など既存E2E配置に従ってテストを実装・更新する
3. E2Eテストを実行する最低: `npm run test:e2e:mock`、必要に応じて対象specの単体実行
4. 失敗時は原因を特定し、テストまたは対象コードを修正して再実行する
5. 既存の関連テストが壊れていないことを確認する
**実装の制約:**
- 既存のE2EフレームワークVitestを変更しない
- テストは1シナリオ1関心で記述し、期待結果を明確にする
- 外部依存があるケースは既存のfixture/helper/mock方針に合わせる
**Scope出力契約実装開始時に作成:**
```markdown
# 変更スコープ宣言
## タスク
{タスクの1行要約}
## 変更予定
| 種別 | ファイル |
|------|---------|
| 作成 | `e2e/specs/example.e2e.ts` |
## 推定規模
Small / Medium / Large
## 影響範囲
- {影響するモジュールや機能}
```
**Decisions出力契約実装完了時、決定がある場合のみ:**
```markdown
# 決定ログ
## 1. {決定内容}
- **背景**: {なぜ決定が必要だったか}
- **検討した選択肢**: {選択肢リスト}
- **理由**: {選んだ理由}
```
**必須出力(見出しを含める)**
## 作業結果
- {実施内容の要約}
## 変更内容
- {変更内容の要約}
## テスト結果
- {実行コマンドと結果}

View File

@ -0,0 +1,11 @@
対象コードを分析し、不足しているE2Eテストを洗い出してください。
**注意:** Previous Responseがある場合は差し戻しのため、
その内容を踏まえてテスト計画を見直してください。
**やること:**
1. 対象機能の仕様・実装・既存E2Eテスト`e2e/specs/**/*.e2e.ts`)を読み、振る舞いを理解する
2. 既存E2Eテストのカバー範囲正常系・異常系・回帰観点を整理する
3. 不足しているE2Eケースを洗い出すシナリオ、期待結果、失敗時の観測点
4. 実行コマンドを明記する(`npm run test:e2e:mock` / 必要時 `npx vitest run e2e/specs/<target>.e2e.ts`
5. 実装者向けに、失敗解析→修正→再実行の手順を具体化する

View File

@ -11,6 +11,7 @@ piece_categories:
- review-fix-minimal
- review-only
- unit-test
- e2e-test
🎨 フロントエンド: {}
⚙️ バックエンド: {}
🔧 エキスパート:

View File

@ -0,0 +1,236 @@
name: e2e-test
description: E2Eテスト追加に特化したピースE2E分析→E2E実装→レビュー→修正
max_iterations: 20
initial_movement: plan_test
loop_monitors:
- cycle:
- ai_review
- ai_fix
threshold: 3
judge:
persona: supervisor
instruction_template: |
ai_review と ai_fix のループが {cycle_count} 回繰り返されました。
各サイクルのレポートを確認し、このループが健全(進捗がある)か、
非生産的(同じ問題を繰り返している)かを判断してください。
**参照するレポート:**
- AIレビュー結果: {report:04-ai-review.md}
**判断基準:**
- 各サイクルで新しい問題が発見・修正されているか
- 同じ指摘が繰り返されていないか
- 修正が実際に反映されているか
rules:
- condition: 健全(進捗あり)
next: ai_review
- condition: 非生産的(改善なし)
next: review_test
movements:
- name: plan_test
edit: false
persona: test-planner
policy: testing
knowledge:
- architecture
- backend
allowed_tools:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
rules:
- condition: テスト計画が完了
next: implement_test
- condition: ユーザーが質問をしているE2Eテスト追加タスクではない
next: COMPLETE
- condition: 要件が不明確、情報不足
next: ABORT
appendix: |
確認事項:
- {質問1}
- {質問2}
instruction: plan-e2e-test
output_contracts:
report:
- name: 00-test-plan.md
format: test-plan
- name: implement_test
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: テスト実装完了
next: ai_review
- condition: 実装未着手(レポートのみ)
next: ai_review
- condition: 判断できない、情報不足
next: ai_review
- condition: ユーザー入力が必要
next: implement_test
requires_user_input: true
interactive_only: true
instruction: implement-e2e-test
output_contracts:
report:
- Scope: 02-coder-scope.md
- Decisions: 03-coder-decisions.md
- name: ai_review
edit: false
persona: ai-antipattern-reviewer
policy:
- review
- ai-antipattern
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: AI特有の問題なし
next: review_test
- condition: AI特有の問題あり
next: ai_fix
instruction: ai-review
output_contracts:
report:
- name: 04-ai-review.md
format: ai-review
- name: ai_fix
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: AI問題の修正完了
next: ai_review
- condition: 修正不要(指摘対象ファイル/仕様の確認済み)
next: ai_no_fix
- condition: 判断できない、情報不足
next: ai_no_fix
instruction: ai-fix
- name: ai_no_fix
edit: false
persona: architecture-reviewer
policy: review
allowed_tools:
- Read
- Glob
- Grep
rules:
- condition: ai_reviewの指摘が妥当修正すべき
next: ai_fix
- condition: ai_fixの判断が妥当修正不要
next: review_test
instruction: arbitrate
- name: review_test
edit: false
persona: qa-reviewer
policy:
- review
- qa
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: approved
next: supervise
- condition: needs_fix
next: fix
instruction: review-test
output_contracts:
report:
- name: 05-qa-review.md
format: qa-review
- name: fix
edit: true
persona: coder
policy:
- coding
- testing
session: refresh
knowledge:
- backend
- architecture
allowed_tools:
- Read
- Glob
- Grep
- Edit
- Write
- Bash
- WebSearch
- WebFetch
permission_mode: edit
rules:
- condition: 修正完了
next: review_test
- condition: 判断できない、情報不足
next: plan_test
instruction: fix
- name: supervise
edit: false
persona: supervisor
policy: review
allowed_tools:
- Read
- Glob
- Grep
- Bash
- WebSearch
- WebFetch
rules:
- condition: すべて問題なし
next: COMPLETE
- condition: 要求未達成、テスト失敗、ビルドエラー
next: plan_test
instruction: supervise
output_contracts:
report:
- Validation: 06-supervisor-validation.md
- Summary: summary.md

View File

@ -465,6 +465,7 @@ TAKTには複数のビルトインピースが同梱されています:
| `review-only` | 変更を加えない読み取り専用のコードレビューピース。 |
| `structural-reform` | プロジェクト全体の構造改革: 段階的なファイル分割を伴う反復的なコードベース再構成。 |
| `unit-test` | ユニットテスト重視ピース: テスト分析 → テスト実装 → レビュー → 修正。 |
| `e2e-test` | E2Eテスト重視ピース: E2E分析 → E2E実装 → レビュー → 修正VitestベースのE2Eフロー。 |
**ペルソナ別プロバイダー設定:** 設定ファイルの `persona_providers` で、特定のペルソナを異なるプロバイダーにルーティングできます(例: coder は Codex、レビュアーは Claude。ピースを複製する必要はありません。

View File

@ -217,7 +217,7 @@ describe('loadPiece (builtin fallback)', () => {
expect(piece).toBeNull();
});
it('should load builtin pieces like minimal, research', () => {
it('should load builtin pieces like minimal, research, e2e-test', () => {
const minimal = loadPiece('minimal', process.cwd());
expect(minimal).not.toBeNull();
expect(minimal!.name).toBe('minimal');
@ -225,6 +225,10 @@ describe('loadPiece (builtin fallback)', () => {
const research = loadPiece('research', process.cwd());
expect(research).not.toBeNull();
expect(research!.name).toBe('research');
const e2eTest = loadPiece('e2e-test', process.cwd());
expect(e2eTest).not.toBeNull();
expect(e2eTest!.name).toBe('e2e-test');
});
});
@ -246,6 +250,7 @@ describe('listPieces (builtin fallback)', () => {
const pieces = listPieces(testDir);
expect(pieces).toContain('default');
expect(pieces).toContain('minimal');
expect(pieces).toContain('e2e-test');
});
it('should return sorted list', () => {

View File

@ -14,10 +14,11 @@ import { join } from 'node:path';
import { tmpdir } from 'node:os';
// --- Mocks ---
const languageState = vi.hoisted(() => ({ value: 'en' as 'en' | 'ja' }));
vi.mock('../infra/config/global/globalConfig.js', () => ({
loadGlobalConfig: vi.fn().mockReturnValue({}),
getLanguage: vi.fn().mockReturnValue('en'),
getLanguage: vi.fn(() => languageState.value),
getDisabledBuiltins: vi.fn().mockReturnValue([]),
getBuiltinPiecesEnabled: vi.fn().mockReturnValue(true),
}));
@ -40,6 +41,7 @@ describe('Piece Loader IT: builtin piece loading', () => {
beforeEach(() => {
testDir = createTestDir();
languageState.value = 'en';
});
afterEach(() => {
@ -64,6 +66,39 @@ describe('Piece Loader IT: builtin piece loading', () => {
const config = loadPiece('non-existent-piece-xyz', testDir);
expect(config).toBeNull();
});
it('should include and load e2e-test as a builtin piece', () => {
expect(builtinNames).toContain('e2e-test');
const config = loadPiece('e2e-test', testDir);
expect(config).not.toBeNull();
const planMovement = config!.movements.find((movement) => movement.name === 'plan_test');
const implementMovement = config!.movements.find((movement) => movement.name === 'implement_test');
expect(planMovement).toBeDefined();
expect(implementMovement).toBeDefined();
expect(planMovement!.instructionTemplate).toContain('missing E2E tests');
expect(implementMovement!.instructionTemplate).toContain('npm run test:e2e:mock');
});
it('should load e2e-test as a builtin piece in ja locale', () => {
languageState.value = 'ja';
const jaBuiltinNames = listBuiltinPieceNames({ includeDisabled: true });
expect(jaBuiltinNames).toContain('e2e-test');
const config = loadPiece('e2e-test', testDir);
expect(config).not.toBeNull();
const planMovement = config!.movements.find((movement) => movement.name === 'plan_test');
const implementMovement = config!.movements.find((movement) => movement.name === 'implement_test');
expect(planMovement).toBeDefined();
expect(implementMovement).toBeDefined();
expect(planMovement!.instructionTemplate).toContain('E2Eテスト');
expect(implementMovement!.instructionTemplate).toContain('npm run test:e2e:mock');
});
});
describe('Piece Loader IT: project-local piece override', () => {

View File

@ -9,8 +9,12 @@ import { tmpdir } from 'node:os';
import { randomUUID } from 'node:crypto';
import type { PieceWithSource } from '../infra/config/index.js';
const languageState = vi.hoisted(() => ({
value: 'en' as 'en' | 'ja',
}));
const pathsState = vi.hoisted(() => ({
resourcesDir: '',
resourcesRoot: '',
userCategoriesPath: '',
}));
@ -18,7 +22,7 @@ vi.mock('../infra/config/global/globalConfig.js', async (importOriginal) => {
const original = await importOriginal() as Record<string, unknown>;
return {
...original,
getLanguage: () => 'en',
getLanguage: () => languageState.value,
getBuiltinPiecesEnabled: () => true,
getDisabledBuiltins: () => [],
};
@ -28,7 +32,7 @@ vi.mock('../infra/resources/index.js', async (importOriginal) => {
const original = await importOriginal() as Record<string, unknown>;
return {
...original,
getLanguageResourcesDir: () => pathsState.resourcesDir,
getLanguageResourcesDir: (lang: string) => join(pathsState.resourcesRoot, lang),
};
});
@ -74,10 +78,12 @@ describe('piece category config loading', () => {
beforeEach(() => {
testDir = join(tmpdir(), `takt-cat-config-${randomUUID()}`);
resourcesDir = join(testDir, 'resources');
resourcesDir = join(testDir, 'resources', 'en');
mkdirSync(resourcesDir, { recursive: true });
pathsState.resourcesDir = resourcesDir;
mkdirSync(join(testDir, 'resources', 'ja'), { recursive: true });
pathsState.resourcesRoot = join(testDir, 'resources');
languageState.value = 'en';
pathsState.userCategoriesPath = join(testDir, 'user-piece-categories.yaml');
});
@ -142,6 +148,7 @@ piece_categories:
Review:
pieces:
- review-only
- e2e-test
show_others_category: true
others_category_name: Others
`);
@ -168,7 +175,7 @@ others_category_name: Unclassified
{ name: 'Child', pieces: ['nested'], children: [] },
],
},
{ name: 'Review', pieces: ['review-only'], children: [] },
{ name: 'Review', pieces: ['review-only', 'e2e-test'], children: [] },
{ name: 'My Team', pieces: ['team-flow'], children: [] },
]);
expect(config!.builtinPieceCategories).toEqual([
@ -179,7 +186,7 @@ others_category_name: Unclassified
{ name: 'Child', pieces: ['nested'], children: [] },
],
},
{ name: 'Review', pieces: ['review-only'], children: [] },
{ name: 'Review', pieces: ['review-only', 'e2e-test'], children: [] },
]);
expect(config!.userPieceCategories).toEqual([
{ name: 'Main', pieces: ['custom'], children: [] },
@ -189,6 +196,24 @@ others_category_name: Unclassified
expect(config!.othersCategoryName).toBe('Unclassified');
});
it('should load ja builtin categories and include e2e-test under レビュー', () => {
languageState.value = 'ja';
writeYaml(join(testDir, 'resources', 'ja', 'piece-categories.yaml'), `
piece_categories:
:
pieces:
- review-only
- e2e-test
`);
const config = getPieceCategories();
expect(config).not.toBeNull();
expect(config!.pieceCategories).toEqual([
{ name: 'レビュー', pieces: ['review-only', 'e2e-test'], children: [] },
]);
});
it('should override others settings without replacing categories when user overlay has no piece_categories', () => {
writeYaml(join(resourcesDir, 'piece-categories.yaml'), `
piece_categories: