タスクを監視する /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.
|
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 |
|
| 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 src/__tests__/client.test.ts` | Run single test file |
|
||||||
| `npx vitest run -t "pattern"` | Run tests matching pattern |
|
| `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
|
## Architecture
|
||||||
|
|
||||||
### Core Flow
|
### Core Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
CLI (cli.ts)
|
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()
|
→ or executeTask()
|
||||||
→ WorkflowEngine (workflow/engine.ts)
|
→ WorkflowEngine (workflow/engine.ts)
|
||||||
→ runAgent() (agents/runner.ts)
|
→ runAgent() (agents/runner.ts)
|
||||||
@ -63,6 +76,11 @@ CLI (cli.ts)
|
|||||||
- `agentLoader.ts` - Agent prompt file loading
|
- `agentLoader.ts` - Agent prompt file loading
|
||||||
- `paths.ts` - Directory structure (`.takt/`, `~/.takt/`), session management
|
- `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
|
### Data Flow
|
||||||
|
|
||||||
1. User provides task or slash command → CLI
|
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
|
- `rejected` - Review failed, needs major rework
|
||||||
- `improve` - Needs improvement (security concerns, quality issues)
|
- `improve` - Needs improvement (security concerns, quality issues)
|
||||||
- `always` - Unconditional transition
|
- `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)
|
# Run a task (will prompt for workflow selection)
|
||||||
takt "Add a login feature"
|
takt "Add a login feature"
|
||||||
|
|
||||||
# Switch workflow
|
# Add a task to the queue
|
||||||
takt /switch
|
takt /add-task "Fix the login bug"
|
||||||
|
|
||||||
# Run all pending tasks
|
# Run all pending tasks
|
||||||
takt /run-tasks
|
takt /run-tasks
|
||||||
|
|
||||||
|
# Watch for tasks and auto-execute
|
||||||
|
takt /watch
|
||||||
|
|
||||||
|
# Switch workflow
|
||||||
|
takt /switch
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
| Command | Description |
|
| 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 -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 /switch` | Switch workflow interactively |
|
||||||
| `takt /clear` | Clear agent conversation sessions |
|
| `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 |
|
| `takt /help` | Show help |
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
@ -88,11 +98,28 @@ steps:
|
|||||||
next_step: implement
|
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
|
## Built-in Agents
|
||||||
|
|
||||||
- **coder** - Implements features and fixes bugs
|
- **coder** - Implements features and fixes bugs
|
||||||
- **architect** - Reviews code and provides feedback
|
- **architect** - Reviews code and provides feedback
|
||||||
- **supervisor** - Final verification and approval
|
- **supervisor** - Final verification and approval
|
||||||
|
- **planner** - Task analysis and implementation planning
|
||||||
|
- **ai-reviewer** - AI-generated code quality review
|
||||||
|
- **security** - Security vulnerability assessment
|
||||||
|
|
||||||
## Custom Agents
|
## Custom Agents
|
||||||
|
|
||||||
@ -149,6 +176,14 @@ Available Codex models:
|
|||||||
├── config.yaml # Global config (provider, model, workflows, etc.)
|
├── config.yaml # Global config (provider, model, workflows, etc.)
|
||||||
├── workflows/ # Workflow definitions
|
├── workflows/ # Workflow definitions
|
||||||
└── agents/ # Agent prompt files
|
└── 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
|
### 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.
|
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
|
### Adding Custom Workflows
|
||||||
|
|
||||||
Create your own workflow by adding YAML files to `~/.takt/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)
|
- [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
|
```bash
|
||||||
# Create task files as you think of them
|
# Quick add (no worktree)
|
||||||
echo "Add unit tests for the auth module" > .takt/tasks/001-add-tests.md
|
takt /add-task "Add authentication feature"
|
||||||
echo "Refactor the database layer" > .takt/tasks/002-refactor-db.md
|
|
||||||
echo "Update API documentation" > .takt/tasks/003-update-docs.md
|
|
||||||
|
|
||||||
# Run all pending tasks
|
# Interactive mode (prompts for worktree, branch, workflow options)
|
||||||
takt /run-tasks
|
takt /add-task
|
||||||
```
|
```
|
||||||
|
|
||||||
**How it works:**
|
#### Task File Formats
|
||||||
- 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 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
|
```markdown
|
||||||
# .takt/tasks/add-login-feature.md
|
# .takt/tasks/add-login-feature.md
|
||||||
@ -301,10 +326,35 @@ Requirements:
|
|||||||
- Error handling for failed attempts
|
- Error handling for failed attempts
|
||||||
```
|
```
|
||||||
|
|
||||||
This is perfect for:
|
#### Git Worktree Isolation
|
||||||
- Brainstorming sessions where you capture ideas as files
|
|
||||||
- Breaking down large features into smaller tasks
|
YAML task files can specify `worktree` to run each task in an isolated git worktree, keeping the main working directory clean:
|
||||||
- Automated pipelines that generate task files
|
|
||||||
|
- `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
|
### Workflow Variables
|
||||||
|
|
||||||
@ -313,11 +363,56 @@ Available variables in `instruction_template`:
|
|||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `{task}` | Original user request |
|
| `{task}` | Original user request |
|
||||||
| `{iteration}` | Current iteration number |
|
| `{iteration}` | Workflow-wide turn count (total steps executed) |
|
||||||
| `{max_iterations}` | Maximum iterations |
|
| `{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`) |
|
| `{previous_response}` | Previous step's output (requires `pass_previous_response: true`) |
|
||||||
| `{user_inputs}` | Additional user inputs during workflow |
|
| `{user_inputs}` | Additional user inputs during workflow |
|
||||||
| `{git_diff}` | Current git diff (uncommitted changes) |
|
| `{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
|
## 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
|
- [Agent Guide](./docs/agents.md) - Configure custom agents
|
||||||
- [Changelog](./CHANGELOG.md) - Version history
|
- [Changelog](./CHANGELOG.md) - Version history
|
||||||
- [Security Policy](./SECURITY.md) - Vulnerability reporting
|
- [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
|
## License
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
# TAKT
|
# 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**: このプロジェクトは個人のペースで開発されています。詳細は[免責事項](#免責事項)をご覧ください。
|
> **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 "ログイン機能を追加して"
|
||||||
|
|
||||||
# ワークフローを切り替え
|
# タスクをキューに追加
|
||||||
takt /switch
|
takt /add-task "ログインのバグを修正"
|
||||||
|
|
||||||
# 保留中のタスクをすべて実行
|
# 保留中のタスクをすべて実行
|
||||||
takt /run-tasks
|
takt /run-tasks
|
||||||
|
|
||||||
|
# タスクを監視して自動実行
|
||||||
|
takt /watch
|
||||||
|
|
||||||
|
# ワークフローを切り替え
|
||||||
|
takt /switch
|
||||||
```
|
```
|
||||||
|
|
||||||
## コマンド一覧
|
## コマンド一覧
|
||||||
|
|
||||||
| コマンド | 説明 |
|
| コマンド | 説明 |
|
||||||
|---------|------|
|
|---------|------|
|
||||||
| `takt "タスク"` | ワークフロー選択後にタスクを実行 |
|
| `takt "タスク"` | 現在のワークフローでタスクを実行(セッション継続) |
|
||||||
| `takt -r "タスク"` | 前回のセッションを再開してタスクを実行 |
|
| `takt -r "タスク"` | 前回のセッションを再開してタスクを実行 |
|
||||||
| `takt /run-tasks` | 保留中のタスクをすべて実行 |
|
| `takt /run-tasks` | `.takt/tasks/` の保留中タスクをすべて実行 |
|
||||||
|
| `takt /watch` | `.takt/tasks/` を監視してタスクを自動実行(常駐プロセス) |
|
||||||
|
| `takt /add-task` | 新しいタスクを対話的に追加(YAML形式) |
|
||||||
| `takt /switch` | ワークフローを対話的に切り替え |
|
| `takt /switch` | ワークフローを対話的に切り替え |
|
||||||
| `takt /clear` | エージェントの会話セッションをクリア |
|
| `takt /clear` | エージェントの会話セッションをクリア |
|
||||||
|
| `takt /refresh-builtin` | ビルトインのエージェント/ワークフローを最新版に更新 |
|
||||||
|
| `takt /config` | 現在の設定を表示 |
|
||||||
| `takt /help` | ヘルプを表示 |
|
| `takt /help` | ヘルプを表示 |
|
||||||
|
|
||||||
## 実践的な使い方ガイド
|
## 実践的な使い方ガイド
|
||||||
@ -54,43 +66,33 @@ takt -r "パスワードに特殊文字が含まれているとバグが発生
|
|||||||
|
|
||||||
`-r`フラグはエージェントの会話履歴を保持し、自然なやり取りを可能にします。
|
`-r`フラグはエージェントの会話履歴を保持し、自然なやり取りを可能にします。
|
||||||
|
|
||||||
### MAGIシステムで遊ぶ
|
### タスク管理
|
||||||
|
|
||||||
MAGIはエヴァンゲリオンにインスパイアされた審議システムです。3つのAIペルソナがあなたの質問を異なる視点から分析し、投票します:
|
TAKTは`.takt/tasks/`内のタスクファイルによるバッチ処理をサポートしています。`.yaml`/`.yml`と`.md`の両方のファイル形式に対応しています。
|
||||||
|
|
||||||
|
#### `/add-task` でタスクを追加
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# プロンプトが表示されたら'magi'ワークフローを選択
|
# クイック追加(worktreeなし)
|
||||||
takt "RESTからGraphQLに移行すべきか?"
|
takt /add-task "認証機能を追加"
|
||||||
|
|
||||||
|
# 対話モード(worktree、ブランチ、ワークフローオプションを指定可能)
|
||||||
|
takt /add-task
|
||||||
```
|
```
|
||||||
|
|
||||||
3つのMAGIペルソナ:
|
#### タスクファイルの形式
|
||||||
- **MELCHIOR-1**(科学者):論理的、データ駆動の分析
|
|
||||||
- **BALTHASAR-2**(母性):チームと人間中心の視点
|
|
||||||
- **CASPER-3**(現実主義者):実用的で現実的な考慮
|
|
||||||
|
|
||||||
各ペルソナは APPROVE、REJECT、または CONDITIONAL で投票します。最終決定は多数決で行われます。
|
**YAML形式**(推奨、worktree/branch/workflowオプション対応):
|
||||||
|
|
||||||
### `/run-tasks` でバッチ処理
|
```yaml
|
||||||
|
# .takt/tasks/add-auth.yaml
|
||||||
`/run-tasks`コマンドは`.takt/tasks/`ディレクトリ内のすべてのタスクファイルを実行します:
|
task: "認証機能を追加する"
|
||||||
|
worktree: true # 隔離されたgit worktreeで実行
|
||||||
```bash
|
branch: "feat/add-auth" # ブランチ名(省略時は自動生成)
|
||||||
# 思いつくままにタスクファイルを作成
|
workflow: "default" # ワークフロー指定(省略時は現在のもの)
|
||||||
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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**動作の仕組み:**
|
**Markdown形式**(シンプル、後方互換):
|
||||||
- タスクはアルファベット順に実行されます(`001-`、`002-`のようなプレフィックスで順序を制御)
|
|
||||||
- 各タスクファイルには実行すべき内容の説明を含めます
|
|
||||||
- 完了したタスクは実行レポートとともに`.takt/completed/`に移動されます
|
|
||||||
- 実行中に追加された新しいタスクも動的に取得されます
|
|
||||||
|
|
||||||
**タスクファイルの形式:**
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
# .takt/tasks/add-login-feature.md
|
# .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}` | 元のユーザーリクエスト |
|
| `{task}` | 元のユーザーリクエスト |
|
||||||
| `{iteration}` | 現在のイテレーション番号 |
|
| `{iteration}` | ワークフロー全体のターン数(実行された全ステップ数) |
|
||||||
| `{max_iterations}` | 最大イテレーション数 |
|
| `{max_iterations}` | 最大イテレーション数 |
|
||||||
|
| `{step_iteration}` | ステップごとのイテレーション数(このステップが実行された回数) |
|
||||||
| `{previous_response}` | 前のステップの出力(`pass_previous_response: true`が必要) |
|
| `{previous_response}` | 前のステップの出力(`pass_previous_response: true`が必要) |
|
||||||
| `{user_inputs}` | ワークフロー中の追加ユーザー入力 |
|
| `{user_inputs}` | ワークフロー中の追加ユーザー入力 |
|
||||||
| `{git_diff}` | 現在のgit diff(コミットされていない変更) |
|
| `{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
|
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** - 機能を実装しバグを修正
|
- **coder** - 機能を実装しバグを修正
|
||||||
- **architect** - コードをレビューしフィードバックを提供
|
- **architect** - コードをレビューしフィードバックを提供
|
||||||
- **supervisor** - 最終検証と承認
|
- **supervisor** - 最終検証と承認
|
||||||
|
- **planner** - タスク分析と実装計画
|
||||||
|
- **ai-reviewer** - AI生成コードの品質レビュー
|
||||||
|
- **security** - セキュリティ脆弱性の評価
|
||||||
|
|
||||||
## カスタムエージェント
|
## カスタムエージェント
|
||||||
|
|
||||||
@ -230,6 +319,8 @@ agents:
|
|||||||
- name: my-reviewer
|
- name: my-reviewer
|
||||||
prompt_file: .takt/prompts/reviewer.md
|
prompt_file: .takt/prompts/reviewer.md
|
||||||
allowed_tools: [Read, Glob, Grep]
|
allowed_tools: [Read, Glob, Grep]
|
||||||
|
provider: claude # オプション:claude または codex
|
||||||
|
model: opus # Claude: opus/sonnet/haiku、Codex: gpt-5.2-codex 等
|
||||||
status_patterns:
|
status_patterns:
|
||||||
approved: "\\[APPROVE\\]"
|
approved: "\\[APPROVE\\]"
|
||||||
rejected: "\\[REJECT\\]"
|
rejected: "\\[REJECT\\]"
|
||||||
@ -239,9 +330,17 @@ agents:
|
|||||||
|
|
||||||
```
|
```
|
||||||
~/.takt/
|
~/.takt/
|
||||||
├── config.yaml # グローバル設定
|
├── config.yaml # グローバル設定(プロバイダー、モデル、ワークフロー等)
|
||||||
├── workflows/ # ワークフロー定義
|
├── workflows/ # ワークフロー定義
|
||||||
└── agents/ # エージェントプロンプトファイル
|
└── agents/ # エージェントプロンプトファイル
|
||||||
|
|
||||||
|
.takt/ # プロジェクトレベルの設定
|
||||||
|
├── agents.yaml # カスタムエージェント定義
|
||||||
|
├── tasks/ # 保留中のタスクファイル(.yaml, .md)
|
||||||
|
├── completed/ # 完了したタスクとレポート
|
||||||
|
├── worktrees/ # タスク隔離実行用のgit worktree
|
||||||
|
├── reports/ # 実行レポート(自動生成)
|
||||||
|
└── logs/ # セッションログ
|
||||||
```
|
```
|
||||||
|
|
||||||
## API使用例
|
## API使用例
|
||||||
@ -300,6 +399,7 @@ docker compose run --rm build
|
|||||||
- [Agent Guide](./agents.md) - カスタムエージェントの設定
|
- [Agent Guide](./agents.md) - カスタムエージェントの設定
|
||||||
- [Changelog](../CHANGELOG.md) - バージョン履歴
|
- [Changelog](../CHANGELOG.md) - バージョン履歴
|
||||||
- [Security Policy](../SECURITY.md) - 脆弱性報告
|
- [Security Policy](../SECURITY.md) - 脆弱性報告
|
||||||
|
- [ブログ:TAKT - AIエージェントオーケストレーション](https://zenn.dev/nrs/articles/c6842288a526d7) - 設計思想と実践的な使い方ガイド
|
||||||
|
|
||||||
## ライセンス
|
## ライセンス
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"lint": "eslint src/",
|
"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": [
|
"keywords": [
|
||||||
"claude",
|
"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,
|
switchConfig,
|
||||||
addTask,
|
addTask,
|
||||||
refreshBuiltin,
|
refreshBuiltin,
|
||||||
|
watchTasks,
|
||||||
} from './commands/index.js';
|
} from './commands/index.js';
|
||||||
import { listWorkflows } from './config/workflowLoader.js';
|
import { listWorkflows } from './config/workflowLoader.js';
|
||||||
import { selectOptionWithDefault } from './prompt/index.js';
|
import { selectOptionWithDefault } from './prompt/index.js';
|
||||||
@ -114,9 +115,13 @@ program
|
|||||||
await refreshBuiltin();
|
await refreshBuiltin();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 'watch':
|
||||||
|
await watchTasks(cwd);
|
||||||
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
error(`Unknown command: /${command}`);
|
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);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ export function showHelp(): void {
|
|||||||
Usage:
|
Usage:
|
||||||
takt {task} Execute task with current workflow (continues session)
|
takt {task} Execute task with current workflow (continues session)
|
||||||
takt /run-tasks Run all pending tasks from .takt/tasks/
|
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 /add-task Add a new task (interactive, YAML format)
|
||||||
takt /switch Switch workflow interactively
|
takt /switch Switch workflow interactively
|
||||||
takt /clear Clear agent conversation sessions (reset to initial state)
|
takt /clear Clear agent conversation sessions (reset to initial state)
|
||||||
@ -26,6 +27,7 @@ Examples:
|
|||||||
takt /add-task "認証機能を追加する" # Quick add task
|
takt /add-task "認証機能を追加する" # Quick add task
|
||||||
takt /add-task # Interactive task creation
|
takt /add-task # Interactive task creation
|
||||||
takt /clear # Clear sessions, start fresh
|
takt /clear # Clear sessions, start fresh
|
||||||
|
takt /watch # Watch & auto-execute tasks
|
||||||
takt /refresh-builtin # Update builtin resources
|
takt /refresh-builtin # Update builtin resources
|
||||||
takt /switch
|
takt /switch
|
||||||
takt /run-tasks
|
takt /run-tasks
|
||||||
|
|||||||
@ -6,6 +6,7 @@ export { executeWorkflow, type WorkflowExecutionResult, type WorkflowExecutionOp
|
|||||||
export { executeTask, runAllTasks } from './taskExecution.js';
|
export { executeTask, runAllTasks } from './taskExecution.js';
|
||||||
export { addTask } from './addTask.js';
|
export { addTask } from './addTask.js';
|
||||||
export { refreshBuiltin } from './refreshBuiltin.js';
|
export { refreshBuiltin } from './refreshBuiltin.js';
|
||||||
|
export { watchTasks } from './watchTasks.js';
|
||||||
export { showHelp } from './help.js';
|
export { showHelp } from './help.js';
|
||||||
export { withAgentSession } from './session.js';
|
export { withAgentSession } from './session.js';
|
||||||
export { switchWorkflow } from './workflow.js';
|
export { switchWorkflow } from './workflow.js';
|
||||||
|
|||||||
@ -136,7 +136,7 @@ export async function runAllTasks(
|
|||||||
* Resolve execution directory and workflow from task data.
|
* Resolve execution directory and workflow from task data.
|
||||||
* If the task has worktree settings, create a worktree and use it as cwd.
|
* If the task has worktree settings, create a worktree and use it as cwd.
|
||||||
*/
|
*/
|
||||||
function resolveTaskExecution(
|
export function resolveTaskExecution(
|
||||||
task: TaskInfo,
|
task: TaskInfo,
|
||||||
defaultCwd: string,
|
defaultCwd: string,
|
||||||
defaultWorkflow: 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 { TaskFileSchema, type TaskFileData } from './schema.js';
|
||||||
export { parseTaskFile, parseTaskFiles, type ParsedTask } from './parser.js';
|
export { parseTaskFile, parseTaskFiles, type ParsedTask } from './parser.js';
|
||||||
export { createWorktree, removeWorktree, type WorktreeOptions, type WorktreeResult } from './worktree.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