feat(FN-4168): complete Step 4 — hydrate stalled review signal
Fusion-Task-Id: FN-4168 Fusion-Task-Lineage: 781ffc66-cb1c-42b9-85e1-e5cbe0655863
This commit is contained in:
41
packages/core/src/__tests__/store-stalled-review.test.ts
Normal file
41
packages/core/src/__tests__/store-stalled-review.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { tmpdir } from "node:os";
|
||||
|
||||
import { TaskStore } from "../store.js";
|
||||
|
||||
describe("TaskStore stalledReview hydration", () => {
|
||||
let rootDir: string;
|
||||
let globalDir: string;
|
||||
let store: TaskStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
rootDir = await mkdtemp(join(tmpdir(), "store-stalled-review-"));
|
||||
globalDir = join(rootDir, ".fusion-global-settings");
|
||||
store = new TaskStore(rootDir, globalDir, { inMemoryDb: true });
|
||||
await store.init();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await store.close();
|
||||
await rm(rootDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it("populates stalledReview on slim listings when reenqueue churn threshold is met", async () => {
|
||||
const task = await store.createTask({
|
||||
description: "stalled review candidate",
|
||||
column: "in-review",
|
||||
});
|
||||
|
||||
for (let i = 0; i < 3; i += 1) {
|
||||
await store.logEntry(task.id, "Auto-recovered: eligible in-review task re-enqueued for merge");
|
||||
}
|
||||
|
||||
const slimTasks = await store.listTasks({ slim: true, column: "in-review" });
|
||||
const hydrated = slimTasks.find((entry) => entry.id === task.id);
|
||||
|
||||
expect(hydrated?.stalledReview?.heuristic).toBe("reenqueue-churn");
|
||||
expect(hydrated?.stalledReview?.matchCount).toBe(3);
|
||||
});
|
||||
});
|
||||
@@ -30,6 +30,7 @@ import { sanitizeTitle } from "./ai-summarize.js";
|
||||
import { assertProjectRootDir } from "./project-root-guard.js";
|
||||
import { generateTaskLineageId, normalizeTaskCommitAssociation } from "./task-lineage.js";
|
||||
import { createDistributedTaskIdAllocator, reconcileTaskIdState, resolveLocalNodeId, type DistributedTaskIdAllocator } from "./distributed-task-id.js";
|
||||
import { detectStalledReview } from "./stalled-review-detector.js";
|
||||
import {
|
||||
detectTaskIdIntegrityAnomalies,
|
||||
type TaskIdIntegrityReport,
|
||||
@@ -3084,7 +3085,9 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
// the board card has no way to display the same total-execution
|
||||
// figure that the task detail panel shows.
|
||||
if (slim) {
|
||||
const now = Date.now();
|
||||
task.timedExecutionMs = this.computeTimedExecutionMs(task.log);
|
||||
task.stalledReview = detectStalledReview(task, { now });
|
||||
task.log = [];
|
||||
task.githubTracking = undefined;
|
||||
}
|
||||
@@ -3164,6 +3167,7 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
const task = this.rowToTask(row);
|
||||
task.inReviewStall = getInReviewStallReason(task, { now });
|
||||
task.timedExecutionMs = this.computeTimedExecutionMs(task.log);
|
||||
task.stalledReview = detectStalledReview(task, { now });
|
||||
task.log = [];
|
||||
task.githubTracking = undefined;
|
||||
return task;
|
||||
@@ -3277,7 +3281,9 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
// Slim path mirrors `listTasks`: aggregate timed execution server-side
|
||||
// before stripping the heavy log payload from the wire response.
|
||||
if (slim) {
|
||||
const now = Date.now();
|
||||
task.timedExecutionMs = this.computeTimedExecutionMs(task.log);
|
||||
task.stalledReview = detectStalledReview(task, { now });
|
||||
task.log = [];
|
||||
task.githubTracking = undefined;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user