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

This commit is contained in:
YeonGyu-Kim
2026-04-28 10:46:04 +09:00
parent 23e6a53ea2
commit 6e0f6aefcc
2 changed files with 85 additions and 0 deletions
@@ -0,0 +1,32 @@
/// <reference types="bun-types" />
import { expect, test } from "bun:test"
import { readFile } 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"
test("createTask assigns distinct ids during concurrent creation", async () => {
// given
const fixture = await createTasklistFixture()
try {
// when
const [firstTask, secondTask] = await Promise.all([
createTask(fixture.teamRunId, createTaskInput({ subject: "first task" }), fixture.config),
createTask(fixture.teamRunId, createTaskInput({ subject: "second task" }), fixture.config),
])
const tasksDirectory = getTasksDir(resolveBaseDir(fixture.config), fixture.teamRunId)
const watermarkContent = await readFile(path.join(tasksDirectory, ".highwatermark"), "utf8")
const sortedIds = [firstTask.id, secondTask.id].sort((leftId, rightId) => Number(leftId) - Number(rightId))
// then
expect(sortedIds).toEqual(["1", "2"])
expect(watermarkContent.trim()).toBe("2")
} finally {
await fixture.cleanup()
}
})
@@ -0,0 +1,53 @@
import { mkdir, readFile } from "node:fs/promises"
import path from "node:path"
import type { TeamModeConfig } from "../../../config/schema/team-mode"
import { getTasksDir, resolveBaseDir } from "../team-registry"
import { atomicWrite, withLock } from "../team-state-store/locks"
import { TaskSchema } from "../types"
import type { Task } from "../types"
const HIGH_WATERMARK_FILE = ".highwatermark"
async function readHighWatermark(watermarkPath: string): Promise<number> {
try {
const watermarkContent = (await readFile(watermarkPath, "utf8")).trim()
const parsedWatermark = Number.parseInt(watermarkContent, 10)
return Number.isInteger(parsedWatermark) && parsedWatermark >= 0 ? parsedWatermark : 0
} catch {
await atomicWrite(watermarkPath, "0")
return 0
}
}
export async function createTask(
teamRunId: string,
taskInput: Omit<Task, "id" | "createdAt" | "updatedAt" | "version">,
config: TeamModeConfig,
): Promise<Task> {
const tasksDirectory = getTasksDir(resolveBaseDir(config), teamRunId)
await mkdir(tasksDirectory, { recursive: true, mode: 0o700 })
await mkdir(path.join(tasksDirectory, "claims"), { recursive: true, mode: 0o700 })
return withLock(path.join(tasksDirectory, ".lock"), async () => {
const watermarkPath = path.join(tasksDirectory, HIGH_WATERMARK_FILE)
const nextTaskId = (await readHighWatermark(watermarkPath)) + 1
await atomicWrite(watermarkPath, String(nextTaskId))
const now = Date.now()
const task = TaskSchema.parse({
...taskInput,
version: 1,
id: String(nextTaskId),
createdAt: now,
updatedAt: now,
})
await atomicWrite(
path.join(tasksDirectory, `${task.id}.json`),
`${JSON.stringify(task, null, 2)}\n`,
)
return task
}, { ownerTag: `create-task:${teamRunId}` })
}