fix(FN-4401): address snapshot review feedback

Fusion-Task-Id: FN-4401
Fusion-Task-Lineage: c1b6c497-b22c-48d5-b1c8-299877bf09ac
This commit is contained in:
Fusion
2026-05-14 00:17:18 -07:00
committed by gsxdsm
parent 90ed849e07
commit 422e598a84
2 changed files with 27 additions and 7 deletions

View File

@@ -1,8 +1,8 @@
import { describe, expect, it, vi } from "vitest";
import type { TaskDetail } from "@fusion/core";
import type { Task } from "@fusion/core";
import { AutoClaimSnapshotManager, extractDescriptionFirstLine } from "../auto-claim-snapshot.js";
function makeTask(overrides: Partial<TaskDetail> & Pick<TaskDetail, "id">): TaskDetail {
function makeTask(overrides: Partial<Task> & Pick<Task, "id">): Task {
return {
id: overrides.id,
title: overrides.title ?? null,
@@ -20,7 +20,7 @@ function makeTask(overrides: Partial<TaskDetail> & Pick<TaskDetail, "id">): Task
checkedOutBy: overrides.checkedOutBy,
paused: overrides.paused,
columnMovedAt: overrides.columnMovedAt,
} as TaskDetail;
} as Task;
}
describe("AutoClaimSnapshotManager", () => {
@@ -87,6 +87,22 @@ describe("AutoClaimSnapshotManager", () => {
expect(snapshot.tasks.map((t) => t.id)).toEqual(["FN-1", "FN-2", "FN-3"]);
});
it("caps candidate set to 50 and computes capped baseScore", async () => {
const tasks = Array.from({ length: 55 }, (_, idx) => makeTask({
id: `FN-${idx + 1}`,
createdAt: "2025-12-01T00:00:00.000Z",
}));
const manager = new AutoClaimSnapshotManager({
taskStore: { listTasks: vi.fn(async () => tasks) },
now: () => Date.parse("2026-01-03T00:00:00.000Z"),
});
const snapshot = await manager.getSnapshot();
expect(snapshot.tasks).toHaveLength(50);
expect(snapshot.tasks[0]?.baseScore).toBe(5);
});
it("extracts first non-empty description line and caps length", () => {
expect(extractDescriptionFirstLine("\n\nfirst line\nsecond line")).toBe("first line");
expect(extractDescriptionFirstLine(" \n\t\n")).toBe("");

View File

@@ -1,4 +1,4 @@
import type { TaskDetail, TaskStore } from "@fusion/core";
import type { Task, TaskStore } from "@fusion/core";
import { createLogger, type Logger } from "./logger.js";
/**
@@ -13,7 +13,7 @@ export interface AutoClaimCandidate {
createdAt: string;
columnMovedAt?: string;
baseScore: number;
column: TaskDetail["column"];
column: Task["column"];
}
export interface AutoClaimSnapshot {
@@ -37,6 +37,7 @@ export class AutoClaimSnapshotManager {
private readonly now: () => number;
private cache: AutoClaimSnapshot | null = null;
private staleReason: "ttl" | "invalidate" = "ttl";
private invalidatedAt = 0;
private inFlight: Promise<AutoClaimSnapshot> | null = null;
constructor({ taskStore, ttlMs = 30_000, logger = autoClaimSnapshotLog, now = Date.now }: AutoClaimSnapshotManagerOptions) {
@@ -49,6 +50,7 @@ export class AutoClaimSnapshotManager {
invalidate(reason: string): void {
this.cache = null;
this.staleReason = "invalidate";
this.invalidatedAt = this.now();
this.logger.log(`invalidate reason=${reason}`);
}
@@ -61,10 +63,12 @@ export class AutoClaimSnapshotManager {
return this.inFlight;
}
const startedAt = this.now();
this.inFlight = this.rebuild();
try {
const next = await this.inFlight;
this.cache = next;
const invalidatedDuringRebuild = this.invalidatedAt > startedAt;
this.cache = invalidatedDuringRebuild ? null : next;
return next;
} finally {
this.inFlight = null;
@@ -105,7 +109,7 @@ export class AutoClaimSnapshotManager {
return snapshot;
}
private toCandidate(task: TaskDetail, now: number): AutoClaimCandidate {
private toCandidate(task: Task, now: number): AutoClaimCandidate {
const reference = task.columnMovedAt ?? task.createdAt;
const ageMs = Math.max(0, now - Date.parse(reference));
const ageHours = ageMs / (1000 * 60 * 60);