feat(services): add task service module
This commit is contained in:
parent
d73ff27129
commit
4a8f5cbb8e
79
src/core/services/taskService.ts
Normal file
79
src/core/services/taskService.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { join } from 'path';
|
||||
import { getEntityDir } from '../paths.js';
|
||||
import { getNextId } from '../counter.js';
|
||||
import { readEntity, writeEntity } from '../files.js';
|
||||
import { TaskSchema, type Task } from '../schema.js';
|
||||
import { Index } from '../index.js';
|
||||
|
||||
export function createTask(cwd: string, options: Partial<Task> = {}): Task {
|
||||
const now = new Date().toISOString();
|
||||
const task: Task = TaskSchema.parse({
|
||||
id: getNextId(cwd, 'task'),
|
||||
title: options.title ?? 'Untitled',
|
||||
description: options.description ?? '',
|
||||
status: 'open',
|
||||
priority: options.priority ?? 'medium',
|
||||
role: options.role,
|
||||
assignedTo: options.assignedTo,
|
||||
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();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
export function listTasks(cwd: string, filters?: { status?: string; role?: string }): ReturnType<Index['list']> {
|
||||
const index = new Index(cwd);
|
||||
const tasks = index.list('task', filters);
|
||||
index.close();
|
||||
return tasks;
|
||||
}
|
||||
|
||||
export function getTask(cwd: string, id: string): { task: Task; body: string; filePath: string } {
|
||||
const filePath = join(getEntityDir(cwd, 'tasks'), `${id}.md`);
|
||||
const { frontmatter, body } = readEntity(filePath);
|
||||
return { task: TaskSchema.parse(frontmatter), body, filePath };
|
||||
}
|
||||
|
||||
export function updateTask(cwd: string, id: string, patch: Partial<Task>): Task {
|
||||
const { task, body, filePath } = getTask(cwd, id);
|
||||
const updated: Task = TaskSchema.parse({ ...task, ...patch, updatedAt: new Date().toISOString() });
|
||||
writeEntity(filePath, updated, body);
|
||||
|
||||
const index = new Index(cwd);
|
||||
index.upsert(toIndexEntry(updated, filePath));
|
||||
index.close();
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
export function claimTask(cwd: string, id: string, agentName: string): Task {
|
||||
return updateTask(cwd, id, { status: 'in_progress', assignedTo: agentName });
|
||||
}
|
||||
|
||||
export function doneTask(cwd: string, id: string): Task {
|
||||
return updateTask(cwd, id, { status: 'done' });
|
||||
}
|
||||
|
||||
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),
|
||||
};
|
||||
}
|
||||
38
tests/taskService.test.ts
Normal file
38
tests/taskService.test.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { mkdtempSync, rmSync } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { createTask, listTasks, getTask, claimTask, doneTask } from '../src/core/services/taskService.js';
|
||||
|
||||
describe('taskService', () => {
|
||||
let cwd: string;
|
||||
|
||||
beforeEach(() => {
|
||||
cwd = mkdtempSync(join(tmpdir(), 'ah-task-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('creates a task', () => {
|
||||
const task = createTask(cwd, { title: 'Test', role: 'implementer' });
|
||||
expect(task.id).toBe('TSK-0001');
|
||||
expect(task.title).toBe('Test');
|
||||
expect(task.status).toBe('open');
|
||||
});
|
||||
|
||||
it('lists tasks', () => {
|
||||
createTask(cwd, { title: 'A', role: 'implementer' });
|
||||
expect(listTasks(cwd)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('claims and completes a task', () => {
|
||||
const task = createTask(cwd, { title: 'B', role: 'implementer' });
|
||||
const claimed = claimTask(cwd, task.id, 'codex');
|
||||
expect(claimed.status).toBe('in_progress');
|
||||
expect(claimed.assignedTo).toBe('codex');
|
||||
const done = doneTask(cwd, task.id);
|
||||
expect(done.status).toBe('done');
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user