feat(cli): add task create/list/show/claim/done commands

This commit is contained in:
chahinebrini 2026-06-24 23:40:23 +02:00
parent cd6add215d
commit be9161d7d1
2 changed files with 131 additions and 0 deletions

102
src/cli/commands/task.ts Normal file
View File

@ -0,0 +1,102 @@
import { input, select } from '@inquirer/prompts';
import { join } from 'path';
import { getEntityDir } from '../../core/paths.js';
import { getNextId } from '../../core/counter.js';
import { readEntity, writeEntity, listEntities } from '../../core/files.js';
import { TaskSchema, type Task } from '../../core/schema.js';
import { Index } from '../../core/index.js';
export async function taskCreate(cwd: string, options: Partial<Task> = {}): Promise<void> {
const title = options.title ?? await input({ message: 'Task title:' });
const role = options.role ?? await select({
message: 'Role:',
choices: [
{ name: 'architect', value: 'architect' },
{ name: 'implementer', value: 'implementer' },
{ name: 'reviewer', value: 'reviewer' },
{ name: 'tester', value: 'tester' },
],
});
const priority = options.priority ?? 'medium';
const now = new Date().toISOString();
const task: Task = TaskSchema.parse({
id: getNextId(cwd, 'task'),
title,
description: options.description ?? '',
status: 'open',
priority,
role,
createdAt: now,
updatedAt: now,
});
const filePath = join(getEntityDir(cwd, 'tasks'), `${task.id}.md`);
writeEntity(filePath, task, `# ${task.title}\n\n${task.description}`);
const index = new Index(cwd);
index.upsert(toIndexEntry(task, filePath));
index.close();
console.log(`Created ${task.id}: ${task.title}`);
}
export function taskList(cwd: string, filters?: { status?: string; role?: string }): void {
const index = new Index(cwd);
const tasks = index.list('task', filters);
index.close();
if (tasks.length === 0) {
console.log('No tasks found.');
return;
}
for (const t of tasks) {
console.log(`${t.id} [${t.status}] (${t.role ?? 'unassigned'}) ${t.title}`);
}
}
export function taskShow(cwd: string, id: string): void {
const filePath = join(getEntityDir(cwd, 'tasks'), `${id}.md`);
const { frontmatter, body } = readEntity(filePath);
console.log(`# ${frontmatter.title}`);
console.log(`Status: ${frontmatter.status} | Role: ${frontmatter.role ?? '-'} | Priority: ${frontmatter.priority}`);
console.log('\n' + body);
}
export function taskClaim(cwd: string, id: string, agentName: string): void {
updateTask(cwd, id, { status: 'in_progress', assignedTo: agentName });
console.log(`${id} claimed by ${agentName}.`);
}
export function taskDone(cwd: string, id: string): void {
updateTask(cwd, id, { status: 'done' });
console.log(`${id} marked as done.`);
}
function updateTask(cwd: string, id: string, patch: Partial<Task>): void {
const filePath = join(getEntityDir(cwd, 'tasks'), `${id}.md`);
const { frontmatter, body } = readEntity(filePath);
const task = TaskSchema.parse({ ...frontmatter, ...patch, updatedAt: new Date().toISOString() });
writeEntity(filePath, task, body);
const index = new Index(cwd);
index.upsert(toIndexEntry(task, filePath));
index.close();
}
function toIndexEntry(task: Task, filePath: string) {
return {
id: task.id,
type: 'task',
title: task.title,
content: `${task.description} ${task.tags.join(' ')}`,
filePath,
createdAt: task.createdAt,
updatedAt: task.updatedAt,
status: task.status,
role: task.role,
assignedTo: task.assignedTo,
tags: JSON.stringify(task.tags),
};
}

29
tests/task-cmd.test.ts Normal file
View File

@ -0,0 +1,29 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { taskCreate, taskList, taskDone } from '../src/cli/commands/task.js';
import { init } from '../src/cli/commands/init.js';
describe('task commands', () => {
let cwd: string;
beforeEach(async () => {
cwd = mkdtempSync(join(tmpdir(), 'agenthub-'));
await init(cwd, { yes: true, projectName: 'test' });
});
afterEach(() => {
rmSync(cwd, { recursive: true, force: true });
});
it('creates and lists tasks', async () => {
await taskCreate(cwd, { title: 'Test task', role: 'implementer', priority: 'high' });
const logs: string[] = [];
const original = console.log;
console.log = (msg: string) => logs.push(msg);
taskList(cwd);
console.log = original;
expect(logs.some((m) => m.includes('TSK-0001'))).toBe(true);
});
});