

























@@ -96,6 +96,7 @@ type ShortTermPhaseSignalEntry = {
9696remHits: number;
9797lastLightAt?: string;
9898lastRemAt?: string;
99+lastRemConsideredAt?: string;
99100};
100101101102type ShortTermPhaseSignalStore = {
@@ -831,12 +832,17 @@ function normalizePhaseSignalStore(raw: unknown, nowIso: string): ShortTermPhase
831832typeof entry.lastRemAt === "string" && entry.lastRemAt.trim().length > 0
832833 ? entry.lastRemAt
833834 : undefined;
835+const lastRemConsideredAt =
836+typeof entry.lastRemConsideredAt === "string" && entry.lastRemConsideredAt.trim().length > 0
837+ ? entry.lastRemConsideredAt
838+ : undefined;
834839entries[key] = {
835840 key,
836841 lightHits,
837842 remHits,
838843 ...(lastLightAt ? { lastLightAt } : {}),
839844 ...(lastRemAt ? { lastRemAt } : {}),
845+ ...(lastRemConsideredAt ? { lastRemConsideredAt } : {}),
840846};
841847}
842848return {
@@ -1222,6 +1228,86 @@ export async function recordDreamingPhaseSignals(params: {
12221228});
12231229}
122412301231+export async function recordRemConsideredPhaseSignals(params: {
1232+workspaceDir?: string;
1233+keys: string[];
1234+nowMs?: number;
1235+}): Promise<void> {
1236+const workspaceDir = params.workspaceDir?.trim();
1237+if (!workspaceDir) {
1238+return;
1239+}
1240+const keys = [...new Set(params.keys.map((key) => key.trim()).filter(Boolean))];
1241+if (keys.length === 0) {
1242+return;
1243+}
1244+const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now();
1245+const nowIso = new Date(nowMs).toISOString();
1246+1247+await withShortTermLock(workspaceDir, async () => {
1248+const [store, phaseSignals] = await Promise.all([
1249+readStore(workspaceDir, nowIso),
1250+readPhaseSignalStore(workspaceDir, nowIso),
1251+]);
1252+const knownKeys = new Set(Object.keys(store.entries));
1253+1254+for (const key of keys) {
1255+if (!knownKeys.has(key)) {
1256+continue;
1257+}
1258+const entry = phaseSignals.entries[key] ?? {
1259+ key,
1260+lightHits: 0,
1261+remHits: 0,
1262+};
1263+entry.lastRemConsideredAt = nowIso;
1264+phaseSignals.entries[key] = entry;
1265+}
1266+1267+for (const [key, entry] of Object.entries(phaseSignals.entries)) {
1268+if (!knownKeys.has(key) || (entry.lightHits <= 0 && entry.remHits <= 0)) {
1269+delete phaseSignals.entries[key];
1270+}
1271+}
1272+1273+phaseSignals.updatedAt = nowIso;
1274+await writePhaseSignalStore(workspaceDir, phaseSignals);
1275+});
1276+}
1277+1278+export async function readLightStagedKeys(params: {
1279+workspaceDir: string;
1280+nowMs?: number;
1281+}): Promise<Set<string>> {
1282+const workspaceDir = params.workspaceDir?.trim();
1283+if (!workspaceDir) {
1284+return new Set();
1285+}
1286+const nowMs = Number.isFinite(params.nowMs) ? (params.nowMs as number) : Date.now();
1287+const nowIso = new Date(nowMs).toISOString();
1288+const store = await readPhaseSignalStore(workspaceDir, nowIso);
1289+const keys = new Set<string>();
1290+for (const [key, entry] of Object.entries(store.entries)) {
1291+if (entry.lightHits <= 0) {
1292+continue;
1293+}
1294+const lastLightMs = Date.parse(entry.lastLightAt ?? "");
1295+const lastRemMs = Date.parse(entry.lastRemAt ?? "");
1296+const lastRemConsideredMs = Date.parse(entry.lastRemConsideredAt ?? "");
1297+const lastConsumedMs = Math.max(
1298+Number.isFinite(lastRemMs) ? lastRemMs : Number.NEGATIVE_INFINITY,
1299+Number.isFinite(lastRemConsideredMs) ? lastRemConsideredMs : Number.NEGATIVE_INFINITY,
1300+);
1301+const hasPendingLightSignal = Number.isFinite(lastLightMs)
1302+ ? lastLightMs > lastConsumedMs
1303+ : !entry.lastRemAt;
1304+if (hasPendingLightSignal) {
1305+keys.add(key);
1306+}
1307+}
1308+return keys;
1309+}
1310+12251311export async function rankShortTermPromotionCandidates(
12261312options: RankShortTermPromotionOptions,
12271313): Promise<PromotionCandidate[]> {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。