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/).
|
||||
|
||||
## [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
|
||||
|
||||
### Added
|
||||
|
||||
40
README.md
40
README.md
@ -266,9 +266,9 @@ TAKT uses YAML-based workflow definitions and rule-based routing. Builtin workfl
|
||||
```yaml
|
||||
name: default
|
||||
max_iterations: 10
|
||||
initial_step: plan
|
||||
initial_movement: plan
|
||||
|
||||
steps:
|
||||
movements:
|
||||
- name: plan
|
||||
agent: ../agents/default/planner.md
|
||||
model: opus
|
||||
@ -303,9 +303,9 @@ steps:
|
||||
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
|
||||
- name: summarize
|
||||
@ -328,9 +328,9 @@ You can also write an inline system prompt as the `agent` value (if the specifie
|
||||
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
|
||||
- name: reviewers
|
||||
@ -356,15 +356,15 @@ Execute sub-steps in parallel within a step and evaluate with aggregate conditio
|
||||
next: fix
|
||||
```
|
||||
|
||||
- `all("X")`: true if ALL sub-steps matched condition X
|
||||
- `any("X")`: true if ANY sub-step matched condition X
|
||||
- Sub-step `rules` define possible outcomes, but `next` is optional (parent controls transition)
|
||||
- `all("X")`: true if ALL sub-movements matched condition X
|
||||
- `any("X")`: true if ANY sub-movement matched condition X
|
||||
- Sub-movement `rules` define possible outcomes, but `next` is optional (parent controls transition)
|
||||
|
||||
### Rule Condition Types
|
||||
|
||||
| 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 |
|
||||
| Aggregate | `all("X")` / `any("X")` | Aggregates parallel sub-step matched conditions |
|
||||
|
||||
@ -579,9 +579,9 @@ takt eject default
|
||||
name: my-workflow
|
||||
description: Custom workflow
|
||||
max_iterations: 5
|
||||
initial_step: analyze
|
||||
initial_movement: analyze
|
||||
|
||||
steps:
|
||||
movements:
|
||||
- name: analyze
|
||||
agent: ~/.takt/agents/my-agents/analyzer.md
|
||||
edit: false
|
||||
@ -629,7 +629,7 @@ Variables available in `instruction_template`:
|
||||
| `{task}` | Original user request (auto-injected if not in template) |
|
||||
| `{iteration}` | Workflow-wide turn count (total steps executed) |
|
||||
| `{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) |
|
||||
| `{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`) |
|
||||
@ -637,7 +637,7 @@ Variables available in `instruction_template`:
|
||||
|
||||
### Workflow Design
|
||||
|
||||
Elements needed for each workflow step:
|
||||
Elements needed for each workflow movement:
|
||||
|
||||
**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)
|
||||
```
|
||||
|
||||
**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
|
||||
rules:
|
||||
@ -658,15 +658,15 @@ rules:
|
||||
|
||||
Special `next` values: `COMPLETE` (success), `ABORT` (failure)
|
||||
|
||||
**3. Step Options:**
|
||||
**3. Movement Options:**
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| `edit` | - | Whether step can edit project files (`true`/`false`) |
|
||||
| `pass_previous_response` | `true` | Pass previous step output to `{previous_response}` |
|
||||
| `edit` | - | Whether movement can edit project files (`true`/`false`) |
|
||||
| `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.) |
|
||||
| `provider` | - | Override provider for this step (`claude` or `codex`) |
|
||||
| `model` | - | Override model for this step |
|
||||
| `provider` | - | Override provider for this movement (`claude` or `codex`) |
|
||||
| `model` | - | Override model for this movement |
|
||||
| `permission_mode` | - | Permission mode: `readonly`, `edit`, `full` (provider-independent) |
|
||||
| `report` | - | Auto-generated report file settings (name, format) |
|
||||
|
||||
|
||||
@ -55,7 +55,10 @@ export class ParallelRunner {
|
||||
maxIterations: number,
|
||||
updateAgentSession: (agent: string, sessionId: string | undefined) => void,
|
||||
): 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);
|
||||
log.debug('Running parallel movement', {
|
||||
movement: step.name,
|
||||
|
||||
@ -50,7 +50,7 @@ export class ParallelLogger {
|
||||
* Format: `\x1b[COLORm[name]\x1b[0m` + padding spaces
|
||||
*/
|
||||
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);
|
||||
return `${color}[${name}]${RESET}${padding} `;
|
||||
}
|
||||
@ -99,7 +99,8 @@ export class ParallelLogger {
|
||||
const parts = combined.split('\n');
|
||||
|
||||
// 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
|
||||
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;
|
||||
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -115,7 +115,8 @@ export class RuleEvaluator {
|
||||
|
||||
const aiConditions: { index: number; text: string }[] = [];
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
@ -135,7 +136,8 @@ export class RuleEvaluator {
|
||||
const judgeResult = await this.ctx.callAiJudge(agentOutput, judgeConditions, { cwd: this.ctx.cwd });
|
||||
|
||||
if (judgeResult >= 0 && judgeResult < aiConditions.length) {
|
||||
const matched = aiConditions[judgeResult]!;
|
||||
const matched = aiConditions[judgeResult];
|
||||
if (!matched) return -1;
|
||||
log.debug('AI judge matched condition', {
|
||||
movement: this.step.name,
|
||||
judgeResult,
|
||||
@ -172,7 +174,7 @@ export class RuleEvaluator {
|
||||
log.debug('AI judge (fallback) matched condition', {
|
||||
movement: this.step.name,
|
||||
ruleIndex: judgeResult,
|
||||
condition: conditions[judgeResult]!.text,
|
||||
condition: conditions[judgeResult]?.text,
|
||||
});
|
||||
return judgeResult;
|
||||
}
|
||||
|
||||
@ -49,8 +49,8 @@ export class InstructionBuilder {
|
||||
const hasReport = !!(this.step.report && this.context.reportDir);
|
||||
let reportInfo = '';
|
||||
let phaseNote = '';
|
||||
if (hasReport) {
|
||||
reportInfo = renderReportContext(this.step.report!, this.context.reportDir!);
|
||||
if (hasReport && this.step.report && this.context.reportDir) {
|
||||
reportInfo = renderReportContext(this.step.report, this.context.reportDir);
|
||||
phaseNote = language === 'ja'
|
||||
? '**注意:** これは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.';
|
||||
@ -72,8 +72,8 @@ export class InstructionBuilder {
|
||||
this.context.previousOutput &&
|
||||
!hasPreviousResponsePlaceholder
|
||||
);
|
||||
const previousResponse = hasPreviousResponse
|
||||
? escapeTemplateChars(this.context.previousOutput!.content)
|
||||
const previousResponse = hasPreviousResponse && this.context.previousOutput
|
||||
? escapeTemplateChars(this.context.previousOutput.content)
|
||||
: '';
|
||||
|
||||
// User Inputs
|
||||
|
||||
@ -11,7 +11,7 @@ import { stringify as stringifyYaml } from 'yaml';
|
||||
import { promptInput, confirm } from '../../../shared/prompt/index.js';
|
||||
import { success, info } from '../../../shared/ui/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 { createLogger, getErrorMessage } from '../../../shared/utils/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;
|
||||
|
||||
return categoryItem.workflows.map((qualifiedName) => {
|
||||
const displayName = qualifiedName.split('/').pop()!;
|
||||
const displayName = qualifiedName.split('/').pop() ?? qualifiedName;
|
||||
const isCurrent = qualifiedName === currentWorkflow;
|
||||
const label = isCurrent ? `${displayName} (current)` : displayName;
|
||||
return { label, value: qualifiedName };
|
||||
|
||||
@ -77,7 +77,8 @@ export function loadAgentPrompt(agent: CustomAgentConfig): string {
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
while ((match = regex.exec(argsText)) !== null) {
|
||||
conditions.push(match[1]!);
|
||||
if (match[1]) conditions.push(match[1]);
|
||||
}
|
||||
|
||||
if (conditions.length === 0) {
|
||||
@ -146,7 +146,9 @@ function normalizeRule(r: {
|
||||
const aggMatch = r.condition.match(AGGREGATE_CONDITION_REGEX);
|
||||
if (aggMatch?.[1] && 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 {
|
||||
condition: r.condition,
|
||||
next,
|
||||
|
||||
@ -33,7 +33,8 @@ export function renderMenu<T extends string>(
|
||||
const lines: string[] = [];
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const opt = options[i]!;
|
||||
const opt = options[i];
|
||||
if (!opt) continue;
|
||||
const isSelected = i === selectedIndex;
|
||||
const cursor = isSelected ? chalk.cyan('❯') : ' ';
|
||||
const truncatedLabel = truncateText(opt.label, maxWidth - labelPrefix);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user