タスクを監視する /watch を追加
This commit is contained in:
parent
7270b29044
commit
7323d6d288
30
CLAUDE.md
30
CLAUDE.md
@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
TAKT (Task Agent Koordination Tool) is a multi-agent orchestration system for Claude Code. It enables YAML-based workflow definitions that coordinate multiple AI agents through state machine transitions.
|
||||
|
||||
## Commands
|
||||
## Development Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
@ -17,13 +17,26 @@ TAKT (Task Agent Koordination Tool) is a multi-agent orchestration system for Cl
|
||||
| `npx vitest run src/__tests__/client.test.ts` | Run single test file |
|
||||
| `npx vitest run -t "pattern"` | Run tests matching pattern |
|
||||
|
||||
## CLI Slash Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `takt /run-tasks` | Execute all pending tasks from `.takt/tasks/` once |
|
||||
| `takt /watch` | Watch `.takt/tasks/` and auto-execute tasks (resident process) |
|
||||
| `takt /add-task` | Add a new task interactively (YAML format) |
|
||||
| `takt /switch` | Switch workflow interactively |
|
||||
| `takt /clear` | Clear agent conversation sessions (reset state) |
|
||||
| `takt /refresh-builtin` | Update builtin resources from `resources/` to `~/.takt/` |
|
||||
| `takt /help` | Show help message |
|
||||
| `takt /config` | Display current configuration |
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Flow
|
||||
|
||||
```
|
||||
CLI (cli.ts)
|
||||
→ Slash commands (/run-tasks, /switch, /clear, /help, /config)
|
||||
→ Slash commands (/run-tasks, /watch, /add-task, /switch, /clear, /refresh-builtin, /help, /config)
|
||||
→ or executeTask()
|
||||
→ WorkflowEngine (workflow/engine.ts)
|
||||
→ runAgent() (agents/runner.ts)
|
||||
@ -63,6 +76,11 @@ CLI (cli.ts)
|
||||
- `agentLoader.ts` - Agent prompt file loading
|
||||
- `paths.ts` - Directory structure (`.takt/`, `~/.takt/`), session management
|
||||
|
||||
**Task Management** (`src/task/`)
|
||||
- `runner.ts` - TaskRunner class for managing task files (`.takt/tasks/`)
|
||||
- `watcher.ts` - TaskWatcher class for polling and auto-executing tasks (used by `/watch`)
|
||||
- `index.ts` - Task operations (getNextTask, completeTask, addTask)
|
||||
|
||||
### Data Flow
|
||||
|
||||
1. User provides task or slash command → CLI
|
||||
@ -166,3 +184,11 @@ model: opus # Default model for all steps (unless overridden)
|
||||
- `rejected` - Review failed, needs major rework
|
||||
- `improve` - Needs improvement (security concerns, quality issues)
|
||||
- `always` - Unconditional transition
|
||||
|
||||
## Testing Notes
|
||||
|
||||
- Vitest for testing framework
|
||||
- Tests use file system fixtures in `__tests__/` subdirectories
|
||||
- Mock workflows and agent configs for integration tests
|
||||
- Test single files: `npx vitest run src/__tests__/filename.test.ts`
|
||||
- Pattern matching: `npx vitest run -t "test pattern"`
|
||||
|
||||
176
README.md
176
README.md
@ -26,22 +26,32 @@ npm install -g takt
|
||||
# Run a task (will prompt for workflow selection)
|
||||
takt "Add a login feature"
|
||||
|
||||
# Switch workflow
|
||||
takt /switch
|
||||
# Add a task to the queue
|
||||
takt /add-task "Fix the login bug"
|
||||
|
||||
# Run all pending tasks
|
||||
takt /run-tasks
|
||||
|
||||
# Watch for tasks and auto-execute
|
||||
takt /watch
|
||||
|
||||
# Switch workflow
|
||||
takt /switch
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `takt "task"` | Execute task with workflow selection |
|
||||
| `takt "task"` | Execute task with current workflow (continues session) |
|
||||
| `takt -r "task"` | Execute task, resuming previous session |
|
||||
| `takt /run-tasks` | Run all pending tasks |
|
||||
| `takt /run-tasks` | Run all pending tasks from `.takt/tasks/` |
|
||||
| `takt /watch` | Watch `.takt/tasks/` and auto-execute tasks (stays resident) |
|
||||
| `takt /add-task` | Add a new task interactively (YAML format) |
|
||||
| `takt /switch` | Switch workflow interactively |
|
||||
| `takt /clear` | Clear agent conversation sessions |
|
||||
| `takt /refresh-builtin` | Update builtin agents/workflows to latest version |
|
||||
| `takt /config` | Display current configuration |
|
||||
| `takt /help` | Show help |
|
||||
|
||||
## Workflows
|
||||
@ -88,11 +98,28 @@ steps:
|
||||
next_step: implement
|
||||
```
|
||||
|
||||
## Built-in Workflows
|
||||
|
||||
TAKT ships with several built-in workflows:
|
||||
|
||||
| Workflow | Description |
|
||||
|----------|-------------|
|
||||
| `default` | Full development workflow: plan → implement → architect review → AI review → security review → supervisor approval. Includes fix loops for each review stage. |
|
||||
| `simple` | Simplified version of default: plan → implement → architect review → AI review → supervisor. No intermediate fix steps. |
|
||||
| `research` | Research workflow: planner → digger → supervisor. Autonomously researches topics without asking questions. |
|
||||
| `expert-review` | Comprehensive review with domain experts: CQRS+ES, Frontend, AI, Security, QA reviews with fix loops. |
|
||||
| `magi` | Deliberation system inspired by Evangelion. Three AI personas (MELCHIOR, BALTHASAR, CASPER) analyze and vote. |
|
||||
|
||||
Switch between workflows with `takt /switch`.
|
||||
|
||||
## Built-in Agents
|
||||
|
||||
- **coder** - Implements features and fixes bugs
|
||||
- **architect** - Reviews code and provides feedback
|
||||
- **supervisor** - Final verification and approval
|
||||
- **planner** - Task analysis and implementation planning
|
||||
- **ai-reviewer** - AI-generated code quality review
|
||||
- **security** - Security vulnerability assessment
|
||||
|
||||
## Custom Agents
|
||||
|
||||
@ -149,6 +176,14 @@ Available Codex models:
|
||||
├── config.yaml # Global config (provider, model, workflows, etc.)
|
||||
├── workflows/ # Workflow definitions
|
||||
└── agents/ # Agent prompt files
|
||||
|
||||
.takt/ # Project-level config
|
||||
├── agents.yaml # Custom agent definitions
|
||||
├── tasks/ # Pending task files (.yaml, .md)
|
||||
├── completed/ # Completed tasks with reports
|
||||
├── worktrees/ # Git worktrees for isolated task execution
|
||||
├── reports/ # Execution reports (auto-generated)
|
||||
└── logs/ # Session logs
|
||||
```
|
||||
|
||||
### Global Configuration
|
||||
@ -189,22 +224,6 @@ takt -r "The bug occurs when the password contains special characters"
|
||||
|
||||
The `-r` flag preserves the agent's conversation history, allowing for natural back-and-forth interaction.
|
||||
|
||||
### Playing with MAGI System
|
||||
|
||||
MAGI is a deliberation system inspired by Evangelion. Three AI personas analyze your question from different perspectives and vote:
|
||||
|
||||
```bash
|
||||
# Select 'magi' workflow when prompted
|
||||
takt "Should we migrate from REST to GraphQL?"
|
||||
```
|
||||
|
||||
The three MAGI personas:
|
||||
- **MELCHIOR-1** (Scientist): Logical, data-driven analysis
|
||||
- **BALTHASAR-2** (Nurturer): Team and human-centered perspective
|
||||
- **CASPER-3** (Pragmatist): Practical, real-world considerations
|
||||
|
||||
Each persona votes: APPROVE, REJECT, or CONDITIONAL. The final decision is made by majority vote.
|
||||
|
||||
### Adding Custom Workflows
|
||||
|
||||
Create your own workflow by adding YAML files to `~/.takt/workflows/`:
|
||||
@ -268,27 +287,33 @@ You are a code reviewer focused on security.
|
||||
- [REVIEWER:REJECT] if issues found (list them)
|
||||
```
|
||||
|
||||
### Using `/run-tasks` for Batch Processing
|
||||
### Task Management
|
||||
|
||||
The `/run-tasks` command executes all task files in `.takt/tasks/` directory:
|
||||
TAKT supports batch task processing through task files in `.takt/tasks/`. Both `.yaml`/`.yml` and `.md` file formats are supported.
|
||||
|
||||
#### Adding Tasks with `/add-task`
|
||||
|
||||
```bash
|
||||
# Create task files as you think of them
|
||||
echo "Add unit tests for the auth module" > .takt/tasks/001-add-tests.md
|
||||
echo "Refactor the database layer" > .takt/tasks/002-refactor-db.md
|
||||
echo "Update API documentation" > .takt/tasks/003-update-docs.md
|
||||
# Quick add (no worktree)
|
||||
takt /add-task "Add authentication feature"
|
||||
|
||||
# Run all pending tasks
|
||||
takt /run-tasks
|
||||
# Interactive mode (prompts for worktree, branch, workflow options)
|
||||
takt /add-task
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Tasks are executed in alphabetical order (use prefixes like `001-`, `002-` for ordering)
|
||||
- Each task file should contain a description of what needs to be done
|
||||
- Completed tasks are moved to `.takt/completed/` with execution reports
|
||||
- New tasks added during execution will be picked up dynamically
|
||||
#### Task File Formats
|
||||
|
||||
**Task file format:**
|
||||
**YAML format** (recommended, supports worktree/branch/workflow options):
|
||||
|
||||
```yaml
|
||||
# .takt/tasks/add-auth.yaml
|
||||
task: "Add authentication feature"
|
||||
worktree: true # Run in isolated git worktree
|
||||
branch: "feat/add-auth" # Branch name (auto-generated if omitted)
|
||||
workflow: "default" # Workflow override (uses current if omitted)
|
||||
```
|
||||
|
||||
**Markdown format** (simple, backward compatible):
|
||||
|
||||
```markdown
|
||||
# .takt/tasks/add-login-feature.md
|
||||
@ -301,10 +326,35 @@ Requirements:
|
||||
- Error handling for failed attempts
|
||||
```
|
||||
|
||||
This is perfect for:
|
||||
- Brainstorming sessions where you capture ideas as files
|
||||
- Breaking down large features into smaller tasks
|
||||
- Automated pipelines that generate task files
|
||||
#### Git Worktree Isolation
|
||||
|
||||
YAML task files can specify `worktree` to run each task in an isolated git worktree, keeping the main working directory clean:
|
||||
|
||||
- `worktree: true` - Auto-create at `.takt/worktrees/{timestamp}-{task-slug}/`
|
||||
- `worktree: "/path/to/dir"` - Create at specified path
|
||||
- `branch: "feat/xxx"` - Use specified branch (auto-generated as `takt/{timestamp}-{slug}` if omitted)
|
||||
- Omit `worktree` - Run in current working directory (default)
|
||||
|
||||
#### Running Tasks with `/run-tasks`
|
||||
|
||||
```bash
|
||||
takt /run-tasks
|
||||
```
|
||||
|
||||
- Tasks are executed in alphabetical order (use prefixes like `001-`, `002-` for ordering)
|
||||
- Completed tasks are moved to `.takt/completed/` with execution reports
|
||||
- New tasks added during execution will be picked up dynamically
|
||||
|
||||
#### Watching Tasks with `/watch`
|
||||
|
||||
```bash
|
||||
takt /watch
|
||||
```
|
||||
|
||||
Watch mode polls `.takt/tasks/` for new task files and auto-executes them as they appear. The process stays resident until `Ctrl+C`. This is useful for:
|
||||
- CI/CD pipelines that generate task files
|
||||
- Automated workflows where tasks are added by external processes
|
||||
- Long-running development sessions where tasks are queued over time
|
||||
|
||||
### Workflow Variables
|
||||
|
||||
@ -313,11 +363,56 @@ Available variables in `instruction_template`:
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `{task}` | Original user request |
|
||||
| `{iteration}` | Current iteration number |
|
||||
| `{max_iterations}` | Maximum iterations |
|
||||
| `{iteration}` | Workflow-wide turn count (total steps executed) |
|
||||
| `{max_iterations}` | Maximum iterations allowed |
|
||||
| `{step_iteration}` | Per-step iteration count (how many times THIS step has run) |
|
||||
| `{previous_response}` | Previous step's output (requires `pass_previous_response: true`) |
|
||||
| `{user_inputs}` | Additional user inputs during workflow |
|
||||
| `{git_diff}` | Current git diff (uncommitted changes) |
|
||||
| `{report_dir}` | Report directory name (e.g., `20250126-143052-task-summary`) |
|
||||
|
||||
### Designing Workflows
|
||||
|
||||
Each workflow step requires three key elements:
|
||||
|
||||
**1. Agent** - A Markdown file containing the system prompt:
|
||||
|
||||
```yaml
|
||||
agent: ~/.takt/agents/default/coder.md # Path to agent prompt file
|
||||
agent_name: coder # Display name (optional)
|
||||
```
|
||||
|
||||
**2. Status Rules** - Define how the agent signals completion. Agents output status markers like `[CODER:DONE]` or `[ARCHITECT:REJECT]` that TAKT detects to drive transitions:
|
||||
|
||||
```yaml
|
||||
status_rules_prompt: |
|
||||
Your final output MUST include a status tag:
|
||||
- `[CODER:DONE]` if implementation is complete
|
||||
- `[CODER:BLOCKED]` if you cannot proceed
|
||||
```
|
||||
|
||||
**3. Transitions** - Route to the next step based on status:
|
||||
|
||||
```yaml
|
||||
transitions:
|
||||
- condition: done # Maps to status tag DONE
|
||||
next_step: review # Go to review step
|
||||
- condition: blocked # Maps to status tag BLOCKED
|
||||
next_step: ABORT # End workflow with failure
|
||||
```
|
||||
|
||||
Available transition conditions: `done`, `blocked`, `approved`, `rejected`, `improve`, `always`.
|
||||
Special next_step values: `COMPLETE` (success), `ABORT` (failure).
|
||||
|
||||
**Step options:**
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `pass_previous_response` | `true` | Pass previous step's output to `{previous_response}` |
|
||||
| `on_no_status` | - | Behavior when no status is detected: `complete`, `continue`, `stay` |
|
||||
| `allowed_tools` | - | List of tools the agent can use (Read, Glob, Grep, Edit, Write, Bash, etc.) |
|
||||
| `provider` | - | Override provider for this step (`claude` or `codex`) |
|
||||
| `model` | - | Override model for this step |
|
||||
|
||||
## API Usage
|
||||
|
||||
@ -375,6 +470,7 @@ This ensures the project works correctly in a clean Node.js 20 environment.
|
||||
- [Agent Guide](./docs/agents.md) - Configure custom agents
|
||||
- [Changelog](./CHANGELOG.md) - Version history
|
||||
- [Security Policy](./SECURITY.md) - Vulnerability reporting
|
||||
- [Blog: TAKT - AI Agent Orchestration](https://zenn.dev/nrs/articles/c6842288a526d7) - Design philosophy and practical usage guide (Japanese)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
# TAKT
|
||||
|
||||
**T**ask **A**gent **K**oordination **T**ool - Claude Code向けのマルチエージェントオーケストレーションシステム(Codex対応予定)
|
||||
**T**ask **A**gent **K**oordination **T**ool - Claude CodeとOpenAI Codex向けのマルチエージェントオーケストレーションシステム
|
||||
|
||||
> **Note**: このプロジェクトは個人のペースで開発されています。詳細は[免責事項](#免責事項)をご覧ください。
|
||||
|
||||
## 必要条件
|
||||
|
||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) がインストール・設定済みであること
|
||||
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) または Codex がインストール・設定済みであること
|
||||
|
||||
TAKTはClaude CodeとCodexの両方をプロバイダーとしてサポートしています。セットアップ時にプロバイダーを選択できます。
|
||||
|
||||
## インストール
|
||||
|
||||
@ -20,22 +22,32 @@ npm install -g takt
|
||||
# タスクを実行(ワークフロー選択プロンプトが表示されます)
|
||||
takt "ログイン機能を追加して"
|
||||
|
||||
# ワークフローを切り替え
|
||||
takt /switch
|
||||
# タスクをキューに追加
|
||||
takt /add-task "ログインのバグを修正"
|
||||
|
||||
# 保留中のタスクをすべて実行
|
||||
takt /run-tasks
|
||||
|
||||
# タスクを監視して自動実行
|
||||
takt /watch
|
||||
|
||||
# ワークフローを切り替え
|
||||
takt /switch
|
||||
```
|
||||
|
||||
## コマンド一覧
|
||||
|
||||
| コマンド | 説明 |
|
||||
|---------|------|
|
||||
| `takt "タスク"` | ワークフロー選択後にタスクを実行 |
|
||||
| `takt "タスク"` | 現在のワークフローでタスクを実行(セッション継続) |
|
||||
| `takt -r "タスク"` | 前回のセッションを再開してタスクを実行 |
|
||||
| `takt /run-tasks` | 保留中のタスクをすべて実行 |
|
||||
| `takt /run-tasks` | `.takt/tasks/` の保留中タスクをすべて実行 |
|
||||
| `takt /watch` | `.takt/tasks/` を監視してタスクを自動実行(常駐プロセス) |
|
||||
| `takt /add-task` | 新しいタスクを対話的に追加(YAML形式) |
|
||||
| `takt /switch` | ワークフローを対話的に切り替え |
|
||||
| `takt /clear` | エージェントの会話セッションをクリア |
|
||||
| `takt /refresh-builtin` | ビルトインのエージェント/ワークフローを最新版に更新 |
|
||||
| `takt /config` | 現在の設定を表示 |
|
||||
| `takt /help` | ヘルプを表示 |
|
||||
|
||||
## 実践的な使い方ガイド
|
||||
@ -54,43 +66,33 @@ takt -r "パスワードに特殊文字が含まれているとバグが発生
|
||||
|
||||
`-r`フラグはエージェントの会話履歴を保持し、自然なやり取りを可能にします。
|
||||
|
||||
### MAGIシステムで遊ぶ
|
||||
### タスク管理
|
||||
|
||||
MAGIはエヴァンゲリオンにインスパイアされた審議システムです。3つのAIペルソナがあなたの質問を異なる視点から分析し、投票します:
|
||||
TAKTは`.takt/tasks/`内のタスクファイルによるバッチ処理をサポートしています。`.yaml`/`.yml`と`.md`の両方のファイル形式に対応しています。
|
||||
|
||||
#### `/add-task` でタスクを追加
|
||||
|
||||
```bash
|
||||
# プロンプトが表示されたら'magi'ワークフローを選択
|
||||
takt "RESTからGraphQLに移行すべきか?"
|
||||
# クイック追加(worktreeなし)
|
||||
takt /add-task "認証機能を追加"
|
||||
|
||||
# 対話モード(worktree、ブランチ、ワークフローオプションを指定可能)
|
||||
takt /add-task
|
||||
```
|
||||
|
||||
3つのMAGIペルソナ:
|
||||
- **MELCHIOR-1**(科学者):論理的、データ駆動の分析
|
||||
- **BALTHASAR-2**(母性):チームと人間中心の視点
|
||||
- **CASPER-3**(現実主義者):実用的で現実的な考慮
|
||||
#### タスクファイルの形式
|
||||
|
||||
各ペルソナは APPROVE、REJECT、または CONDITIONAL で投票します。最終決定は多数決で行われます。
|
||||
**YAML形式**(推奨、worktree/branch/workflowオプション対応):
|
||||
|
||||
### `/run-tasks` でバッチ処理
|
||||
|
||||
`/run-tasks`コマンドは`.takt/tasks/`ディレクトリ内のすべてのタスクファイルを実行します:
|
||||
|
||||
```bash
|
||||
# 思いつくままにタスクファイルを作成
|
||||
echo "認証モジュールのユニットテストを追加" > .takt/tasks/001-add-tests.md
|
||||
echo "データベースレイヤーをリファクタリング" > .takt/tasks/002-refactor-db.md
|
||||
echo "APIドキュメントを更新" > .takt/tasks/003-update-docs.md
|
||||
|
||||
# すべての保留タスクを実行
|
||||
takt /run-tasks
|
||||
```yaml
|
||||
# .takt/tasks/add-auth.yaml
|
||||
task: "認証機能を追加する"
|
||||
worktree: true # 隔離されたgit worktreeで実行
|
||||
branch: "feat/add-auth" # ブランチ名(省略時は自動生成)
|
||||
workflow: "default" # ワークフロー指定(省略時は現在のもの)
|
||||
```
|
||||
|
||||
**動作の仕組み:**
|
||||
- タスクはアルファベット順に実行されます(`001-`、`002-`のようなプレフィックスで順序を制御)
|
||||
- 各タスクファイルには実行すべき内容の説明を含めます
|
||||
- 完了したタスクは実行レポートとともに`.takt/completed/`に移動されます
|
||||
- 実行中に追加された新しいタスクも動的に取得されます
|
||||
|
||||
**タスクファイルの形式:**
|
||||
**Markdown形式**(シンプル、後方互換):
|
||||
|
||||
```markdown
|
||||
# .takt/tasks/add-login-feature.md
|
||||
@ -103,10 +105,35 @@ takt /run-tasks
|
||||
- 失敗時のエラーハンドリング
|
||||
```
|
||||
|
||||
これは以下のような場合に最適です:
|
||||
- アイデアをファイルとしてキャプチャするブレインストーミングセッション
|
||||
- 大きな機能を小さなタスクに分割する場合
|
||||
- タスクファイルを生成する自動化パイプライン
|
||||
#### Git Worktree による隔離実行
|
||||
|
||||
YAMLタスクファイルで`worktree`を指定すると、各タスクを隔離されたgit worktreeで実行し、メインの作業ディレクトリをクリーンに保てます:
|
||||
|
||||
- `worktree: true` - `.takt/worktrees/{timestamp}-{task-slug}/`に自動作成
|
||||
- `worktree: "/path/to/dir"` - 指定パスに作成
|
||||
- `branch: "feat/xxx"` - 指定ブランチを使用(省略時は`takt/{timestamp}-{slug}`で自動生成)
|
||||
- `worktree`省略 - カレントディレクトリで実行(デフォルト)
|
||||
|
||||
#### `/run-tasks` でタスクを実行
|
||||
|
||||
```bash
|
||||
takt /run-tasks
|
||||
```
|
||||
|
||||
- タスクはアルファベット順に実行されます(`001-`、`002-`のようなプレフィックスで順序を制御)
|
||||
- 完了したタスクは実行レポートとともに`.takt/completed/`に移動されます
|
||||
- 実行中に追加された新しいタスクも動的に取得されます
|
||||
|
||||
#### `/watch` でタスクを監視
|
||||
|
||||
```bash
|
||||
takt /watch
|
||||
```
|
||||
|
||||
ウォッチモードは`.takt/tasks/`をポーリングし、新しいタスクファイルが現れると自動実行します。`Ctrl+C`で停止する常駐プロセスです。以下のような場合に便利です:
|
||||
- タスクファイルを生成するCI/CDパイプライン
|
||||
- 外部プロセスがタスクを追加する自動化ワークフロー
|
||||
- タスクを順次キューイングする長時間の開発セッション
|
||||
|
||||
### カスタムワークフローの追加
|
||||
|
||||
@ -178,11 +205,56 @@ agent: /path/to/custom/agent.md
|
||||
| 変数 | 説明 |
|
||||
|------|------|
|
||||
| `{task}` | 元のユーザーリクエスト |
|
||||
| `{iteration}` | 現在のイテレーション番号 |
|
||||
| `{iteration}` | ワークフロー全体のターン数(実行された全ステップ数) |
|
||||
| `{max_iterations}` | 最大イテレーション数 |
|
||||
| `{step_iteration}` | ステップごとのイテレーション数(このステップが実行された回数) |
|
||||
| `{previous_response}` | 前のステップの出力(`pass_previous_response: true`が必要) |
|
||||
| `{user_inputs}` | ワークフロー中の追加ユーザー入力 |
|
||||
| `{git_diff}` | 現在のgit diff(コミットされていない変更) |
|
||||
| `{report_dir}` | レポートディレクトリ名(例:`20250126-143052-task-summary`) |
|
||||
|
||||
### ワークフローの設計
|
||||
|
||||
各ワークフローステップには3つの重要な要素が必要です。
|
||||
|
||||
**1. エージェント** - システムプロンプトを含むMarkdownファイル:
|
||||
|
||||
```yaml
|
||||
agent: ~/.takt/agents/default/coder.md # エージェントプロンプトファイルのパス
|
||||
agent_name: coder # 表示名(オプション)
|
||||
```
|
||||
|
||||
**2. ステータスルール** - エージェントが完了を通知する方法を定義。エージェントは`[CODER:DONE]`や`[ARCHITECT:REJECT]`のようなステータスマーカーを出力し、TAKTがそれを検出して遷移を駆動します:
|
||||
|
||||
```yaml
|
||||
status_rules_prompt: |
|
||||
最終出力には必ずステータスタグを含めてください:
|
||||
- `[CODER:DONE]` 実装が完了した場合
|
||||
- `[CODER:BLOCKED]` 進行できない場合
|
||||
```
|
||||
|
||||
**3. 遷移** - ステータスに基づいて次のステップにルーティング:
|
||||
|
||||
```yaml
|
||||
transitions:
|
||||
- condition: done # ステータスタグDONEに対応
|
||||
next_step: review # reviewステップへ遷移
|
||||
- condition: blocked # ステータスタグBLOCKEDに対応
|
||||
next_step: ABORT # ワークフローを失敗終了
|
||||
```
|
||||
|
||||
使用可能な遷移条件:`done`、`blocked`、`approved`、`rejected`、`improve`、`always`
|
||||
特殊なnext_step値:`COMPLETE`(成功)、`ABORT`(失敗)
|
||||
|
||||
**ステップオプション:**
|
||||
|
||||
| オプション | デフォルト | 説明 |
|
||||
|-----------|-----------|------|
|
||||
| `pass_previous_response` | `true` | 前のステップの出力を`{previous_response}`に渡す |
|
||||
| `on_no_status` | - | ステータス未検出時の動作:`complete`、`continue`、`stay` |
|
||||
| `allowed_tools` | - | エージェントが使用できるツール一覧(Read, Glob, Grep, Edit, Write, Bash等) |
|
||||
| `provider` | - | このステップのプロバイダーを上書き(`claude`または`codex`) |
|
||||
| `model` | - | このステップのモデルを上書き |
|
||||
|
||||
## ワークフロー
|
||||
|
||||
@ -215,11 +287,28 @@ steps:
|
||||
next_step: implement
|
||||
```
|
||||
|
||||
## ビルトインワークフロー
|
||||
|
||||
TAKTには複数のビルトインワークフローが同梱されています:
|
||||
|
||||
| ワークフロー | 説明 |
|
||||
|------------|------|
|
||||
| `default` | フル開発ワークフロー:計画 → 実装 → アーキテクトレビュー → AIレビュー → セキュリティレビュー → スーパーバイザー承認。各レビュー段階に修正ループあり。 |
|
||||
| `simple` | defaultの簡略版:計画 → 実装 → アーキテクトレビュー → AIレビュー → スーパーバイザー。中間の修正ステップなし。 |
|
||||
| `research` | リサーチワークフロー:プランナー → ディガー → スーパーバイザー。質問せずに自律的にリサーチを実行。 |
|
||||
| `expert-review` | ドメインエキスパートによる包括的レビュー:CQRS+ES、フロントエンド、AI、セキュリティ、QAレビューと修正ループ。 |
|
||||
| `magi` | エヴァンゲリオンにインスパイアされた審議システム。3つのAIペルソナ(MELCHIOR、BALTHASAR、CASPER)が分析し投票。 |
|
||||
|
||||
`takt /switch` でワークフローを切り替えられます。
|
||||
|
||||
## ビルトインエージェント
|
||||
|
||||
- **coder** - 機能を実装しバグを修正
|
||||
- **architect** - コードをレビューしフィードバックを提供
|
||||
- **supervisor** - 最終検証と承認
|
||||
- **planner** - タスク分析と実装計画
|
||||
- **ai-reviewer** - AI生成コードの品質レビュー
|
||||
- **security** - セキュリティ脆弱性の評価
|
||||
|
||||
## カスタムエージェント
|
||||
|
||||
@ -230,6 +319,8 @@ agents:
|
||||
- name: my-reviewer
|
||||
prompt_file: .takt/prompts/reviewer.md
|
||||
allowed_tools: [Read, Glob, Grep]
|
||||
provider: claude # オプション:claude または codex
|
||||
model: opus # Claude: opus/sonnet/haiku、Codex: gpt-5.2-codex 等
|
||||
status_patterns:
|
||||
approved: "\\[APPROVE\\]"
|
||||
rejected: "\\[REJECT\\]"
|
||||
@ -239,9 +330,17 @@ agents:
|
||||
|
||||
```
|
||||
~/.takt/
|
||||
├── config.yaml # グローバル設定
|
||||
├── config.yaml # グローバル設定(プロバイダー、モデル、ワークフロー等)
|
||||
├── workflows/ # ワークフロー定義
|
||||
└── agents/ # エージェントプロンプトファイル
|
||||
|
||||
.takt/ # プロジェクトレベルの設定
|
||||
├── agents.yaml # カスタムエージェント定義
|
||||
├── tasks/ # 保留中のタスクファイル(.yaml, .md)
|
||||
├── completed/ # 完了したタスクとレポート
|
||||
├── worktrees/ # タスク隔離実行用のgit worktree
|
||||
├── reports/ # 実行レポート(自動生成)
|
||||
└── logs/ # セッションログ
|
||||
```
|
||||
|
||||
## API使用例
|
||||
@ -300,6 +399,7 @@ docker compose run --rm build
|
||||
- [Agent Guide](./agents.md) - カスタムエージェントの設定
|
||||
- [Changelog](../CHANGELOG.md) - バージョン履歴
|
||||
- [Security Policy](../SECURITY.md) - 脆弱性報告
|
||||
- [ブログ:TAKT - AIエージェントオーケストレーション](https://zenn.dev/nrs/articles/c6842288a526d7) - 設計思想と実践的な使い方ガイド
|
||||
|
||||
## ライセンス
|
||||
|
||||
|
||||
@ -15,7 +15,8 @@
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"lint": "eslint src/",
|
||||
"prepublishOnly": "npm run lint && npm run build && npm run test"
|
||||
"prepublishOnly": "npm run lint && npm run build && npm run test",
|
||||
"postversion": "git push --follow-tags"
|
||||
},
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
145
src/__tests__/watcher.test.ts
Normal file
145
src/__tests__/watcher.test.ts
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* TaskWatcher tests
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdirSync, writeFileSync, existsSync, rmSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { TaskWatcher } from '../task/watcher.js';
|
||||
import type { TaskInfo } from '../task/runner.js';
|
||||
|
||||
describe('TaskWatcher', () => {
|
||||
const testDir = `/tmp/takt-watcher-test-${Date.now()}`;
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync(join(testDir, '.takt', 'tasks'), { recursive: true });
|
||||
mkdirSync(join(testDir, '.takt', 'completed'), { recursive: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create watcher with default options', () => {
|
||||
const watcher = new TaskWatcher(testDir);
|
||||
expect(watcher.isRunning()).toBe(false);
|
||||
});
|
||||
|
||||
it('should accept custom poll interval', () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 500 });
|
||||
expect(watcher.isRunning()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('watch', () => {
|
||||
it('should detect and process a task file', async () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 50 });
|
||||
const processed: string[] = [];
|
||||
|
||||
// Pre-create a task file
|
||||
writeFileSync(
|
||||
join(testDir, '.takt', 'tasks', 'test-task.md'),
|
||||
'Test task content'
|
||||
);
|
||||
|
||||
// Start watching, stop after first task
|
||||
const watchPromise = watcher.watch(async (task: TaskInfo) => {
|
||||
processed.push(task.name);
|
||||
// Stop after processing to avoid infinite loop in test
|
||||
watcher.stop();
|
||||
});
|
||||
|
||||
await watchPromise;
|
||||
|
||||
expect(processed).toEqual(['test-task']);
|
||||
expect(watcher.isRunning()).toBe(false);
|
||||
});
|
||||
|
||||
it('should wait when no tasks are available', async () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 50 });
|
||||
let pollCount = 0;
|
||||
|
||||
// Start watching, add a task after a delay
|
||||
const watchPromise = watcher.watch(async (task: TaskInfo) => {
|
||||
pollCount++;
|
||||
watcher.stop();
|
||||
});
|
||||
|
||||
// Add task after short delay (after at least one empty poll)
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
writeFileSync(
|
||||
join(testDir, '.takt', 'tasks', 'delayed-task.md'),
|
||||
'Delayed task'
|
||||
);
|
||||
|
||||
await watchPromise;
|
||||
|
||||
expect(pollCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should process multiple tasks sequentially', async () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 50 });
|
||||
const processed: string[] = [];
|
||||
|
||||
// Pre-create two task files
|
||||
writeFileSync(
|
||||
join(testDir, '.takt', 'tasks', 'a-task.md'),
|
||||
'First task'
|
||||
);
|
||||
writeFileSync(
|
||||
join(testDir, '.takt', 'tasks', 'b-task.md'),
|
||||
'Second task'
|
||||
);
|
||||
|
||||
const watchPromise = watcher.watch(async (task: TaskInfo) => {
|
||||
processed.push(task.name);
|
||||
// Remove the task file to simulate completion
|
||||
rmSync(task.filePath);
|
||||
if (processed.length >= 2) {
|
||||
watcher.stop();
|
||||
}
|
||||
});
|
||||
|
||||
await watchPromise;
|
||||
|
||||
expect(processed).toEqual(['a-task', 'b-task']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stop', () => {
|
||||
it('should stop the watcher gracefully', async () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 50 });
|
||||
|
||||
// Start watching, stop after a short delay
|
||||
const watchPromise = watcher.watch(async () => {
|
||||
// Should not be called since no tasks
|
||||
});
|
||||
|
||||
// Stop after short delay
|
||||
setTimeout(() => watcher.stop(), 100);
|
||||
|
||||
await watchPromise;
|
||||
|
||||
expect(watcher.isRunning()).toBe(false);
|
||||
});
|
||||
|
||||
it('should abort sleep immediately when stopped', async () => {
|
||||
const watcher = new TaskWatcher(testDir, { pollInterval: 10000 });
|
||||
|
||||
const start = Date.now();
|
||||
const watchPromise = watcher.watch(async () => {});
|
||||
|
||||
// Stop after 50ms, should not wait the full 10s
|
||||
setTimeout(() => watcher.stop(), 50);
|
||||
|
||||
await watchPromise;
|
||||
|
||||
const elapsed = Date.now() - start;
|
||||
// Should complete well under the 10s poll interval
|
||||
expect(elapsed).toBeLessThan(1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -31,6 +31,7 @@ import {
|
||||
switchConfig,
|
||||
addTask,
|
||||
refreshBuiltin,
|
||||
watchTasks,
|
||||
} from './commands/index.js';
|
||||
import { listWorkflows } from './config/workflowLoader.js';
|
||||
import { selectOptionWithDefault } from './prompt/index.js';
|
||||
@ -114,9 +115,13 @@ program
|
||||
await refreshBuiltin();
|
||||
return;
|
||||
|
||||
case 'watch':
|
||||
await watchTasks(cwd);
|
||||
return;
|
||||
|
||||
default:
|
||||
error(`Unknown command: /${command}`);
|
||||
info('Available: /run-tasks, /add-task, /switch, /clear, /refresh-builtin, /help, /config');
|
||||
info('Available: /run-tasks, /watch, /add-task, /switch, /clear, /refresh-builtin, /help, /config');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ export function showHelp(): void {
|
||||
Usage:
|
||||
takt {task} Execute task with current workflow (continues session)
|
||||
takt /run-tasks Run all pending tasks from .takt/tasks/
|
||||
takt /watch Watch for tasks and auto-execute (stays resident)
|
||||
takt /add-task Add a new task (interactive, YAML format)
|
||||
takt /switch Switch workflow interactively
|
||||
takt /clear Clear agent conversation sessions (reset to initial state)
|
||||
@ -26,6 +27,7 @@ Examples:
|
||||
takt /add-task "認証機能を追加する" # Quick add task
|
||||
takt /add-task # Interactive task creation
|
||||
takt /clear # Clear sessions, start fresh
|
||||
takt /watch # Watch & auto-execute tasks
|
||||
takt /refresh-builtin # Update builtin resources
|
||||
takt /switch
|
||||
takt /run-tasks
|
||||
|
||||
@ -6,6 +6,7 @@ export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOp
|
||||
export { executeTask, runAllTasks } from './taskExecution.js';
|
||||
export { addTask } from './addTask.js';
|
||||
export { refreshBuiltin } from './refreshBuiltin.js';
|
||||
export { watchTasks } from './watchTasks.js';
|
||||
export { showHelp } from './help.js';
|
||||
export { withAgentSession } from './session.js';
|
||||
export { switchWorkflow } from './workflow.js';
|
||||
|
||||
@ -136,7 +136,7 @@ export async function runAllTasks(
|
||||
* Resolve execution directory and workflow from task data.
|
||||
* If the task has worktree settings, create a worktree and use it as cwd.
|
||||
*/
|
||||
function resolveTaskExecution(
|
||||
export function resolveTaskExecution(
|
||||
task: TaskInfo,
|
||||
defaultCwd: string,
|
||||
defaultWorkflow: string
|
||||
|
||||
118
src/commands/watchTasks.ts
Normal file
118
src/commands/watchTasks.ts
Normal file
@ -0,0 +1,118 @@
|
||||
/**
|
||||
* /watch command implementation
|
||||
*
|
||||
* Watches .takt/tasks/ for new task files and executes them automatically.
|
||||
* Stays resident until Ctrl+C (SIGINT).
|
||||
*/
|
||||
|
||||
import { TaskRunner, type TaskInfo } from '../task/index.js';
|
||||
import { TaskWatcher } from '../task/watcher.js';
|
||||
import { getCurrentWorkflow } from '../config/paths.js';
|
||||
import {
|
||||
header,
|
||||
info,
|
||||
error,
|
||||
success,
|
||||
status,
|
||||
} from '../utils/ui.js';
|
||||
import { createLogger } from '../utils/debug.js';
|
||||
import { getErrorMessage } from '../utils/error.js';
|
||||
import { executeTask, resolveTaskExecution } from './taskExecution.js';
|
||||
import { DEFAULT_WORKFLOW_NAME } from '../constants.js';
|
||||
|
||||
const log = createLogger('watch');
|
||||
|
||||
/**
|
||||
* Watch for tasks and execute them as they appear.
|
||||
* Runs until Ctrl+C.
|
||||
*/
|
||||
export async function watchTasks(cwd: string): Promise<void> {
|
||||
const workflowName = getCurrentWorkflow(cwd) || DEFAULT_WORKFLOW_NAME;
|
||||
const taskRunner = new TaskRunner(cwd);
|
||||
const watcher = new TaskWatcher(cwd);
|
||||
|
||||
let taskCount = 0;
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
header('TAKT Watch Mode');
|
||||
info(`Workflow: ${workflowName}`);
|
||||
info(`Watching: ${taskRunner.getTasksDir()}`);
|
||||
info('Waiting for tasks... (Ctrl+C to stop)');
|
||||
console.log();
|
||||
|
||||
// Graceful shutdown on SIGINT
|
||||
const onSigInt = () => {
|
||||
console.log();
|
||||
info('Stopping watch...');
|
||||
watcher.stop();
|
||||
};
|
||||
process.on('SIGINT', onSigInt);
|
||||
|
||||
try {
|
||||
await watcher.watch(async (task: TaskInfo) => {
|
||||
taskCount++;
|
||||
console.log();
|
||||
info(`=== Task ${taskCount}: ${task.name} ===`);
|
||||
console.log();
|
||||
|
||||
const startedAt = new Date().toISOString();
|
||||
const executionLog: string[] = [];
|
||||
|
||||
try {
|
||||
const { execCwd, execWorkflow } = resolveTaskExecution(task, cwd, workflowName);
|
||||
const taskSuccess = await executeTask(task.content, execCwd, execWorkflow);
|
||||
const completedAt = new Date().toISOString();
|
||||
|
||||
taskRunner.completeTask({
|
||||
task,
|
||||
success: taskSuccess,
|
||||
response: taskSuccess ? 'Task completed successfully' : 'Task failed',
|
||||
executionLog,
|
||||
startedAt,
|
||||
completedAt,
|
||||
});
|
||||
|
||||
if (taskSuccess) {
|
||||
successCount++;
|
||||
success(`Task "${task.name}" completed`);
|
||||
} else {
|
||||
failCount++;
|
||||
error(`Task "${task.name}" failed`);
|
||||
}
|
||||
} catch (err) {
|
||||
failCount++;
|
||||
const completedAt = new Date().toISOString();
|
||||
|
||||
taskRunner.completeTask({
|
||||
task,
|
||||
success: false,
|
||||
response: getErrorMessage(err),
|
||||
executionLog,
|
||||
startedAt,
|
||||
completedAt,
|
||||
});
|
||||
|
||||
error(`Task "${task.name}" error: ${getErrorMessage(err)}`);
|
||||
}
|
||||
|
||||
console.log();
|
||||
info('Waiting for tasks... (Ctrl+C to stop)');
|
||||
});
|
||||
} finally {
|
||||
process.removeListener('SIGINT', onSigInt);
|
||||
}
|
||||
|
||||
// Summary on exit
|
||||
if (taskCount > 0) {
|
||||
console.log();
|
||||
header('Watch Summary');
|
||||
status('Total', String(taskCount));
|
||||
status('Success', String(successCount), successCount === taskCount ? 'green' : undefined);
|
||||
if (failCount > 0) {
|
||||
status('Failed', String(failCount), 'red');
|
||||
}
|
||||
}
|
||||
|
||||
success('Watch stopped.');
|
||||
}
|
||||
@ -13,3 +13,4 @@ export { showTaskList } from './display.js';
|
||||
export { TaskFileSchema, type TaskFileData } from './schema.js';
|
||||
export { parseTaskFile, parseTaskFiles, type ParsedTask } from './parser.js';
|
||||
export { createWorktree, removeWorktree, type WorktreeOptions, type WorktreeResult } from './worktree.js';
|
||||
export { TaskWatcher, type TaskWatcherOptions } from './watcher.js';
|
||||
|
||||
90
src/task/watcher.ts
Normal file
90
src/task/watcher.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Task directory watcher
|
||||
*
|
||||
* Polls .takt/tasks/ for new task files and invokes a callback when found.
|
||||
* Uses polling (not fs.watch) for cross-platform reliability.
|
||||
*/
|
||||
|
||||
import { createLogger } from '../utils/debug.js';
|
||||
import { TaskRunner, type TaskInfo } from './runner.js';
|
||||
|
||||
const log = createLogger('watcher');
|
||||
|
||||
export interface TaskWatcherOptions {
|
||||
/** Polling interval in milliseconds (default: 2000) */
|
||||
pollInterval?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_POLL_INTERVAL = 2000;
|
||||
|
||||
export class TaskWatcher {
|
||||
private runner: TaskRunner;
|
||||
private pollInterval: number;
|
||||
private running = false;
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
constructor(projectDir: string, options?: TaskWatcherOptions) {
|
||||
this.runner = new TaskRunner(projectDir);
|
||||
this.pollInterval = options?.pollInterval ?? DEFAULT_POLL_INTERVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching for tasks.
|
||||
* Resolves only when stop() is called.
|
||||
*/
|
||||
async watch(onTask: (task: TaskInfo) => Promise<void>): Promise<void> {
|
||||
this.running = true;
|
||||
this.abortController = new AbortController();
|
||||
|
||||
log.info('Watch started', { pollInterval: this.pollInterval });
|
||||
|
||||
while (this.running) {
|
||||
const task = this.runner.getNextTask();
|
||||
|
||||
if (task) {
|
||||
log.info('Task found', { name: task.name });
|
||||
await onTask(task);
|
||||
// After task execution, immediately check for next task (no sleep)
|
||||
continue;
|
||||
}
|
||||
|
||||
// No tasks: wait before next poll
|
||||
await this.sleep(this.pollInterval);
|
||||
}
|
||||
|
||||
log.info('Watch stopped');
|
||||
}
|
||||
|
||||
/** Stop watching */
|
||||
stop(): void {
|
||||
this.running = false;
|
||||
this.abortController?.abort();
|
||||
}
|
||||
|
||||
/** Whether the watcher is currently active */
|
||||
isRunning(): boolean {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep with abort support.
|
||||
* Resolves early if stop() is called.
|
||||
*/
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const signal = this.abortController?.signal;
|
||||
|
||||
if (signal?.aborted) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(resolve, ms);
|
||||
|
||||
signal?.addEventListener('abort', () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user