lint対応

This commit is contained in:
nrslib 2026-02-04 04:10:49 +09:00
parent 887365c4eb
commit be2f892ef5
12 changed files with 84 additions and 37 deletions

View File

@ -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

View File

@ -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) |

View File

@ -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,

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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';

View File

@ -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 };

View File

@ -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}`);
}

View File

@ -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,

View File

@ -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);