diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ff812..856b739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,37 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [0.11.0] - 2026-02-10 + +### Added + +- **`e2e-test` ビルトインピース**: E2Eテスト特化のピースを新規追加 — E2E分析 → E2E実装 → レビュー → 修正のフロー(VitestベースのE2Eテスト向け) +- **`error` ステータス**: プロバイダーエラーを `blocked` から分離し、エラー状態を明確に区別可能に。Codex にリトライ機構を追加 +- **タスク YAML 一元管理**: タスクファイルの管理を `tasks.yaml` に統合。`TaskRecordSchema` による構造化されたタスクライフサイクル管理(pending/running/completed/failed) +- **タスク指示書ドキュメント**: タスク指示書の構造と目的を明文化 (#174) +- **レビューポリシー**: 共通レビューポリシーファセット(`builtins/{lang}/policies/review.md`)を追加 +- **SIGINT グレースフルシャットダウンの E2E テスト**: 並列実行中の Ctrl+C 動作を検証する E2E テストを追加 + +### Changed + +- **ビルトインピース簡素化**: 全ビルトインピースからトップレベルの `policies`/`personas`/`knowledge`/`instructions`/`report_formats` 宣言を削除し、名前ベースの暗黙的解決に移行。ピース YAML がよりシンプルに +- **ピースカテゴリ仕様更新**: カテゴリの設定・表示ロジックを改善。グローバル設定でのカテゴリ管理を強化 (#184) +- **`takt list` の優先度・参照改善**: ブランチ解決のパフォーマンス最適化。ベースコミットキャッシュの導入 (#186, #195, #196) +- **Ctrl+C シグナルハンドリング改善**: 並列実行中の SIGINT 処理を安定化 +- **ループ防止ポリシー強化**: エージェントの無限ループを防止するためのポリシーを強化 + +### Fixed + +- オリジナル指示の差分処理が正しく動作しない問題を修正 (#181) +- タスク指示書のゴールが不適切にスコープ拡張される問題を修正 — ゴールを常に実装・実行に固定 + +### Internal + +- タスク管理コードの大規模リファクタリング: `parser.ts` を廃止し `store.ts`/`mapper.ts`/`schema.ts`/`naming.ts` に分離。`branchGitResolver.ts`/`branchBaseCandidateResolver.ts`/`branchBaseRefCache.ts`/`branchEntryPointResolver.ts` でブランチ解決を細分化 +- テストの大幅な拡充・リファクタリング: aggregate-evaluator, blocked-handler, branchGitResolver-performance, branchList-regression, buildListItems-performance, error-utils, escape, facet-resolution, getFilesChanged, global-pieceCategories, instruction-context, instruction-helpers, judgment-strategies, listTasksInteractivePendingLabel, loop-detector, naming, reportDir, resetCategories, rule-evaluator, rule-utils, slug, state-manager, switchPiece, task-schema, text, transitions, watchTasks 等を新規追加 +- Codex クライアントのリファクタリング +- ピースパーサーのファセット解決ロジック改善 + ## [0.10.0] - 2026-02-09 ### Added diff --git a/builtins/en/piece-categories.yaml b/builtins/en/piece-categories.yaml index ad5a5bc..69719ce 100644 --- a/builtins/en/piece-categories.yaml +++ b/builtins/en/piece-categories.yaml @@ -10,6 +10,8 @@ piece_categories: pieces: - review-fix-minimal - review-only + 🧪 Testing: + pieces: - unit-test - e2e-test 🎨 Frontend: {} diff --git a/builtins/ja/piece-categories.yaml b/builtins/ja/piece-categories.yaml index 28721f6..41ca3e2 100644 --- a/builtins/ja/piece-categories.yaml +++ b/builtins/ja/piece-categories.yaml @@ -10,6 +10,8 @@ piece_categories: pieces: - review-fix-minimal - review-only + 🧪 テスト: + pieces: - unit-test - e2e-test 🎨 フロントエンド: {} diff --git a/e2e/specs/add-and-run.e2e.ts b/e2e/specs/add-and-run.e2e.ts index 08dd0c4..3b7c47b 100644 --- a/e2e/specs/add-and-run.e2e.ts +++ b/e2e/specs/add-and-run.e2e.ts @@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'; import { join, resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { parse as parseYaml } from 'yaml'; import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env'; import { createTestRepo, type TestRepo } from '../helpers/test-repo'; import { runTakt } from '../helpers/takt-runner'; @@ -35,15 +36,22 @@ describe('E2E: Add task and run (takt add → takt run)', () => { it('should add a task file and execute it with takt run', () => { const piecePath = resolve(__dirname, '../fixtures/pieces/simple.yaml'); - // Step 1: Create a task file in .takt/tasks/ (simulates `takt add`) - const tasksDir = join(testRepo.path, '.takt', 'tasks'); - mkdirSync(tasksDir, { recursive: true }); + // Step 1: Create a pending task in .takt/tasks.yaml (simulates `takt add`) + const taktDir = join(testRepo.path, '.takt'); + mkdirSync(taktDir, { recursive: true }); + const tasksFile = join(taktDir, 'tasks.yaml'); const taskYaml = [ - 'task: "Add a single line \\"E2E test passed\\" to README.md"', - `piece: "${piecePath}"`, + 'tasks:', + ' - name: e2e-test-task', + ' status: pending', + ' content: "Add a single line \\"E2E test passed\\" to README.md"', + ` piece: "${piecePath}"`, + ` created_at: "${new Date().toISOString()}"`, + ' started_at: null', + ' completed_at: null', ].join('\n'); - writeFileSync(join(tasksDir, 'e2e-test-task.yaml'), taskYaml, 'utf-8'); + writeFileSync(tasksFile, taskYaml, 'utf-8'); // Step 2: Run `takt run` to execute the pending task const result = runTakt({ @@ -66,7 +74,10 @@ describe('E2E: Add task and run (takt add → takt run)', () => { const readme = readFileSync(readmePath, 'utf-8'); expect(readme).toContain('E2E test passed'); - // Verify task file was moved out of tasks/ (completed or failed) - expect(existsSync(join(tasksDir, 'e2e-test-task.yaml'))).toBe(false); + // Verify task status became completed + const tasksRaw = readFileSync(tasksFile, 'utf-8'); + const parsed = parseYaml(tasksRaw) as { tasks?: Array<{ name?: string; status?: string }> }; + const executed = parsed.tasks?.find((task) => task.name === 'e2e-test-task'); + expect(executed?.status).toBe('completed'); }, 240_000); }); diff --git a/e2e/specs/add.e2e.ts b/e2e/specs/add.e2e.ts index 6cb0f92..a4bb031 100644 --- a/e2e/specs/add.e2e.ts +++ b/e2e/specs/add.e2e.ts @@ -1,8 +1,9 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { execFileSync } from 'node:child_process'; -import { readFileSync, readdirSync, writeFileSync } from 'node:fs'; +import { readFileSync, writeFileSync } from 'node:fs'; import { join, dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { parse as parseYaml } from 'yaml'; import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env'; import { createTestRepo, type TestRepo } from '../helpers/test-repo'; import { runTakt } from '../helpers/takt-runner'; @@ -84,12 +85,10 @@ describe('E2E: Add task from GitHub issue (takt add)', () => { expect(result.exitCode).toBe(0); - const tasksDir = join(testRepo.path, '.takt', 'tasks'); - const files = readdirSync(tasksDir).filter((file) => file.endsWith('.yaml')); - expect(files.length).toBe(1); - - const taskFile = join(tasksDir, files[0] ?? ''); - const content = readFileSync(taskFile, 'utf-8'); - expect(content).toContain('issue:'); + const tasksFile = join(testRepo.path, '.takt', 'tasks.yaml'); + const content = readFileSync(tasksFile, 'utf-8'); + const parsed = parseYaml(content) as { tasks?: Array<{ issue?: number }> }; + expect(parsed.tasks?.length).toBe(1); + expect(parsed.tasks?.[0]?.issue).toBe(Number(issueNumber)); }, 240_000); }); diff --git a/e2e/specs/watch.e2e.ts b/e2e/specs/watch.e2e.ts index 3cb2162..29e0c18 100644 --- a/e2e/specs/watch.e2e.ts +++ b/e2e/specs/watch.e2e.ts @@ -1,8 +1,9 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { spawn } from 'node:child_process'; -import { mkdirSync, writeFileSync, existsSync } from 'node:fs'; +import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { join, resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { parse as parseYaml } from 'yaml'; import { createIsolatedEnv, type IsolatedEnv } from '../helpers/isolated-env'; import { createTestRepo, type TestRepo } from '../helpers/test-repo'; @@ -51,16 +52,21 @@ describe('E2E: Watch tasks (takt watch)', () => { stdout += chunk.toString(); }); - const tasksDir = join(testRepo.path, '.takt', 'tasks'); - mkdirSync(tasksDir, { recursive: true }); - + const taktDir = join(testRepo.path, '.takt'); + mkdirSync(taktDir, { recursive: true }); + const tasksFile = join(taktDir, 'tasks.yaml'); + const createdAt = new Date().toISOString(); const taskYaml = [ - 'task: "Add a single line \\\"watch test\\\" to README.md"', - `piece: "${piecePath}"`, + 'tasks:', + ' - name: watch-task', + ' status: pending', + ' content: "Add a single line \\"watch test\\" to README.md"', + ` piece: "${piecePath}"`, + ` created_at: "${createdAt}"`, + ' started_at: null', + ' completed_at: null', ].join('\n'); - - const taskPath = join(tasksDir, 'watch-task.yaml'); - writeFileSync(taskPath, taskYaml, 'utf-8'); + writeFileSync(tasksFile, taskYaml, 'utf-8'); const completed = await new Promise((resolvePromise) => { const timeout = setTimeout(() => resolvePromise(false), 240_000); @@ -87,6 +93,9 @@ describe('E2E: Watch tasks (takt watch)', () => { }); expect(completed).toBe(true); - expect(existsSync(taskPath)).toBe(false); + const tasksRaw = readFileSync(tasksFile, 'utf-8'); + const parsed = parseYaml(tasksRaw) as { tasks?: Array<{ name?: string; status?: string }> }; + const watchTask = parsed.tasks?.find((task) => task.name === 'watch-task'); + expect(watchTask?.status).toBe('completed'); }, 240_000); }); diff --git a/package-lock.json b/package-lock.json index 4e2d30a..c071983 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "takt", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "takt", - "version": "0.10.0", + "version": "0.11.0", "license": "MIT", "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.2.37", diff --git a/package.json b/package.json index 5c03c7b..f414b82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "takt", - "version": "0.10.0", + "version": "0.11.0", "description": "TAKT: Task Agent Koordination Tool - AI Agent Piece Orchestration", "main": "dist/index.js", "types": "dist/index.d.ts",