Merge branch 'takt/20260129T0506-add-list-command'

This commit is contained in:
nrslib 2026-01-29 14:30:53 +09:00
commit cc9366f2b2
12 changed files with 63 additions and 63 deletions

View File

@ -24,7 +24,7 @@ TAKT (Task Agent Koordination Tool) is a multi-agent orchestration system for Cl
| `takt /run-tasks` | `/run` | Execute all pending tasks from `.takt/tasks/` once |
| `takt /watch` | | Watch `.takt/tasks/` and auto-execute tasks (resident process) |
| `takt /add-task` | `/add` | Add a new task interactively (YAML format, multiline supported) |
| `takt /review-tasks` | `/review` | Review task branches (try merge, merge & cleanup, or delete) |
| `takt /list-tasks` | `/list` | List task branches (try merge, merge & cleanup, or delete) |
| `takt /switch` | | Switch workflow interactively |
| `takt /clear` | | Clear agent conversation sessions (reset state) |
| `takt /refresh-builtin` | | Update builtin resources from `resources/` to `~/.takt/` |
@ -37,7 +37,7 @@ TAKT (Task Agent Koordination Tool) is a multi-agent orchestration system for Cl
```
CLI (cli.ts)
→ Slash commands (/run-tasks, /watch, /add-task, /switch, /clear, /refresh-builtin, /help, /config)
→ Slash commands (/run-tasks, /watch, /add-task, /list-tasks, /switch, /clear, /refresh-builtin, /help, /config)
→ or executeTask()
→ WorkflowEngine (workflow/engine.ts)
→ runAgent() (agents/runner.ts)
@ -199,7 +199,7 @@ Key constraints:
- **Session isolation**: Claude Code sessions are stored per-cwd in `~/.claude/projects/{encoded-path}/`. Sessions from the main project cannot be resumed in a clone. The engine skips session resume when `cwd !== projectCwd`.
- **No node_modules**: Clones only contain tracked files. `node_modules/` is absent.
- **Dual cwd**: `cwd` = clone path (where agents run), `projectCwd` = project root (where `.takt/` lives). Reports, logs, and session data always write to `projectCwd`.
- **Review**: Use `takt /review-tasks` to review branches. Instruct action creates a temporary clone for the branch, executes, pushes, then removes the clone.
- **List**: Use `takt /list-tasks` to list branches. Instruct action creates a temporary clone for the branch, executes, pushes, then removes the clone.
## Error Propagation

View File

@ -35,8 +35,8 @@ takt /run-tasks
# Watch for tasks and auto-execute
takt /watch
# Review task branches (merge or delete)
takt /review-tasks
# List task branches (merge or delete)
takt /list-tasks
# Switch workflow
takt /switch
@ -51,7 +51,7 @@ takt /switch
| `takt /run-tasks` | `/run` | Run all pending tasks from `.takt/tasks/` |
| `takt /watch` | | Watch `.takt/tasks/` and auto-execute tasks (stays resident) |
| `takt /add-task` | `/add` | Add a new task interactively (YAML format, multiline supported) |
| `takt /review-tasks` | `/review` | Review task branches (try merge, merge & cleanup, or delete) |
| `takt /list-tasks` | `/list` | List task branches (try merge, merge & cleanup, or delete) |
| `takt /switch` | | Switch workflow interactively |
| `takt /clear` | | Clear agent conversation sessions |
| `takt /refresh-builtin` | | Update builtin agents/workflows to latest version |
@ -353,7 +353,7 @@ YAML task files can specify `worktree` to run each task in an isolated `git clon
> **Note**: The YAML field is named `worktree` for backward compatibility. Internally, `git clone --shared` is used instead of `git worktree` because git worktrees have a `.git` file with `gitdir:` that points back to the main repository, causing Claude Code to recognize the main repo as the project root. Shared clones have an independent `.git` directory that avoids this issue.
Clones are ephemeral. When a task completes successfully, TAKT automatically commits all changes and pushes the branch to the main repository, then deletes the clone. Use `takt /review-tasks` to review, try-merge, or delete task branches.
Clones are ephemeral. When a task completes successfully, TAKT automatically commits all changes and pushes the branch to the main repository, then deletes the clone. Use `takt /list-tasks` to list, try-merge, or delete task branches.
#### Running Tasks with `/run-tasks`
@ -376,10 +376,10 @@ Watch mode polls `.takt/tasks/` for new task files and auto-executes them as the
- Automated workflows where tasks are added by external processes
- Long-running development sessions where tasks are queued over time
#### Reviewing Task Branches with `/review-tasks`
#### Listing Task Branches with `/list-tasks`
```bash
takt /review-tasks
takt /list-tasks
```
Lists all `takt/`-prefixed branches with file change counts. For each branch you can:

View File

@ -116,7 +116,7 @@ YAMLタスクファイルで`worktree`を指定すると、各タスクを`git c
> **Note**: YAMLフィールド名は後方互換のため`worktree`のままです。内部的には`git worktree`ではなく`git clone --shared`を使用しています。git worktreeの`.git`ファイルには`gitdir:`でメインリポジトリへのパスが記載されており、Claude Codeがそれを辿ってメインリポジトリをプロジェクトルートと認識してしまうためです。共有クローンは独立した`.git`ディレクトリを持つため、この問題が発生しません。
クローンは使い捨てです。タスク完了後に自動的にコミット+プッシュし、クローンを削除します。ブランチが唯一の永続的な成果物です。`takt /review-tasks`でブランチのレビュー・マージ・削除ができます。
クローンは使い捨てです。タスク完了後に自動的にコミット+プッシュし、クローンを削除します。ブランチが唯一の永続的な成果物です。`takt /list-tasks`でブランチの一覧表示・マージ・削除ができます。
#### `/run-tasks` でタスクを実行

View File

@ -65,7 +65,7 @@ vi.mock('../commands/index.js', () => ({
addTask: vi.fn(),
refreshBuiltin: vi.fn(),
watchTasks: vi.fn(),
reviewTasks: vi.fn(),
listTasks: vi.fn(),
}));
vi.mock('../config/workflowLoader.js', () => ({

View File

@ -12,7 +12,7 @@ vi.mock('node:child_process', () => ({
import { execFileSync } from 'node:child_process';
const mockExecFileSync = vi.mocked(execFileSync);
import { getOriginalInstruction } from '../task/branchReview.js';
import { getOriginalInstruction } from '../task/branchList.js';
beforeEach(() => {
vi.clearAllMocks();

View File

@ -1,15 +1,15 @@
/**
* Tests for review-tasks command
* Tests for list-tasks command
*/
import { describe, it, expect, vi } from 'vitest';
import {
parseTaktBranches,
extractTaskSlug,
buildReviewItems,
buildListItems,
type BranchInfo,
} from '../task/branchReview.js';
import { isBranchMerged, showFullDiff, type ReviewAction } from '../commands/reviewTasks.js';
} from '../task/branchList.js';
import { isBranchMerged, showFullDiff, type ListAction } from '../commands/listTasks.js';
describe('parseTaktBranches', () => {
it('should parse takt/ branches from git branch output', () => {
@ -92,7 +92,7 @@ describe('extractTaskSlug', () => {
});
});
describe('buildReviewItems', () => {
describe('buildListItems', () => {
it('should build items with correct task slug and originalInstruction', () => {
const branches: BranchInfo[] = [
{
@ -101,7 +101,7 @@ describe('buildReviewItems', () => {
},
];
const items = buildReviewItems('/project', branches, 'main');
const items = buildListItems('/project', branches, 'main');
expect(items).toHaveLength(1);
expect(items[0]!.taskSlug).toBe('fix-auth');
expect(items[0]!.info).toBe(branches[0]);
@ -123,21 +123,21 @@ describe('buildReviewItems', () => {
},
];
const items = buildReviewItems('/project', branches, 'main');
const items = buildListItems('/project', branches, 'main');
expect(items).toHaveLength(2);
expect(items[0]!.taskSlug).toBe('fix-auth');
expect(items[1]!.taskSlug).toBe('add-search');
});
it('should handle empty branch list', () => {
const items = buildReviewItems('/project', [], 'main');
const items = buildListItems('/project', [], 'main');
expect(items).toHaveLength(0);
});
});
describe('ReviewAction type', () => {
describe('ListAction type', () => {
it('should include diff, instruct, try, merge, delete (no skip)', () => {
const actions: ReviewAction[] = ['diff', 'instruct', 'try', 'merge', 'delete'];
const actions: ListAction[] = ['diff', 'instruct', 'try', 'merge', 'delete'];
expect(actions).toHaveLength(5);
expect(actions).toContain('diff');
expect(actions).toContain('instruct');

View File

@ -32,7 +32,7 @@ import {
addTask,
refreshBuiltin,
watchTasks,
reviewTasks,
listTasks,
} from './commands/index.js';
import { listWorkflows } from './config/workflowLoader.js';
import { selectOptionWithDefault, confirm } from './prompt/index.js';
@ -167,14 +167,14 @@ program
await watchTasks(cwd);
return;
case 'review-tasks':
case 'review':
await reviewTasks(cwd);
case 'list-tasks':
case 'list':
await listTasks(cwd);
return;
default:
error(`Unknown command: /${command}`);
info('Available: /run-tasks (/run), /watch, /add-task (/add), /review-tasks (/review), /switch (/sw), /clear, /refresh-builtin, /help, /config');
info('Available: /run-tasks (/run), /watch, /add-task (/add), /list-tasks (/list), /switch (/sw), /clear, /refresh-builtin, /help, /config');
process.exit(1);
}
}

View File

@ -17,7 +17,7 @@ Usage:
takt /run-tasks (/run) Run all pending tasks from .takt/tasks/
takt /watch Watch for tasks and auto-execute (stays resident)
takt /add-task (/add) Add a new task (interactive, YAML format)
takt /review-tasks (/review) Review task branches (merge/delete)
takt /list-tasks (/list) List task branches (merge/delete)
takt /switch Switch workflow interactively
takt /clear Clear agent conversation sessions (reset to initial state)
takt /refresh-builtin Overwrite builtin agents/workflows with latest version
@ -30,7 +30,7 @@ Examples:
takt /clear # Clear sessions, start fresh
takt /watch # Watch & auto-execute tasks
takt /refresh-builtin # Update builtin resources
takt /review-tasks # Review & merge task branches
takt /list-tasks # List & merge task branches
takt /switch
takt /run-tasks

View File

@ -11,4 +11,4 @@ export { showHelp } from './help.js';
export { withAgentSession } from './session.js';
export { switchWorkflow } from './workflow.js';
export { switchConfig, getCurrentPermissionMode, setPermissionMode, type PermissionMode } from './config.js';
export { reviewTasks } from './reviewTasks.js';
export { listTasks } from './listTasks.js';

View File

@ -1,5 +1,5 @@
/**
* Review tasks command
* List tasks command
*
* Interactive UI for reviewing branch-based task results:
* try merge, merge & cleanup, or delete actions.
@ -17,9 +17,9 @@ import {
import {
detectDefaultBranch,
listTaktBranches,
buildReviewItems,
type BranchReviewItem,
} from '../task/branchReview.js';
buildListItems,
type BranchListItem,
} from '../task/branchList.js';
import { autoCommitAndPush } from '../task/autoCommit.js';
import { selectOption, confirm, promptInput } from '../prompt/index.js';
import { info, success, error as logError, warn } from '../utils/ui.js';
@ -29,10 +29,10 @@ import { listWorkflows } from '../config/workflowLoader.js';
import { getCurrentWorkflow } from '../config/paths.js';
import { DEFAULT_WORKFLOW_NAME } from '../constants.js';
const log = createLogger('review-tasks');
const log = createLogger('list-tasks');
/** Actions available for a reviewed branch */
export type ReviewAction = 'diff' | 'instruct' | 'try' | 'merge' | 'delete';
/** Actions available for a listed branch */
export type ListAction = 'diff' | 'instruct' | 'try' | 'merge' | 'delete';
/**
* Check if a branch has already been merged into HEAD.
@ -78,8 +78,8 @@ export function showFullDiff(
async function showDiffAndPromptAction(
cwd: string,
defaultBranch: string,
item: BranchReviewItem,
): Promise<ReviewAction | null> {
item: BranchListItem,
): Promise<ListAction | null> {
console.log();
console.log(chalk.bold.cyan(`=== ${item.info.branch} ===`));
if (item.originalInstruction) {
@ -99,7 +99,7 @@ async function showDiffAndPromptAction(
}
// Prompt action
const action = await selectOption<ReviewAction>(
const action = await selectOption<ListAction>(
`Action for ${item.info.branch}:`,
[
{ label: 'View diff', value: 'diff', description: 'Show full diff in pager' },
@ -117,7 +117,7 @@ async function showDiffAndPromptAction(
* Try-merge (squash): stage changes from branch without committing.
* User can inspect staged changes and commit manually if satisfied.
*/
export function tryMergeBranch(projectDir: string, item: BranchReviewItem): boolean {
export function tryMergeBranch(projectDir: string, item: BranchListItem): boolean {
const { branch } = item.info;
try {
@ -145,7 +145,7 @@ export function tryMergeBranch(projectDir: string, item: BranchReviewItem): bool
* Otherwise merge first, then delete the branch.
* No worktree removal needed clones are ephemeral.
*/
export function mergeBranch(projectDir: string, item: BranchReviewItem): boolean {
export function mergeBranch(projectDir: string, item: BranchListItem): boolean {
const { branch } = item.info;
const alreadyMerged = isBranchMerged(projectDir, branch);
@ -192,7 +192,7 @@ export function mergeBranch(projectDir: string, item: BranchReviewItem): boolean
* Delete a branch (discard changes).
* No worktree removal needed clones are ephemeral.
*/
export function deleteBranch(projectDir: string, item: BranchReviewItem): boolean {
export function deleteBranch(projectDir: string, item: BranchListItem): boolean {
const { branch } = item.info;
try {
@ -291,7 +291,7 @@ function getBranchContext(projectDir: string, branch: string): string {
*/
export async function instructBranch(
projectDir: string,
item: BranchReviewItem,
item: BranchListItem,
): Promise<boolean> {
const { branch } = item.info;
@ -349,22 +349,22 @@ export async function instructBranch(
}
/**
* Main entry point: review branch-based tasks interactively.
* Main entry point: list branch-based tasks interactively.
*/
export async function reviewTasks(cwd: string): Promise<void> {
log.info('Starting review-tasks');
export async function listTasks(cwd: string): Promise<void> {
log.info('Starting list-tasks');
const defaultBranch = detectDefaultBranch(cwd);
let branches = listTaktBranches(cwd);
if (branches.length === 0) {
info('No tasks to review.');
info('No tasks to list.');
return;
}
// Interactive loop
while (branches.length > 0) {
const items = buildReviewItems(cwd, branches, defaultBranch);
const items = buildListItems(cwd, branches, defaultBranch);
// Build selection options
const options = items.map((item, idx) => {
@ -380,7 +380,7 @@ export async function reviewTasks(cwd: string): Promise<void> {
});
const selected = await selectOption<string>(
'Review Tasks (Branches)',
'List Tasks (Branches)',
options,
);
@ -393,7 +393,7 @@ export async function reviewTasks(cwd: string): Promise<void> {
if (!item) continue;
// Action loop: re-show menu after viewing diff
let action: ReviewAction | null;
let action: ListAction | null;
do {
action = await showDiffAndPromptAction(cwd, defaultBranch, item);
@ -430,5 +430,5 @@ export async function reviewTasks(cwd: string): Promise<void> {
branches = listTaktBranches(cwd);
}
info('All tasks reviewed.');
info('All tasks listed.');
}

View File

@ -1,15 +1,15 @@
/**
* Branch review helpers
* Branch list helpers
*
* Functions for listing, parsing, and enriching takt-managed branches
* with metadata (diff stats, original instruction, task slug).
* Used by the /review command.
* Used by the /list command.
*/
import { execFileSync } from 'node:child_process';
import { createLogger } from '../utils/debug.js';
const log = createLogger('branchReview');
const log = createLogger('branchList');
/** Branch info from `git branch --list` */
export interface BranchInfo {
@ -17,8 +17,8 @@ export interface BranchInfo {
commit: string;
}
/** Branch with review metadata */
export interface BranchReviewItem {
/** Branch with list metadata */
export interface BranchListItem {
info: BranchInfo;
filesChanged: number;
taskSlug: string;
@ -159,13 +159,13 @@ export function getOriginalInstruction(
}
/**
* Build review items from branch list, enriching with diff stats.
* Build list items from branch list, enriching with diff stats.
*/
export function buildReviewItems(
export function buildListItems(
projectDir: string,
branches: BranchInfo[],
defaultBranch: string,
): BranchReviewItem[] {
): BranchListItem[] {
return branches.map(br => ({
info: br,
filesChanged: getFilesChanged(projectDir, defaultBranch, br.branch),

View File

@ -29,9 +29,9 @@ export {
getFilesChanged,
extractTaskSlug,
getOriginalInstruction,
buildReviewItems,
buildListItems,
type BranchInfo,
type BranchReviewItem,
} from './branchReview.js';
type BranchListItem,
} from './branchList.js';
export { autoCommitAndPush, type AutoCommitResult } from './autoCommit.js';
export { TaskWatcher, type TaskWatcherOptions } from './watcher.js';