lint対応
This commit is contained in:
parent
887365c4eb
commit
be2f892ef5
36
CHANGELOG.md
36
CHANGELOG.md
@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## [0.4.0] - 2026-02-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Externalized prompt system: all internal prompts moved to versioned, translatable files (`src/shared/prompts/en/`, `src/shared/prompts/ja/`)
|
||||||
|
- i18n label system: UI labels extracted to separate YAML files (`labels_en.yaml`, `labels_ja.yaml`) with `src/shared/i18n/` module
|
||||||
|
- Prompt preview functionality (`src/features/prompt/preview.ts`)
|
||||||
|
- Phase system injection into agents for improved workflow phase awareness
|
||||||
|
- Enhanced debug capabilities with new debug log viewer (`tools/debug-log-viewer.html`)
|
||||||
|
- Comprehensive test coverage:
|
||||||
|
- i18n system tests (`i18n.test.ts`)
|
||||||
|
- Prompt system tests (`prompts.test.ts`)
|
||||||
|
- Session management tests (`session.test.ts`)
|
||||||
|
- Worktree integration tests (`it-worktree-delete.test.ts`, `it-worktree-sessions.test.ts`)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **BREAKING:** Internal terminology renamed: `WorkflowStep` → `WorkflowMovement`, `StepExecutor` → `MovementExecutor`, `ParallelSubStepRawSchema` → `ParallelSubMovementRawSchema`, `WorkflowStepRawSchema` → `WorkflowMovementRawSchema`
|
||||||
|
- **BREAKING:** Removed unnecessary backward compatibility code
|
||||||
|
- **BREAKING:** Disabled interactive prompt override feature
|
||||||
|
- Workflow resource directory renamed: `resources/global/*/workflows/` → `resources/global/*/pieces/`
|
||||||
|
- Prompts restructured for better readability and maintainability
|
||||||
|
- Removed unnecessary task requirement summarization from conversation flow
|
||||||
|
- Suppressed unnecessary report output during workflow execution
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `takt worktree` bug fix for worktree operations
|
||||||
|
|
||||||
|
### Internal
|
||||||
|
|
||||||
|
- Extracted prompt management into `src/shared/prompts/index.ts` with language-aware file loading
|
||||||
|
- Created `src/shared/i18n/index.ts` for centralized label management
|
||||||
|
- Enhanced `tools/jsonl-viewer.html` with additional features
|
||||||
|
- Major refactoring across 162 files (~5,800 insertions, ~2,900 deletions)
|
||||||
|
|
||||||
## [0.3.9] - 2026-02-03
|
## [0.3.9] - 2026-02-03
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
40
README.md
40
README.md
@ -266,9 +266,9 @@ TAKT uses YAML-based workflow definitions and rule-based routing. Builtin workfl
|
|||||||
```yaml
|
```yaml
|
||||||
name: default
|
name: default
|
||||||
max_iterations: 10
|
max_iterations: 10
|
||||||
initial_step: plan
|
initial_movement: plan
|
||||||
|
|
||||||
steps:
|
movements:
|
||||||
- name: plan
|
- name: plan
|
||||||
agent: ../agents/default/planner.md
|
agent: ../agents/default/planner.md
|
||||||
model: opus
|
model: opus
|
||||||
@ -303,9 +303,9 @@ steps:
|
|||||||
Review the implementation from architecture and code quality perspectives.
|
Review the implementation from architecture and code quality perspectives.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Agentless Steps
|
### Agentless Movements
|
||||||
|
|
||||||
The `agent` field is optional. When omitted, the step executes using only the `instruction_template` without a system prompt. This is useful for simple tasks that don't require agent behavior customization.
|
The `agent` field is optional. When omitted, the movement executes using only the `instruction_template` without a system prompt. This is useful for simple tasks that don't require agent behavior customization.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: summarize
|
- name: summarize
|
||||||
@ -328,9 +328,9 @@ You can also write an inline system prompt as the `agent` value (if the specifie
|
|||||||
Review code quality.
|
Review code quality.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Parallel Steps
|
### Parallel Movements
|
||||||
|
|
||||||
Execute sub-steps in parallel within a step and evaluate with aggregate conditions:
|
Execute sub-movements in parallel within a movement and evaluate with aggregate conditions:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: reviewers
|
- name: reviewers
|
||||||
@ -356,15 +356,15 @@ Execute sub-steps in parallel within a step and evaluate with aggregate conditio
|
|||||||
next: fix
|
next: fix
|
||||||
```
|
```
|
||||||
|
|
||||||
- `all("X")`: true if ALL sub-steps matched condition X
|
- `all("X")`: true if ALL sub-movements matched condition X
|
||||||
- `any("X")`: true if ANY sub-step matched condition X
|
- `any("X")`: true if ANY sub-movement matched condition X
|
||||||
- Sub-step `rules` define possible outcomes, but `next` is optional (parent controls transition)
|
- Sub-movement `rules` define possible outcomes, but `next` is optional (parent controls transition)
|
||||||
|
|
||||||
### Rule Condition Types
|
### Rule Condition Types
|
||||||
|
|
||||||
| Type | Syntax | Description |
|
| Type | Syntax | Description |
|
||||||
|------|--------|-------------|
|
|------|--------|-------------|
|
||||||
| Tag-based | `"condition text"` | Agent outputs `[STEP:N]` tag, matched by index |
|
| Tag-based | `"condition text"` | Agent outputs `[MOVEMENTNAME:N]` tag, matched by index |
|
||||||
| AI judge | `ai("condition text")` | AI evaluates condition against agent output |
|
| AI judge | `ai("condition text")` | AI evaluates condition against agent output |
|
||||||
| Aggregate | `all("X")` / `any("X")` | Aggregates parallel sub-step matched conditions |
|
| Aggregate | `all("X")` / `any("X")` | Aggregates parallel sub-step matched conditions |
|
||||||
|
|
||||||
@ -579,9 +579,9 @@ takt eject default
|
|||||||
name: my-workflow
|
name: my-workflow
|
||||||
description: Custom workflow
|
description: Custom workflow
|
||||||
max_iterations: 5
|
max_iterations: 5
|
||||||
initial_step: analyze
|
initial_movement: analyze
|
||||||
|
|
||||||
steps:
|
movements:
|
||||||
- name: analyze
|
- name: analyze
|
||||||
agent: ~/.takt/agents/my-agents/analyzer.md
|
agent: ~/.takt/agents/my-agents/analyzer.md
|
||||||
edit: false
|
edit: false
|
||||||
@ -629,7 +629,7 @@ Variables available in `instruction_template`:
|
|||||||
| `{task}` | Original user request (auto-injected if not in template) |
|
| `{task}` | Original user request (auto-injected if not in template) |
|
||||||
| `{iteration}` | Workflow-wide turn count (total steps executed) |
|
| `{iteration}` | Workflow-wide turn count (total steps executed) |
|
||||||
| `{max_iterations}` | Maximum iteration count |
|
| `{max_iterations}` | Maximum iteration count |
|
||||||
| `{step_iteration}` | Per-step iteration count (times this step has been executed) |
|
| `{movement_iteration}` | Per-movement iteration count (times this movement has been executed) |
|
||||||
| `{previous_response}` | Output from previous step (auto-injected if not in template) |
|
| `{previous_response}` | Output from previous step (auto-injected if not in template) |
|
||||||
| `{user_inputs}` | Additional user inputs during workflow (auto-injected if not in template) |
|
| `{user_inputs}` | Additional user inputs during workflow (auto-injected if not in template) |
|
||||||
| `{report_dir}` | Report directory path (e.g., `.takt/reports/20250126-143052-task-summary`) |
|
| `{report_dir}` | Report directory path (e.g., `.takt/reports/20250126-143052-task-summary`) |
|
||||||
@ -637,7 +637,7 @@ Variables available in `instruction_template`:
|
|||||||
|
|
||||||
### Workflow Design
|
### Workflow Design
|
||||||
|
|
||||||
Elements needed for each workflow step:
|
Elements needed for each workflow movement:
|
||||||
|
|
||||||
**1. Agent** - Markdown file containing system prompt:
|
**1. Agent** - Markdown file containing system prompt:
|
||||||
|
|
||||||
@ -646,7 +646,7 @@ agent: ../agents/default/coder.md # Path to agent prompt file
|
|||||||
agent_name: coder # Display name (optional)
|
agent_name: coder # Display name (optional)
|
||||||
```
|
```
|
||||||
|
|
||||||
**2. Rules** - Define routing from step to next step. The instruction builder auto-injects status output rules, so agents know which tags to output:
|
**2. Rules** - Define routing from movement to next movement. The instruction builder auto-injects status output rules, so agents know which tags to output:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rules:
|
rules:
|
||||||
@ -658,15 +658,15 @@ rules:
|
|||||||
|
|
||||||
Special `next` values: `COMPLETE` (success), `ABORT` (failure)
|
Special `next` values: `COMPLETE` (success), `ABORT` (failure)
|
||||||
|
|
||||||
**3. Step Options:**
|
**3. Movement Options:**
|
||||||
|
|
||||||
| Option | Default | Description |
|
| Option | Default | Description |
|
||||||
|--------|---------|-------------|
|
|--------|---------|-------------|
|
||||||
| `edit` | - | Whether step can edit project files (`true`/`false`) |
|
| `edit` | - | Whether movement can edit project files (`true`/`false`) |
|
||||||
| `pass_previous_response` | `true` | Pass previous step output to `{previous_response}` |
|
| `pass_previous_response` | `true` | Pass previous movement output to `{previous_response}` |
|
||||||
| `allowed_tools` | - | List of tools agent can use (Read, Glob, Grep, Edit, Write, Bash, etc.) |
|
| `allowed_tools` | - | List of tools agent can use (Read, Glob, Grep, Edit, Write, Bash, etc.) |
|
||||||
| `provider` | - | Override provider for this step (`claude` or `codex`) |
|
| `provider` | - | Override provider for this movement (`claude` or `codex`) |
|
||||||
| `model` | - | Override model for this step |
|
| `model` | - | Override model for this movement |
|
||||||
| `permission_mode` | - | Permission mode: `readonly`, `edit`, `full` (provider-independent) |
|
| `permission_mode` | - | Permission mode: `readonly`, `edit`, `full` (provider-independent) |
|
||||||
| `report` | - | Auto-generated report file settings (name, format) |
|
| `report` | - | Auto-generated report file settings (name, format) |
|
||||||
|
|
||||||
|
|||||||
@ -55,7 +55,10 @@ export class ParallelRunner {
|
|||||||
maxIterations: number,
|
maxIterations: number,
|
||||||
updateAgentSession: (agent: string, sessionId: string | undefined) => void,
|
updateAgentSession: (agent: string, sessionId: string | undefined) => void,
|
||||||
): Promise<{ response: AgentResponse; instruction: string }> {
|
): Promise<{ response: AgentResponse; instruction: string }> {
|
||||||
const subMovements = step.parallel!;
|
if (!step.parallel) {
|
||||||
|
throw new Error(`Movement "${step.name}" has no parallel sub-movements`);
|
||||||
|
}
|
||||||
|
const subMovements = step.parallel;
|
||||||
const movementIteration = incrementMovementIteration(state, step.name);
|
const movementIteration = incrementMovementIteration(state, step.name);
|
||||||
log.debug('Running parallel movement', {
|
log.debug('Running parallel movement', {
|
||||||
movement: step.name,
|
movement: step.name,
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export class ParallelLogger {
|
|||||||
* Format: `\x1b[COLORm[name]\x1b[0m` + padding spaces
|
* Format: `\x1b[COLORm[name]\x1b[0m` + padding spaces
|
||||||
*/
|
*/
|
||||||
buildPrefix(name: string, index: number): string {
|
buildPrefix(name: string, index: number): string {
|
||||||
const color = COLORS[index % COLORS.length]!;
|
const color = COLORS[index % COLORS.length];
|
||||||
const padding = ' '.repeat(this.maxNameLength - name.length);
|
const padding = ' '.repeat(this.maxNameLength - name.length);
|
||||||
return `${color}[${name}]${RESET}${padding} `;
|
return `${color}[${name}]${RESET}${padding} `;
|
||||||
}
|
}
|
||||||
@ -99,7 +99,8 @@ export class ParallelLogger {
|
|||||||
const parts = combined.split('\n');
|
const parts = combined.split('\n');
|
||||||
|
|
||||||
// Last part is incomplete (no trailing newline) — keep in buffer
|
// Last part is incomplete (no trailing newline) — keep in buffer
|
||||||
this.lineBuffers.set(name, parts.pop()!);
|
const remainder = parts.pop() ?? '';
|
||||||
|
this.lineBuffers.set(name, remainder);
|
||||||
|
|
||||||
// Output all complete lines
|
// Output all complete lines
|
||||||
for (const line of parts) {
|
for (const line of parts) {
|
||||||
|
|||||||
@ -38,7 +38,8 @@ export class AggregateEvaluator {
|
|||||||
if (!this.step.rules || !this.step.parallel || this.step.parallel.length === 0) return -1;
|
if (!this.step.rules || !this.step.parallel || this.step.parallel.length === 0) return -1;
|
||||||
|
|
||||||
for (let i = 0; i < this.step.rules.length; i++) {
|
for (let i = 0; i < this.step.rules.length; i++) {
|
||||||
const rule = this.step.rules[i]!;
|
const rule = this.step.rules[i];
|
||||||
|
if (!rule) continue;
|
||||||
if (!rule.isAggregateCondition || !rule.aggregateType || !rule.aggregateConditionText) {
|
if (!rule.isAggregateCondition || !rule.aggregateType || !rule.aggregateConditionText) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,7 +115,8 @@ export class RuleEvaluator {
|
|||||||
|
|
||||||
const aiConditions: { index: number; text: string }[] = [];
|
const aiConditions: { index: number; text: string }[] = [];
|
||||||
for (let i = 0; i < this.step.rules.length; i++) {
|
for (let i = 0; i < this.step.rules.length; i++) {
|
||||||
const rule = this.step.rules[i]!;
|
const rule = this.step.rules[i];
|
||||||
|
if (!rule) continue;
|
||||||
if (rule.interactiveOnly && this.ctx.interactive !== true) {
|
if (rule.interactiveOnly && this.ctx.interactive !== true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -135,7 +136,8 @@ export class RuleEvaluator {
|
|||||||
const judgeResult = await this.ctx.callAiJudge(agentOutput, judgeConditions, { cwd: this.ctx.cwd });
|
const judgeResult = await this.ctx.callAiJudge(agentOutput, judgeConditions, { cwd: this.ctx.cwd });
|
||||||
|
|
||||||
if (judgeResult >= 0 && judgeResult < aiConditions.length) {
|
if (judgeResult >= 0 && judgeResult < aiConditions.length) {
|
||||||
const matched = aiConditions[judgeResult]!;
|
const matched = aiConditions[judgeResult];
|
||||||
|
if (!matched) return -1;
|
||||||
log.debug('AI judge matched condition', {
|
log.debug('AI judge matched condition', {
|
||||||
movement: this.step.name,
|
movement: this.step.name,
|
||||||
judgeResult,
|
judgeResult,
|
||||||
@ -172,7 +174,7 @@ export class RuleEvaluator {
|
|||||||
log.debug('AI judge (fallback) matched condition', {
|
log.debug('AI judge (fallback) matched condition', {
|
||||||
movement: this.step.name,
|
movement: this.step.name,
|
||||||
ruleIndex: judgeResult,
|
ruleIndex: judgeResult,
|
||||||
condition: conditions[judgeResult]!.text,
|
condition: conditions[judgeResult]?.text,
|
||||||
});
|
});
|
||||||
return judgeResult;
|
return judgeResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,8 +49,8 @@ export class InstructionBuilder {
|
|||||||
const hasReport = !!(this.step.report && this.context.reportDir);
|
const hasReport = !!(this.step.report && this.context.reportDir);
|
||||||
let reportInfo = '';
|
let reportInfo = '';
|
||||||
let phaseNote = '';
|
let phaseNote = '';
|
||||||
if (hasReport) {
|
if (hasReport && this.step.report && this.context.reportDir) {
|
||||||
reportInfo = renderReportContext(this.step.report!, this.context.reportDir!);
|
reportInfo = renderReportContext(this.step.report, this.context.reportDir);
|
||||||
phaseNote = language === 'ja'
|
phaseNote = language === 'ja'
|
||||||
? '**注意:** これはPhase 1(本来の作業)です。作業完了後、Phase 2で自動的にレポートを生成します。'
|
? '**注意:** これはPhase 1(本来の作業)です。作業完了後、Phase 2で自動的にレポートを生成します。'
|
||||||
: '**Note:** This is Phase 1 (main work). After you complete your work, Phase 2 will automatically generate the report based on your findings.';
|
: '**Note:** This is Phase 1 (main work). After you complete your work, Phase 2 will automatically generate the report based on your findings.';
|
||||||
@ -72,8 +72,8 @@ export class InstructionBuilder {
|
|||||||
this.context.previousOutput &&
|
this.context.previousOutput &&
|
||||||
!hasPreviousResponsePlaceholder
|
!hasPreviousResponsePlaceholder
|
||||||
);
|
);
|
||||||
const previousResponse = hasPreviousResponse
|
const previousResponse = hasPreviousResponse && this.context.previousOutput
|
||||||
? escapeTemplateChars(this.context.previousOutput!.content)
|
? escapeTemplateChars(this.context.previousOutput.content)
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// User Inputs
|
// User Inputs
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { stringify as stringifyYaml } from 'yaml';
|
|||||||
import { promptInput, confirm } from '../../../shared/prompt/index.js';
|
import { promptInput, confirm } from '../../../shared/prompt/index.js';
|
||||||
import { success, info } from '../../../shared/ui/index.js';
|
import { success, info } from '../../../shared/ui/index.js';
|
||||||
import { summarizeTaskName, type TaskFileData } from '../../../infra/task/index.js';
|
import { summarizeTaskName, type TaskFileData } from '../../../infra/task/index.js';
|
||||||
import { loadGlobalConfig, getWorkflowDescription } from '../../../infra/config/index.js';
|
import { getWorkflowDescription } from '../../../infra/config/index.js';
|
||||||
import { determineWorkflow } from '../execute/selectAndExecute.js';
|
import { determineWorkflow } from '../execute/selectAndExecute.js';
|
||||||
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
|
import { createLogger, getErrorMessage } from '../../../shared/utils/index.js';
|
||||||
import { isIssueReference, resolveIssueTask, parseIssueNumbers } from '../../../infra/github/index.js';
|
import { isIssueReference, resolveIssueTask, parseIssueNumbers } from '../../../infra/github/index.js';
|
||||||
|
|||||||
@ -106,7 +106,7 @@ export function buildCategoryWorkflowOptions(
|
|||||||
if (!categoryItem || categoryItem.type !== 'category') return null;
|
if (!categoryItem || categoryItem.type !== 'category') return null;
|
||||||
|
|
||||||
return categoryItem.workflows.map((qualifiedName) => {
|
return categoryItem.workflows.map((qualifiedName) => {
|
||||||
const displayName = qualifiedName.split('/').pop()!;
|
const displayName = qualifiedName.split('/').pop() ?? qualifiedName;
|
||||||
const isCurrent = qualifiedName === currentWorkflow;
|
const isCurrent = qualifiedName === currentWorkflow;
|
||||||
const label = isCurrent ? `${displayName} (current)` : displayName;
|
const label = isCurrent ? `${displayName} (current)` : displayName;
|
||||||
return { label, value: qualifiedName };
|
return { label, value: qualifiedName };
|
||||||
|
|||||||
@ -77,7 +77,8 @@ export function loadAgentPrompt(agent: CustomAgentConfig): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (agent.promptFile) {
|
if (agent.promptFile) {
|
||||||
const isValid = getAllowedAgentBases().some((base) => isPathSafe(base, agent.promptFile!));
|
const promptFile = agent.promptFile;
|
||||||
|
const isValid = getAllowedAgentBases().some((base) => isPathSafe(base, promptFile));
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
throw new Error(`Agent prompt file path is not allowed: ${agent.promptFile}`);
|
throw new Error(`Agent prompt file path is not allowed: ${agent.promptFile}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,7 +109,7 @@ function parseAggregateConditions(argsText: string): string[] {
|
|||||||
let match: RegExpExecArray | null;
|
let match: RegExpExecArray | null;
|
||||||
|
|
||||||
while ((match = regex.exec(argsText)) !== null) {
|
while ((match = regex.exec(argsText)) !== null) {
|
||||||
conditions.push(match[1]!);
|
if (match[1]) conditions.push(match[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conditions.length === 0) {
|
if (conditions.length === 0) {
|
||||||
@ -146,7 +146,9 @@ function normalizeRule(r: {
|
|||||||
const aggMatch = r.condition.match(AGGREGATE_CONDITION_REGEX);
|
const aggMatch = r.condition.match(AGGREGATE_CONDITION_REGEX);
|
||||||
if (aggMatch?.[1] && aggMatch[2]) {
|
if (aggMatch?.[1] && aggMatch[2]) {
|
||||||
const conditions = parseAggregateConditions(aggMatch[2]);
|
const conditions = parseAggregateConditions(aggMatch[2]);
|
||||||
const aggregateConditionText = conditions.length === 1 ? conditions[0]! : conditions;
|
// parseAggregateConditions guarantees conditions.length >= 1
|
||||||
|
const aggregateConditionText: string | string[] =
|
||||||
|
conditions.length === 1 ? (conditions[0] as string) : conditions;
|
||||||
return {
|
return {
|
||||||
condition: r.condition,
|
condition: r.condition,
|
||||||
next,
|
next,
|
||||||
|
|||||||
@ -33,7 +33,8 @@ export function renderMenu<T extends string>(
|
|||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
const opt = options[i]!;
|
const opt = options[i];
|
||||||
|
if (!opt) continue;
|
||||||
const isSelected = i === selectedIndex;
|
const isSelected = i === selectedIndex;
|
||||||
const cursor = isSelected ? chalk.cyan('❯') : ' ';
|
const cursor = isSelected ? chalk.cyan('❯') : ' ';
|
||||||
const truncatedLabel = truncateText(opt.label, maxWidth - labelPrefix);
|
const truncatedLabel = truncateText(opt.label, maxWidth - labelPrefix);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user