feat(team-mode): add team tasklist list with tests

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:46:07 +09:00
parent 031a7a4dd4
commit 043348442d
2 changed files with 128 additions and 0 deletions
@@ -0,0 +1,63 @@
/// <reference types="bun-types" />
import { expect, test } from "bun:test"
import { writeFile } from "node:fs/promises"
import path from "node:path"
import { getTasksDir, resolveBaseDir } from "../team-registry"
import { createTask } from "./store"
import { createTaskInput, createTasklistFixture } from "./test-support"
import { updateTaskStatus } from "./update"
import { listTasks } from "./list"
test("listTasks returns tasks sorted ascending and honors filters", async () => {
// given
const fixture = await createTasklistFixture()
try {
const firstTask = await createTask(
fixture.teamRunId,
createTaskInput({ subject: "one", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
fixture.config,
)
await createTask(fixture.teamRunId, createTaskInput({ subject: "two" }), fixture.config)
const thirdTask = await createTask(
fixture.teamRunId,
createTaskInput({ subject: "three", status: "claimed", owner: "member-a", claimedAt: Date.now() }),
fixture.config,
)
await updateTaskStatus(fixture.teamRunId, thirdTask.id, "in_progress", "member-a", fixture.config)
// when
const allTasks = await listTasks(fixture.teamRunId, fixture.config)
const claimedTasks = await listTasks(fixture.teamRunId, fixture.config, { status: "claimed", owner: "member-a" })
// then
expect(allTasks.map((task) => task.id)).toEqual([firstTask.id, "2", thirdTask.id])
expect(claimedTasks).toHaveLength(1)
expect(claimedTasks[0]?.id).toBe(firstTask.id)
} finally {
await fixture.cleanup()
}
})
test("listTasks skips malformed task files", async () => {
// given
const fixture = await createTasklistFixture()
try {
const validTask = await createTask(fixture.teamRunId, createTaskInput(), fixture.config)
const tasksDirectory = getTasksDir(resolveBaseDir(fixture.config), fixture.teamRunId)
await writeFile(path.join(tasksDirectory, "bad.json"), "{not-json")
await writeFile(path.join(tasksDirectory, ".highwatermark"), "1")
// when
const listedTasks = await listTasks(fixture.teamRunId, fixture.config)
// then
expect(listedTasks).toHaveLength(1)
expect(listedTasks[0]?.id).toBe(validTask.id)
} finally {
await fixture.cleanup()
}
})
@@ -0,0 +1,65 @@
import type { Dirent } from "node:fs"
import { readdir, readFile } from "node:fs/promises"
import path from "node:path"
import type { TeamModeConfig } from "../../../config/schema/team-mode"
import { log } from "../../../shared/logger"
import { getTasksDir, resolveBaseDir } from "../team-registry"
import { TaskSchema } from "../types"
import type { Task } from "../types"
type TaskListFilter = {
status?: Task["status"]
owner?: string
}
export async function listTasks(
teamRunId: string,
config: TeamModeConfig,
filter?: TaskListFilter,
): Promise<Task[]> {
const tasksDirectory = getTasksDir(resolveBaseDir(config), teamRunId)
let entries: Dirent[]
try {
entries = await readdir(tasksDirectory, { withFileTypes: true })
} catch {
return []
}
const parsedTasks: Task[] = []
for (const entry of entries) {
if (entry.isDirectory() || entry.name.startsWith(".") || !entry.name.endsWith(".json")) continue
const taskPath = path.join(tasksDirectory, entry.name)
try {
const taskContent = await readFile(taskPath, "utf8")
const parsedTask = TaskSchema.safeParse(JSON.parse(taskContent))
if (!parsedTask.success) {
log("team-tasklist skipped malformed task", {
event: "team-tasklist-malformed-task",
taskPath,
issues: parsedTask.error.issues,
})
continue
}
parsedTasks.push(parsedTask.data)
} catch (error) {
log("team-tasklist skipped malformed task", {
event: "team-tasklist-malformed-task",
taskPath,
error: error instanceof Error ? error.message : String(error),
})
}
}
return parsedTasks
.filter((task) => {
if (filter?.status !== undefined && task.status !== filter.status) {
return false
}
return filter?.owner === undefined || task.owner === filter.owner
})
.sort((leftTask, rightTask) => Number.parseInt(leftTask.id, 10) - Number.parseInt(rightTask.id, 10))
}