211 lines
8.2 KiB
TypeScript
211 lines
8.2 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
import type { BossState, Project, ThreadProgressEvent, ThreadStatusDocument } from "../src/lib/boss-data.ts";
|
|
|
|
let runtimeRoot = "";
|
|
let readState: (typeof import("../src/lib/boss-data"))["readState"];
|
|
let writeState: (typeof import("../src/lib/boss-data"))["writeState"];
|
|
let appendProjectMessage: (typeof import("../src/lib/boss-data"))["appendProjectMessage"];
|
|
|
|
type MutableBossState = BossState & {
|
|
threadStatusDocuments: ThreadStatusDocument[];
|
|
threadProgressEvents: ThreadProgressEvent[];
|
|
projects: Project[];
|
|
};
|
|
|
|
async function setup() {
|
|
if (runtimeRoot) return;
|
|
|
|
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-thread-status-"));
|
|
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
|
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
|
|
|
const data = await import("../src/lib/boss-data.ts");
|
|
readState = data.readState;
|
|
writeState = data.writeState;
|
|
appendProjectMessage = data.appendProjectMessage;
|
|
}
|
|
|
|
test.after(async () => {
|
|
if (runtimeRoot) {
|
|
await rm(runtimeRoot, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("thread status documents and progress events normalize, sort, and trim correctly", async () => {
|
|
await setup();
|
|
|
|
const state = (await readState()) as MutableBossState;
|
|
const baseDocTime = Date.parse("2026-04-04T10:00:00.000Z");
|
|
state.threadStatusDocuments = Array.from({ length: 81 }, (_, index) => ({
|
|
documentId: `doc-${index}`,
|
|
projectId: "master-agent",
|
|
threadId: `thread-${index}`,
|
|
threadDisplayName: index === 80 ? " 树莓派二代查询 " : `线程 ${index}`,
|
|
folderName: index === 80 ? " Talking " : `文件夹 ${index}`,
|
|
deviceId: " mac-studio ",
|
|
projectGoal: index === 80 ? " 完成树莓派二代查询链路 " : `目标 ${index}`,
|
|
currentPhase: index === 80 ? " 功能实现 " : `阶段 ${index}`,
|
|
currentProgress: index === 80 ? " 已完成排序修复 " : `进度 ${index}`,
|
|
technicalArchitecture: index === 80 ? " Next.js API + Android 原生客户端 " : `架构 ${index}`,
|
|
currentBlockers: index === 80 ? " " : `阻塞 ${index}`,
|
|
recommendedNextStep: index === 80 ? " 补会话页展示与排序 " : `下一步 ${index}`,
|
|
keyFiles: index === 80 ? [" src/lib/boss-data.ts ", " tests/thread-status-sync.test.ts "] : [`file-${index}.ts`],
|
|
keyCommands: index === 80 ? [" npm run build ", " npm run lint "] : [`cmd-${index}`],
|
|
updatedAt: new Date(baseDocTime + index * 60_000).toISOString(),
|
|
sourceTaskId: `task-${index}`,
|
|
sourceKind: index === 80 ? undefined : "full_sync",
|
|
}));
|
|
state.threadProgressEvents = [
|
|
...Array.from({ length: 25 }, (_, index) => ({
|
|
eventId: `event-a-${index}`,
|
|
projectId: "master-agent",
|
|
threadId: "thread-a",
|
|
threadDisplayName: "线程 A",
|
|
deviceId: "mac-studio",
|
|
eventType: "progress_updated",
|
|
summary: `线程 A 进展 ${index}`,
|
|
phase: "功能实现",
|
|
createdAt: `2026-04-04T19:${String(index).padStart(2, "0")}:00+08:00`,
|
|
sourceTaskId: `task-a-${index}`,
|
|
})),
|
|
...Array.from({ length: 381 }, (_, index) => ({
|
|
eventId: `event-b-${index}`,
|
|
projectId: "master-agent",
|
|
threadId: `thread-b-${index}`,
|
|
threadDisplayName: `线程 B${index}`,
|
|
deviceId: "mac-studio",
|
|
eventType: "progress_updated",
|
|
summary: `线程 B${index} 进展`,
|
|
createdAt: `2026-04-04T17:${String(index % 60).padStart(2, "0")}:${String(index % 60).padStart(2, "0")}+08:00`,
|
|
sourceTaskId: `task-b-${index}`,
|
|
})),
|
|
];
|
|
|
|
await writeState(state);
|
|
const normalized = (await readState()) as MutableBossState;
|
|
|
|
assert.equal(normalized.threadStatusDocuments.length, 80);
|
|
assert.equal(normalized.threadStatusDocuments[0]?.documentId, "doc-80");
|
|
assert.equal(normalized.threadStatusDocuments[0]?.threadDisplayName, "树莓派二代查询");
|
|
assert.equal(normalized.threadStatusDocuments[0]?.folderName, "Talking");
|
|
assert.equal(normalized.threadStatusDocuments[0]?.deviceId, "mac-studio");
|
|
assert.equal(normalized.threadStatusDocuments[0]?.projectGoal, "完成树莓派二代查询链路");
|
|
assert.equal(normalized.threadStatusDocuments[0]?.currentPhase, "功能实现");
|
|
assert.deepEqual(normalized.threadStatusDocuments[0]?.keyFiles, [
|
|
"src/lib/boss-data.ts",
|
|
"tests/thread-status-sync.test.ts",
|
|
]);
|
|
assert.deepEqual(normalized.threadStatusDocuments[0]?.keyCommands, ["npm run build", "npm run lint"]);
|
|
assert.equal(normalized.threadStatusDocuments[0]?.sourceKind, "incremental_sync");
|
|
|
|
assert.equal(normalized.threadProgressEvents.length, 400);
|
|
assert.equal(normalized.threadProgressEvents[0]?.eventId, "event-a-24");
|
|
assert.equal(
|
|
normalized.threadProgressEvents.filter((event) => event.projectId === "master-agent" && event.threadId === "thread-a")
|
|
.length,
|
|
20,
|
|
);
|
|
});
|
|
|
|
test("thread replies append lightweight progress events and skip redundant understanding sync when status document is fresh", async () => {
|
|
await setup();
|
|
|
|
const state = (await readState()) as MutableBossState;
|
|
state.threadProgressEvents = [];
|
|
state.projects.push({
|
|
id: "thread-sync-demo",
|
|
name: "线程状态演示",
|
|
pinned: false,
|
|
deviceIds: ["mac-studio"],
|
|
preview: "初始状态",
|
|
updatedAt: "2026-04-04T18:00:00+08:00",
|
|
lastMessageAt: "2026-04-04T18:00:00+08:00",
|
|
isGroup: false,
|
|
threadMeta: {
|
|
projectId: "thread-sync-demo",
|
|
threadId: "thread-sync-demo-thread",
|
|
threadDisplayName: "线程状态演示",
|
|
folderName: "演示文件夹",
|
|
activityIconCount: 1,
|
|
updatedAt: "2026-04-04T18:00:00+08:00",
|
|
lastObservedCodexActivityAt: "2026-04-04T18:00:00+08:00",
|
|
lastProjectUnderstandingRequestedAt: "2026-04-04T17:00:00+08:00",
|
|
lastProjectUnderstandingSyncedAt: "2026-04-04T18:00:00+08:00",
|
|
codexThreadRef: "thread-sync-demo-thread",
|
|
codexFolderRef: "thread-sync-demo-folder",
|
|
},
|
|
groupMembers: [],
|
|
createdByAgent: false,
|
|
collaborationMode: "development",
|
|
approvalState: "not_required",
|
|
unreadCount: 0,
|
|
riskLevel: "low",
|
|
projectUnderstanding: {
|
|
projectGoal: "完成线程状态回归",
|
|
currentProgress: "旧进度",
|
|
technicalArchitecture: "旧架构",
|
|
currentBlockers: "",
|
|
recommendedNextStep: "旧下一步",
|
|
sourceTaskId: "task-old",
|
|
updatedAt: "2026-04-04T18:00:00+08:00",
|
|
sourceKind: "thread_sync",
|
|
},
|
|
messages: [],
|
|
goals: [],
|
|
versions: [],
|
|
} as Project);
|
|
state.threadStatusDocuments = [
|
|
{
|
|
documentId: "doc-old",
|
|
projectId: "thread-sync-demo",
|
|
threadId: "thread-sync-demo-thread",
|
|
threadDisplayName: "线程状态演示",
|
|
folderName: "演示文件夹",
|
|
deviceId: "mac-studio",
|
|
projectGoal: "完成线程状态回归",
|
|
currentPhase: "全量理解",
|
|
currentProgress: "旧进度",
|
|
technicalArchitecture: "旧架构",
|
|
currentBlockers: "",
|
|
recommendedNextStep: "旧下一步",
|
|
keyFiles: ["src/lib/boss-data.ts"],
|
|
keyCommands: ["npm run build"],
|
|
updatedAt: "2026-04-04T18:00:00+08:00",
|
|
sourceTaskId: "task-old",
|
|
sourceKind: "full_sync",
|
|
},
|
|
];
|
|
|
|
await writeState(state);
|
|
const before = (await readState()) as MutableBossState;
|
|
const beforeCount = before.threadProgressEvents.filter(
|
|
(event) => event.projectId === "thread-sync-demo",
|
|
).length;
|
|
|
|
const message = await appendProjectMessage({
|
|
projectId: "thread-sync-demo",
|
|
sender: "device",
|
|
senderLabel: "线程执行器",
|
|
body: "已完成手机端排序修复",
|
|
kind: "text",
|
|
});
|
|
assert.equal(message.body, "已完成手机端排序修复");
|
|
|
|
const after = (await readState()) as MutableBossState;
|
|
const events = after.threadProgressEvents.filter((event) => event.projectId === "thread-sync-demo");
|
|
assert.equal(events.length, beforeCount + 1);
|
|
assert.equal(events[0]?.summary, "已完成手机端排序修复");
|
|
assert.equal(events[0]?.eventType, "progress_updated");
|
|
assert.equal(
|
|
after.masterAgentTasks.some(
|
|
(task) =>
|
|
task.projectUnderstandingTargetProjectId === "thread-sync-demo" && task.status === "queued",
|
|
),
|
|
false,
|
|
);
|
|
});
|