From 96ff2ed9619901b71cf7c5a4e9e0f4682c6ebe65 Mon Sep 17 00:00:00 2001 From: nrslib <38722970+nrslib@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:46:16 +0900 Subject: [PATCH] update CLAUDE.md --- CLAUDE.md | 348 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 243 insertions(+), 105 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 40e76fb..6317d24 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,13 +10,19 @@ TAKT (TAKT Agent Koordination Topology) is a multi-agent orchestration system fo | Command | Description | |---------|-------------| -| `npm run build` | TypeScript build | +| `npm run build` | TypeScript build (also copies prompt .md, i18n .yaml, and preset .sh files to dist/) | | `npm run watch` | TypeScript build in watch mode | -| `npm run test` | Run all tests | -| `npm run test:watch` | Run tests in watch mode (alias: `npm run test -- --watch`) | +| `npm run test` | Run all unit tests | +| `npm run test:watch` | Run tests in watch mode | | `npm run lint` | ESLint | | `npx vitest run src/__tests__/client.test.ts` | Run single test file | | `npx vitest run -t "pattern"` | Run tests matching pattern | +| `npm run test:e2e` | Run E2E tests with mock provider (includes GitHub connectivity check) | +| `npm run test:e2e:mock` | Run E2E tests with mock provider (direct, no connectivity check) | +| `npm run test:e2e:provider:claude` | Run E2E tests against Claude provider | +| `npm run test:e2e:provider:codex` | Run E2E tests against Codex provider | +| `npm run test:e2e:provider:opencode` | Run E2E tests against OpenCode provider | +| `npm run check:release` | Full release check (build + lint + test + e2e) with macOS notification | | `npm run prepublishOnly` | Lint, build, and test before publishing | ## CLI Subcommands @@ -27,15 +33,25 @@ TAKT (TAKT Agent Koordination Topology) is a multi-agent orchestration system fo | `takt` | Interactive task input mode (chat with AI to refine requirements) | | `takt run` | Execute all pending tasks from `.takt/tasks/` once | | `takt watch` | Watch `.takt/tasks/` and auto-execute tasks (resident process) | -| `takt add` | Add a new task via AI conversation | -| `takt list` | List task branches (try merge, merge & cleanup, or delete) | -| `takt switch` | Switch piece interactively | +| `takt add [task]` | Add a new task via AI conversation | +| `takt list` | List task branches (merge, delete, retry) | +| `takt switch [piece]` | Switch piece interactively | | `takt clear` | Clear agent conversation sessions (reset state) | -| `takt eject` | Copy builtin piece/agents to `~/.takt/` for customization | +| `takt eject [type] [name]` | Copy builtin piece or facet for customization (`--global` for ~/.takt/) | +| `takt prompt [piece]` | Preview assembled prompts for each movement and phase | +| `takt catalog [type]` | List available facets (personas, policies, knowledge, etc.) | +| `takt export-cc` | Export takt pieces/agents as Claude Code Skill (~/.claude/) | +| `takt reset config` | Reset global config to builtin template | +| `takt reset categories` | Reset piece categories to builtin defaults | +| `takt metrics review` | Show review quality metrics | +| `takt purge` | Purge old analytics event files | +| `takt repertoire add ` | Install a repertoire package from GitHub | +| `takt repertoire remove ` | Remove an installed repertoire package | +| `takt repertoire list` | List installed repertoire packages | | `takt config` | Configure settings (permission mode) | | `takt --help` | Show help message | -**Interactive mode:** Running `takt` (without arguments) or `takt {initial message}` starts an interactive planning session. The AI helps refine task requirements through conversation. Type `/go` to execute the task with the selected piece, or `/cancel` to abort. Implemented in `src/features/interactive/`. +**Interactive mode:** Running `takt` (without arguments) or `takt {initial message}` starts an interactive planning session. Supports 4 modes: `assistant` (default, AI asks clarifying questions), `passthrough` (passes input directly as task), `quiet` (generates instructions without questions), `persona` (uses first movement's persona for conversation). Type `/go` to execute the task with the selected piece, or `/cancel` to abort. Implemented in `src/features/interactive/`. **Pipeline mode:** Specifying `--pipeline` enables non-interactive mode suitable for CI/CD. Automatically creates a branch, runs the piece, commits, and pushes. Use `--auto-pr` to also create a pull request. Use `--skip-git` to run piece only (no git operations). Implemented in `src/features/pipeline/`. @@ -48,36 +64,36 @@ TAKT (TAKT Agent Koordination Topology) is a multi-agent orchestration system fo | `--pipeline` | Enable pipeline (non-interactive) mode — required for CI/automation | | `-t, --task ` | Task content (as alternative to GitHub issue) | | `-i, --issue ` | GitHub issue number (equivalent to `#N` in interactive mode) | -| `-w, --piece ` | Piece name or path to piece YAML file (v0.3.8+) | +| `-w, --piece ` | Piece name or path to piece YAML file | | `-b, --branch ` | Branch name (auto-generated if omitted) | | `--auto-pr` | Create PR after execution (interactive: skip confirmation, pipeline: enable PR) | | `--skip-git` | Skip branch creation, commit, and push (pipeline mode, piece-only) | | `--repo ` | Repository for PR creation | | `--create-worktree ` | Skip worktree confirmation prompt | -| `-q, --quiet` | **Minimal output mode: suppress AI output (for CI)** (v0.3.8+) | -| `--provider ` | Override agent provider (claude\|codex\|mock) (v0.3.8+) | -| `--model ` | Override agent model (v0.3.8+) | -| `--config ` | Path to global config file (default: `~/.takt/config.yaml`) (v0.3.8+) | +| `-q, --quiet` | Minimal output mode: suppress AI output (for CI) | +| `--provider ` | Override agent provider (claude\|codex\|opencode\|mock) | +| `--model ` | Override agent model | +| `--config ` | Path to global config file (default: `~/.takt/config.yaml`) | ## Architecture ### Core Flow ``` -CLI (cli.ts) - → Slash commands or executeTask() - → PieceEngine (piece/engine.ts) - → Per movement: 3-phase execution - Phase 1: runAgent() → main work - Phase 2: runReportPhase() → report output (if output_contracts defined) - Phase 3: runStatusJudgmentPhase() → status tag output (if tag-based rules) - → detectMatchedRule() → rule evaluation → determineNextStep() - → Parallel movements: Promise.all() for sub-movements, aggregate evaluation +CLI (cli.ts → routing.ts) + → Interactive mode / Pipeline mode / Direct task execution + → PieceEngine (piece/engine/PieceEngine.ts) + → Per movement, delegates to one of 4 runners: + MovementExecutor — Normal movements (3-phase execution) + ParallelRunner — Parallel sub-movements via Promise.allSettled() + ArpeggioRunner — Data-driven batch processing (CSV → template → LLM) + TeamLeaderRunner — Dynamic task decomposition into sub-parts + → detectMatchedRule() → rule evaluation → determineNextMovementByRules() ``` ### Three-Phase Movement Execution -Each movement executes in up to 3 phases (session is resumed across phases): +Each normal movement executes in up to 3 phases (session is resumed across phases): | Phase | Purpose | Tools | When | |-------|---------|-------|------| @@ -85,7 +101,7 @@ Each movement executes in up to 3 phases (session is resumed across phases): | Phase 2 | Report output | Write only | When `output_contracts` is defined | | Phase 3 | Status judgment | None (judgment only) | When movement has tag-based rules | -Phase 2/3 are implemented in `src/core/piece/engine/phase-runner.ts`. The session is resumed so the agent retains context from Phase 1. +Phase 2/3 are implemented in `src/core/piece/phase-runner.ts`. The session is resumed so the agent retains context from Phase 1. ### Rule Evaluation (5-Stage Fallback) @@ -97,35 +113,48 @@ After movement execution, rules are evaluated to determine the next movement. Ev 4. **AI judge (ai() only)** - AI evaluates `ai("condition text")` rules 5. **AI judge fallback** - AI evaluates ALL conditions as final resort -Implemented in `src/core/piece/evaluation/RuleEvaluator.ts`. The matched method is tracked as `RuleMatchMethod` type. +Implemented in `src/core/piece/evaluation/RuleEvaluator.ts`. The matched method is tracked as `RuleMatchMethod` type (`aggregate`, `auto_select`, `structured_output`, `phase3_tag`, `phase1_tag`, `ai_judge`, `ai_judge_fallback`). ### Key Components **PieceEngine** (`src/core/piece/engine/PieceEngine.ts`) - State machine that orchestrates agent execution via EventEmitter - Manages movement transitions based on rule evaluation results -- Emits events: `step:start`, `step:complete`, `step:blocked`, `step:loop_detected`, `piece:complete`, `piece:abort`, `iteration:limit` -- Supports loop detection (`LoopDetector`) and iteration limits +- Emits events: `movement:start`, `movement:complete`, `movement:blocked`, `movement:report`, `movement:user_input`, `movement:loop_detected`, `movement:cycle_detected`, `phase:start`, `phase:complete`, `piece:complete`, `piece:abort`, `iteration:limit` +- Supports loop detection (`LoopDetector`), cycle detection (`CycleDetector`), and iteration limits - Maintains agent sessions per movement for conversation continuity -- Delegates to `StepExecutor` (normal steps) and `ParallelRunner` (parallel steps) +- Delegates to `MovementExecutor` (normal), `ParallelRunner` (parallel), `ArpeggioRunner` (data-driven batch), and `TeamLeaderRunner` (task decomposition) -**StepExecutor** (`src/core/piece/engine/StepExecutor.ts`) +**MovementExecutor** (`src/core/piece/engine/MovementExecutor.ts`) - Executes a single piece movement through the 3-phase model - Phase 1: Main agent execution (with tools) - Phase 2: Report output (Write-only, optional) - Phase 3: Status judgment (no tools, optional) - Builds instructions via `InstructionBuilder`, detects matched rules via `RuleEvaluator` +- Writes facet snapshots (knowledge/policy) per movement iteration + +**ArpeggioRunner** (`src/core/piece/engine/ArpeggioRunner.ts`) +- Data-driven batch processing: reads data from a source (e.g., CSV), expands templates per batch, calls LLM for each batch with concurrency control +- Supports retry logic with configurable `maxRetries` and `retryDelayMs` +- Merge strategies: `concat` (default, join with separator) or `custom` (inline JS or file-based) +- Optional output file writing via `outputPath` + +**TeamLeaderRunner** (`src/core/piece/engine/TeamLeaderRunner.ts`) +- Decomposes a task into sub-parts via AI (`decomposeTask()`), then executes each part as a sub-agent +- Uses `PartDefinition` schema (id, title, instruction, optional timeoutMs) for decomposed tasks +- Configured via `TeamLeaderConfig` (maxParts ≤3, separate persona/tools/permissions for parts) +- Aggregates sub-part results and evaluates parent rules **ParallelRunner** (`src/core/piece/engine/ParallelRunner.ts`) -- Executes parallel sub-movements concurrently via `Promise.all()` -- Aggregates sub-movement results for parent rule evaluation -- Supports `all()` / `any()` aggregate conditions +- Executes parallel sub-movements concurrently via `Promise.allSettled()` +- Uses `ParallelLogger` to prefix sub-movement output for readable interleaved display +- Aggregates sub-movement results for parent rule evaluation with `all()` / `any()` conditions **RuleEvaluator** (`src/core/piece/evaluation/RuleEvaluator.ts`) - 5-stage fallback evaluation: aggregate → Phase 3 tag → Phase 1 tag → ai() judge → all-conditions AI judge -- Returns `RuleMatch` with index and detection method (`aggregate`, `phase3_tag`, `phase1_tag`, `ai_judge`, `ai_fallback`) +- Returns `RuleMatch` with index and detection method - Fail-fast: throws if rules exist but no rule matched -- **v0.3.8+:** Tag detection now uses **last match** instead of first match when multiple `[STEP:N]` tags appear in output +- Tag detection uses **last match** when multiple `[STEP:N]` tags appear in output **Instruction Builder** (`src/core/piece/instruction/InstructionBuilder.ts`) - Auto-injects standard sections into every instruction (no need for `{task}` or `{previous_response}` placeholders in templates): @@ -141,59 +170,82 @@ Implemented in `src/core/piece/evaluation/RuleEvaluator.ts`. The matched method **Agent Runner** (`src/agents/runner.ts`) - Resolves agent specs (name or path) to agent configurations -- **v0.3.8+:** Agent is optional — movements can execute with `instruction_template` only (no system prompt) -- Built-in agents with default tools: - - `coder`: Read/Glob/Grep/Edit/Write/Bash/WebSearch/WebFetch - - `architect`: Read/Glob/Grep/WebSearch/WebFetch - - `supervisor`: Read/Glob/Grep/Bash/WebSearch/WebFetch - - `planner`: Read/Glob/Grep/Bash/WebSearch/WebFetch +- Agent is optional — movements can execute with `instruction_template` only (no system prompt) +- 4-layer resolution for provider/model: options.provider → options.stepProvider → config.provider → agentConfig.provider - Custom agents via `.takt/agents.yaml` or prompt files (.md) - Inline system prompts: If agent file doesn't exist, the agent string is used as inline system prompt -**Provider Integration** (`src/infra/claude/`, `src/infra/codex/`) -- **Claude** - Uses `@anthropic-ai/claude-agent-sdk` +**Provider Integration** (`src/infra/providers/`) +- Unified `Provider` interface: `setup(AgentSetup) → ProviderAgent`, `ProviderAgent.call(prompt, options) → AgentResponse` +- **Claude** (`src/infra/claude/`) - Uses `@anthropic-ai/claude-agent-sdk` - `client.ts` - High-level API: `callClaude()`, `callClaudeCustom()`, `callClaudeAgent()`, `callClaudeSkill()` - `process.ts` - SDK wrapper with `ClaudeProcess` class - `executor.ts` - Query execution - `query-manager.ts` - Concurrent query tracking with query IDs -- **Codex** - Direct OpenAI SDK integration - - `CodexStreamHandler.ts` - Stream handling and tool execution +- **Codex** (`src/infra/codex/`) - Uses `@openai/codex-sdk` + - Retry logic with exponential backoff (3 attempts, 250ms base) + - Stream handling with idle timeout (10 minutes) +- **OpenCode** (`src/infra/opencode/`) - Uses `@opencode-ai/sdk/v2` + - Shared server pooling with `acquireClient()` / `releaseClient()` + - Client-side permission auto-reply + - Requires explicit `model` specification (no default) +- **Mock** (`src/infra/mock/`) - Deterministic responses for testing **Configuration** (`src/infra/config/`) -- `loaders/loader.ts` - Custom agent loading from `.takt/agents.yaml` -- `loaders/pieceParser.ts` - YAML parsing, movement/rule normalization with Zod validation -- `loaders/pieceResolver.ts` - **3-layer resolution with correct priority** (v0.3.8+: user → project → builtin) +- `loaders/pieceParser.ts` - YAML parsing, movement/rule normalization with Zod validation. Rule regex: `AI_CONDITION_REGEX = /^ai\("(.+)"\)$/`, `AGGREGATE_CONDITION_REGEX = /^(all|any)\((.+)\)$/` +- `loaders/pieceResolver.ts` - **3-layer resolution**: project `.takt/pieces/` → user `~/.takt/pieces/` → builtin `builtins/{lang}/pieces/`. Also supports repertoire packages `@{owner}/{repo}/{piece-name}` - `loaders/pieceCategories.ts` - Piece categorization and filtering - `loaders/agentLoader.ts` - Agent prompt file loading - `paths.ts` - Directory structure (`.takt/`, `~/.takt/`), session management -- `global/globalConfig.ts` - Global configuration (provider, model, trusted dirs, **quiet mode** v0.3.8+) +- `global/globalConfig.ts` - Global configuration (provider, model, language, quiet mode) - `project/projectConfig.ts` - Project-level configuration **Task Management** (`src/features/tasks/`) -- `execute/taskExecution.ts` - Main task execution orchestration -- `execute/pieceExecution.ts` - Piece execution wrapper +- `execute/taskExecution.ts` - Main task execution orchestration, worker pool for parallel tasks +- `execute/pieceExecution.ts` - Piece execution wrapper, analytics integration, NDJSON logging - `add/index.ts` - Interactive task addition via AI conversation -- `list/index.ts` - List task branches with merge/delete actions +- `list/index.ts` - List task branches with merge/delete/retry actions - `watch/index.ts` - Watch for task files and auto-execute +**Repertoire** (`src/features/repertoire/`) +- Package management for external facet/piece collections +- Install from GitHub: `github:{owner}/{repo}@{ref}` +- Config validation via `takt-repertoire.yaml` (path constraints, min_version semver check) +- Lock file for resolved dependencies +- Packages installed to `~/.takt/repertoire/@{owner}/{repo}/` + +**Analytics** (`src/features/analytics/`) +- Event types: `MovementResultEvent`, `ReviewFindingEvent`, `FixActionEvent`, `RebuttalEvent` +- NDJSON storage at `.takt/events/` +- Integrated into piece execution: movement results, review findings, fix actions + +**Catalog** (`src/features/catalog/`) +- Scans 3 layers (builtin → user → project) for available facets +- Shows override detection and source provenance + +**Faceted Prompting** (`src/faceted-prompting/`) +- Independent module (no TAKT dependencies) for composing prompts from facets +- `compose(facets, options)` → `ComposedPrompt` (systemPrompt + userMessage) +- Supports template rendering, context truncation, facet path resolution, scope references + **GitHub Integration** (`src/infra/github/`) -- `issue.ts` - Fetches issues via `gh` CLI, formats as task text with title/body/labels/comments -- `pr.ts` - Creates pull requests via `gh` CLI +- `issue.ts` - Fetches issues via `gh` CLI, formats as task text, supports `createIssue()` +- `pr.ts` - Creates pull requests via `gh` CLI, supports draft PRs and custom templates ### Data Flow 1. User provides task (text or `#N` issue reference) or slash command → CLI -2. CLI loads piece with **correct priority** (v0.3.8+): user `~/.takt/pieces/` → project `.takt/pieces/` → builtin `builtins/{lang}/pieces/` +2. CLI loads piece with **priority**: project `.takt/pieces/` → user `~/.takt/pieces/` → builtin `builtins/{lang}/pieces/` 3. PieceEngine starts at `initial_movement` -4. Each movement: `buildInstruction()` → Phase 1 (main) → Phase 2 (report) → Phase 3 (status) → `detectMatchedRule()` → `determineNextStep()` -5. Rule evaluation determines next movement name (v0.3.8+: uses **last match** when multiple `[STEP:N]` tags appear) +4. Each movement: delegate to appropriate runner → 3-phase execution → `detectMatchedRule()` → `determineNextMovementByRules()` +5. Rule evaluation determines next movement name (uses **last match** when multiple `[STEP:N]` tags appear) 6. Special transitions: `COMPLETE` ends piece successfully, `ABORT` ends with failure ## Directory Structure ``` ~/.takt/ # Global user config (created on first run) - config.yaml # Trusted dirs, default piece, log level, language + config.yaml # Language, provider, model, log level, etc. pieces/ # User piece YAML files (override builtins) facets/ # User facets personas/ # User persona prompt files (.md) @@ -202,14 +254,16 @@ Implemented in `src/core/piece/evaluation/RuleEvaluator.ts`. The matched method instructions/ # User instruction files output-contracts/ # User output contract files repertoire/ # Installed repertoire packages + @{owner}/{repo}/ # Per-package directory .takt/ # Project-level config config.yaml # Project configuration agents.yaml # Custom agent definitions facets/ # Project-level facets - tasks/ # Task files for /run-tasks - runs/ # Execution reports, logs, context + tasks/ # Task files for takt run + runs/ # Execution reports (runs/{slug}/reports/) logs/ # Session logs in NDJSON format (gitignored) + events/ # Analytics event files (NDJSON) builtins/ # Bundled defaults (builtin, read from dist/ at runtime) en/ # English @@ -220,7 +274,7 @@ builtins/ # Bundled defaults (builtin, read from dist/ at runtim skill/ # Claude Code skill files ``` -Builtin resources are embedded in the npm package (`builtins/`). User files in `~/.takt/` take priority. Use `/eject` to copy builtins to `~/.takt/` for customization. +Builtin resources are embedded in the npm package (`builtins/`). Project files in `.takt/` take highest priority, then user files in `~/.takt/`, then builtins. Use `takt eject` to copy builtins for customization. ## Piece YAML Schema @@ -229,6 +283,30 @@ name: piece-name description: Optional description max_movements: 10 initial_movement: plan # First movement to execute +interactive_mode: assistant # Default interactive mode (assistant|passthrough|quiet|persona) +answer_agent: agent-name # Route AskUserQuestion to this agent (optional) + +# Piece-level provider options (inherited by all movements unless overridden) +piece_config: + provider_options: + codex: { network_access: true } + opencode: { network_access: true } + claude: { sandbox: { allow_unsandboxed_commands: true } } + runtime: + prepare: [node, gradle, ./custom-script.sh] # Runtime environment preparation + +# Loop monitors (cycle detection between movements) +loop_monitors: + - cycle: [review, fix] # Movement names forming the cycle + threshold: 3 # Cycles before triggering judge + judge: + persona: supervisor + instruction_template: "Evaluate if the fix loop is making progress..." + rules: + - condition: "Progress is being made" + next: fix + - condition: "No progress" + next: ABORT # Section maps (key → file path relative to piece YAML directory) personas: @@ -248,13 +326,24 @@ movements: - name: movement-name persona: coder # Persona key (references section map) persona_name: coder # Display name (optional) + session: continue # Session continuity: continue (default) | refresh policy: coding # Policy key (single or array) knowledge: architecture # Knowledge key (single or array) instruction: plan # Instruction key (references section map) - provider: codex # claude|codex|opencode (optional) + provider: claude # claude|codex|opencode|mock (optional) model: opus # Model name (optional) edit: true # Whether movement can edit files - required_permission_mode: edit # Required minimum permission mode (optional) + required_permission_mode: edit # Required minimum permission mode (optional) + quality_gates: # AI directives for completion (optional) + - "All tests pass" + - "No lint errors" + provider_options: # Per-provider options (optional) + codex: { network_access: true } + claude: { sandbox: { excluded_commands: [rm] } } + mcp_servers: # MCP server configuration (optional) + my-server: + command: npx + args: [-y, my-mcp-server] instruction_template: | Custom instructions for this movement. {task}, {previous_response} are auto-injected if not present as placeholders. @@ -263,6 +352,7 @@ movements: report: - name: 01-plan.md # Report file name format: plan # References report_formats map + order: "Write the plan to {report_dir}/01-plan.md" # Instruction prepend rules: - condition: "Human-readable condition" next: next-movement-name @@ -270,6 +360,7 @@ movements: next: other-movement - condition: blocked next: ABORT + requires_user_input: true # Wait for user input (interactive only) # Parallel movement (sub-movements execute concurrently) - name: reviewers @@ -280,7 +371,7 @@ movements: knowledge: architecture edit: false rules: - - condition: approved # next is optional for sub-movements + - condition: approved - condition: needs_fix instruction: review-arch - name: security-review @@ -290,18 +381,51 @@ movements: - condition: approved - condition: needs_fix instruction: review-security - rules: # Parent rules use aggregate conditions + rules: - condition: all("approved") next: supervise - condition: any("needs_fix") next: fix + + # Arpeggio movement (data-driven batch processing) + - name: batch-process + persona: coder + arpeggio: + source: csv + source_path: ./data/items.csv # Relative to piece YAML + batch_size: 5 # Rows per batch (default: 1) + concurrency: 3 # Concurrent LLM calls (default: 1) + template: ./templates/process.txt # Prompt template file + max_retries: 2 # Retry attempts per batch (default: 2) + retry_delay_ms: 1000 # Delay between retries (default: 1000) + merge: + strategy: concat # concat (default) | custom + separator: "\n---\n" # For concat strategy + output_path: ./output/result.txt # Write merged results (optional) + rules: + - condition: "Processing complete" + next: COMPLETE + + # Team leader movement (dynamic task decomposition) + - name: implement + team_leader: + max_parts: 3 # Max parallel parts (1-3, default: 3) + timeout_ms: 600000 # Per-part timeout (default: 600s) + part_persona: coder # Persona for part agents + part_edit: true # Edit permission for parts + part_permission_mode: edit # Permission mode for parts + part_allowed_tools: [Read, Glob, Grep, Edit, Write, Bash] + instruction_template: | + Decompose this task into independent subtasks. + rules: + - condition: "All parts completed" + next: review ``` -Key points about parallel movements: -- Sub-movement `rules` define possible outcomes but `next` is ignored (parent handles routing) -- Parent `rules` use `all("X")`/`any("X")` to aggregate sub-movement results -- `all("X")`: true if ALL sub-movements matched condition X -- `any("X")`: true if ANY sub-movements matched condition X +Key points about movement types (mutually exclusive: `parallel`, `arpeggio`, `team_leader`): +- **Parallel**: Sub-movement `rules` define possible outcomes but `next` is ignored (parent handles routing). Parent uses `all("X")`/`any("X")` to aggregate. +- **Arpeggio**: Template placeholders: `{line:N}`, `{col:N:name}`, `{batch_index}`, `{total_batches}`. Merge custom strategy supports inline JS or file. +- **Team leader**: AI generates `PartDefinition[]` (JSON in ```json block), each part executed as sub-movement. ### Rule Condition Types @@ -351,8 +475,6 @@ show_others_category: true others_category_name: "Other Pieces" ``` -Implemented in `src/infra/config/loaders/pieceCategories.ts`. - ### Model Resolution Model is resolved in the following priority order: @@ -362,11 +484,19 @@ Model is resolved in the following priority order: 3. **Global config `model`** - Default model in `~/.takt/config.yaml` 4. **Provider default** - Falls back to provider's default (Claude: sonnet, Codex: gpt-5.2-codex) -Example `~/.takt/config.yaml`: -```yaml -provider: claude -model: opus # Default model for all movements (unless overridden) -``` +### Loop Detection + +Two distinct mechanisms: + +**LoopDetector** (`src/core/piece/engine/loop-detector.ts`): +- Detects consecutive same-movement executions (simple counter) +- Configurable: `maxConsecutiveSameStep` (default: 10), `action` (`warn` | `abort` | `ignore`) + +**CycleDetector** (`src/core/piece/engine/cycle-detector.ts`): +- Detects cyclic patterns between movements (e.g., review → fix → review → fix) +- Configured via `loop_monitors` in piece config (cycle pattern + threshold + judge) +- When threshold reached, triggers a synthetic judge movement for decision-making +- Resets after judge intervention to prevent immediate re-triggering ## NDJSON Session Logging @@ -375,8 +505,8 @@ Session logs use NDJSON (`.jsonl`) format for real-time append-only writes. Reco | Record | Description | |--------|-------------| | `piece_start` | Piece initialization with task, piece name | -| `step_start` | Step execution start | -| `step_complete` | Step result with status, content, matched rule info | +| `movement_start` | Movement execution start | +| `movement_complete` | Movement result with status, content, matched rule info | | `piece_complete` | Successful completion | | `piece_abort` | Abort with reason | @@ -386,8 +516,8 @@ Files: `.takt/logs/{sessionId}.jsonl`, with `latest.json` pointer. Legacy `.json - ESM modules with `.js` extensions in imports - Strict TypeScript with `noUncheckedIndexedAccess` -- Zod schemas for runtime validation (`src/core/models/schemas.ts`) -- Uses `@anthropic-ai/claude-agent-sdk` for Claude integration +- Zod v4 schemas for runtime validation (`src/core/models/schemas.ts`) +- Uses `@anthropic-ai/claude-agent-sdk` for Claude, `@openai/codex-sdk` for Codex, `@opencode-ai/sdk` for OpenCode ## Design Principles @@ -424,12 +554,14 @@ Key rules: **Separation of concerns in piece engine:** - `PieceEngine` - Orchestration, state management, event emission -- `StepExecutor` - Single movement execution (3-phase model) +- `MovementExecutor` - Single movement execution (3-phase model) - `ParallelRunner` - Parallel movement execution +- `ArpeggioRunner` - Data-driven batch processing +- `TeamLeaderRunner` - Dynamic task decomposition - `RuleEvaluator` - Rule matching and evaluation - `InstructionBuilder` - Instruction template processing -**Session management:** Agent sessions are stored per-cwd in `~/.claude/projects/{encoded-path}/` (Claude Code) or in-memory (Codex). Sessions are resumed across phases (Phase 1 → Phase 2 → Phase 3) to maintain context. When `cwd !== projectCwd` (worktree/clone execution), session resume is skipped to avoid cross-directory contamination. +**Session management:** Agent sessions are stored per-cwd in `~/.claude/projects/{encoded-path}/` (Claude) or in-memory (Codex/OpenCode). Sessions are resumed across phases (Phase 1 → Phase 2 → Phase 3) to maintain context. Session key format: `{persona}:{provider}` to prevent cross-provider contamination. When `cwd !== projectCwd` (worktree/clone execution), session resume is skipped. ## Isolated Execution (Shared Clone) @@ -448,14 +580,25 @@ Key constraints: ## Error Propagation -`ClaudeResult` (from SDK) has an `error` field. This must be propagated through `AgentResponse.error` → session log history → console output. Without this, SDK failures (exit code 1, rate limits, auth errors) appear as empty `blocked` status with no diagnostic info. +Provider errors must be propagated through `AgentResponse.error` → session log history → console output. Without this, SDK failures (exit code 1, rate limits, auth errors) appear as empty `blocked` status with no diagnostic info. **Error handling flow:** -1. Provider error (Claude SDK / Codex) → `AgentResponse.error` -2. `StepExecutor` captures error → `PieceEngine` emits `step:complete` with error +1. Provider error (Claude SDK / Codex / OpenCode) → `AgentResponse.error` +2. `MovementExecutor` captures error → `PieceEngine` emits `phase:complete` with error 3. Error logged to session log (`.takt/logs/{sessionId}.jsonl`) 4. Console output shows error details -5. Piece transitions to `ABORT` step if error is unrecoverable +5. Piece transitions to `ABORT` movement if error is unrecoverable + +## Runtime Environment + +Piece-level runtime preparation via `runtime.prepare` in piece config or `~/.takt/config.yaml`: + +- **Presets**: `gradle` (sets `GRADLE_USER_HOME`, `JAVA_TOOL_OPTIONS`), `node` (sets `npm_config_cache`) +- **Custom scripts**: Arbitrary shell scripts, resolved relative to cwd or as absolute paths +- Environment injected: `TMPDIR`, `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, `XDG_STATE_HOME`, `CI=true` +- Creates `.takt/.runtime/` directory structure with `env.sh` for sourcing + +Implemented in `src/core/runtime/runtime-environment.ts`. ## Debugging @@ -474,13 +617,15 @@ Debug logs are written to `.takt/logs/debug.log` (ndjson format). Log levels: `d ## Testing Notes -- Vitest for testing framework -- Tests use file system fixtures in `__tests__/` subdirectories -- Mock pieces and agent configs for integration tests +- Vitest for testing framework (single-thread mode, 15s timeout, 5s teardown timeout) +- Unit tests: `src/__tests__/*.test.ts` +- E2E mock tests: configured via `vitest.config.e2e.mock.ts` (240s timeout, forceExit) +- E2E provider tests: configured via `vitest.config.e2e.provider.ts` - Test single files: `npx vitest run src/__tests__/filename.test.ts` - Pattern matching: `npx vitest run -t "test pattern"` -- Integration tests: Tests with `it-` prefix are integration tests that simulate full piece execution -- Engine tests: Tests with `engine-` prefix test specific PieceEngine scenarios (happy path, error handling, parallel execution, etc.) +- Integration tests: Tests with `it-` prefix simulate full piece execution +- Engine tests: Tests with `engine-` prefix test PieceEngine scenarios (happy path, error handling, parallel, arpeggio, team-leader, etc.) +- Environment variables cleared in test setup: `TAKT_CONFIG_DIR`, `TAKT_NOTIFY_WEBHOOK` ## Important Implementation Notes @@ -500,31 +645,24 @@ Debug logs are written to `.takt/logs/debug.log` (ndjson format). Log levels: `d **Session continuity across phases:** - Agent sessions persist across Phase 1 → Phase 2 → Phase 3 for context continuity - Session ID is passed via `resumeFrom` in `RunAgentOptions` +- Session key: `{persona}:{provider}` prevents cross-provider session contamination - Sessions are stored per-cwd, so worktree executions create new sessions - Use `takt clear` to reset all agent sessions -**Worktree execution gotchas:** -- `git clone --shared` creates independent `.git` directory (not `git worktree`) -- Clone cwd ≠ project cwd: agents work in clone, reports write to clone, logs write to project -- Session resume is skipped when `cwd !== projectCwd` to avoid cross-directory contamination -- Reports write to `cwd/.takt/runs/{slug}/reports/` (clone) to prevent agents from discovering the main repository path via instruction -- Clones are ephemeral: created → task runs → auto-commit + push → deleted -- Use `takt list` to manage task branches after clone deletion - **Rule evaluation quirks:** - Tag-based rules match by array index (0-based), not by exact condition text -- **v0.3.8+:** When multiple `[STEP:N]` tags appear in output, **last match wins** (not first) -- `ai()` conditions are evaluated by Claude/Codex, not by string matching +- When multiple `[STEP:N]` tags appear in output, **last match wins** (not first) +- `ai()` conditions are evaluated by the provider, not by string matching - Aggregate conditions (`all()`, `any()`) only work in parallel parent movements - Fail-fast: if rules exist but no rule matches, piece aborts - Interactive-only rules are skipped in pipeline mode (`rule.interactiveOnly === true`) **Provider-specific behavior:** -- Claude: Uses session files in `~/.claude/projects/` -- Codex: In-memory sessions +- Claude: Uses session files in `~/.claude/projects/`, supports aliases: `opus`, `sonnet`, `haiku` +- Codex: In-memory sessions, retry with exponential backoff (3 attempts) +- OpenCode: Shared server pooling, requires explicit `model`, client-side permission auto-reply +- Mock: Deterministic responses, scenario queue support - Model names are passed directly to provider (no alias resolution in TAKT) -- Claude supports aliases: `opus`, `sonnet`, `haiku` -- Codex defaults to `codex` if model not specified **Permission modes (provider-independent values):** - `readonly`: Read-only access, no file modifications (Claude: `default`, Codex: `read-only`)