Phase 14: defer reinject plan without registry at compaction — B-002 stage 1
planDeferredReinject locks pending by kept-window only; defer path in index uses it while immediate keeps registered filter at compact time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+5
-1
@@ -23,6 +23,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
applyPendingReinjectAfterCompact,
|
applyPendingReinjectAfterCompact,
|
||||||
clearPendingReinjectOnUserPrompt,
|
clearPendingReinjectOnUserPrompt,
|
||||||
|
planDeferredReinject,
|
||||||
planReinject,
|
planReinject,
|
||||||
sendImmediateReinjectAllFollowUp,
|
sendImmediateReinjectAllFollowUp,
|
||||||
sendImmediateReinjectIdle,
|
sendImmediateReinjectIdle,
|
||||||
@@ -102,7 +103,6 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
const trackedNames = state.skills.map((skill) => skill.name);
|
const trackedNames = state.skills.map((skill) => skill.name);
|
||||||
const keptEntries = getKeptEntries(branch, event.compactionEntry.firstKeptEntryId);
|
const keptEntries = getKeptEntries(branch, event.compactionEntry.firstKeptEntryId);
|
||||||
const keptPresent = skillsPresentInKeptWindow(keptEntries, trackedNames);
|
const keptPresent = skillsPresentInKeptWindow(keptEntries, trackedNames);
|
||||||
const planned = planReinject(state, settings, ctx, event, skills);
|
|
||||||
const shouldReinject = consumeCompactionOnSessionCompact(
|
const shouldReinject = consumeCompactionOnSessionCompact(
|
||||||
compactionRuntime,
|
compactionRuntime,
|
||||||
state,
|
state,
|
||||||
@@ -110,6 +110,10 @@ export default function skillReinject(pi: ExtensionAPI): void {
|
|||||||
settings,
|
settings,
|
||||||
);
|
);
|
||||||
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride);
|
||||||
|
const planned =
|
||||||
|
deliveryMode === "defer"
|
||||||
|
? planDeferredReinject(state, ctx, event)
|
||||||
|
: planReinject(state, settings, ctx, event, skills);
|
||||||
|
|
||||||
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned);
|
||||||
notifyReinjectDiag(
|
notifyReinjectDiag(
|
||||||
|
|||||||
+30
-13
@@ -9,6 +9,7 @@ import { existsSync } from "node:fs";
|
|||||||
import { expandSkill } from "./expand.js";
|
import { expandSkill } from "./expand.js";
|
||||||
import {
|
import {
|
||||||
filterSkillsNeedingReinject,
|
filterSkillsNeedingReinject,
|
||||||
|
filterSkillsNeedingReinjectByKept,
|
||||||
getKeptEntries,
|
getKeptEntries,
|
||||||
skillsPresentInKeptWindow,
|
skillsPresentInKeptWindow,
|
||||||
} from "./kept.js";
|
} from "./kept.js";
|
||||||
@@ -57,6 +58,31 @@ export function registeredSkillNames(skills: readonly Pick<Skill, "name">[]): Re
|
|||||||
return new Set(skills.map((skill) => skill.name));
|
return new Set(skills.map((skill) => skill.name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Kept-window skill names present after compaction (shared by plan helpers). */
|
||||||
|
function keptPresentAfterCompaction(
|
||||||
|
state: ExtensionState,
|
||||||
|
ctx: ExtensionContext,
|
||||||
|
compactionEvent: SessionCompactEvent,
|
||||||
|
): Set<string> {
|
||||||
|
const branch = ctx.sessionManager.getBranch();
|
||||||
|
const keptEntries = getKeptEntries(branch, compactionEvent.compactionEntry.firstKeptEntryId);
|
||||||
|
const trackedNames = state.skills.map((skill) => skill.name);
|
||||||
|
return skillsPresentInKeptWindow(keptEntries, trackedNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defer planning at compaction: tracked skills absent from kept window only (SPEC §6.5.1).
|
||||||
|
* Registry filter runs later on before_agent_start (Phase 14 / B-002).
|
||||||
|
*/
|
||||||
|
export function planDeferredReinject(
|
||||||
|
state: ExtensionState,
|
||||||
|
ctx: ExtensionContext,
|
||||||
|
compactionEvent: SessionCompactEvent,
|
||||||
|
): string[] {
|
||||||
|
const keptPresent = keptPresentAfterCompaction(state, ctx, compactionEvent);
|
||||||
|
return filterSkillsNeedingReinjectByKept(state.skills, keptPresent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skill names to re-inject after compaction: tracked, absent from kept window, still registered (SPEC §5.2).
|
* Skill names to re-inject after compaction: tracked, absent from kept window, still registered (SPEC §5.2).
|
||||||
* `registeredSkills` comes from resourceLoader — ExtensionContext has no getSkills(); wired in index.ts.
|
* `registeredSkills` comes from resourceLoader — ExtensionContext has no getSkills(); wired in index.ts.
|
||||||
@@ -68,10 +94,7 @@ export function planReinject(
|
|||||||
compactionEvent: SessionCompactEvent,
|
compactionEvent: SessionCompactEvent,
|
||||||
registeredSkills: readonly Pick<Skill, "name">[],
|
registeredSkills: readonly Pick<Skill, "name">[],
|
||||||
): string[] {
|
): string[] {
|
||||||
const branch = ctx.sessionManager.getBranch();
|
const keptPresent = keptPresentAfterCompaction(state, ctx, compactionEvent);
|
||||||
const keptEntries = getKeptEntries(branch, compactionEvent.compactionEntry.firstKeptEntryId);
|
|
||||||
const trackedNames = state.skills.map((skill) => skill.name);
|
|
||||||
const keptPresent = skillsPresentInKeptWindow(keptEntries, trackedNames);
|
|
||||||
return filterSkillsNeedingReinject(
|
return filterSkillsNeedingReinject(
|
||||||
state.skills,
|
state.skills,
|
||||||
keptPresent,
|
keptPresent,
|
||||||
@@ -82,18 +105,12 @@ export function planReinject(
|
|||||||
/** Defer path on session_compact: queue planned skills without sendUserMessage (SPEC §6.5.1, §16.2). */
|
/** Defer path on session_compact: queue planned skills without sendUserMessage (SPEC §6.5.1, §16.2). */
|
||||||
export function enqueueDeferredReinjectFromCompact(
|
export function enqueueDeferredReinjectFromCompact(
|
||||||
state: ExtensionState,
|
state: ExtensionState,
|
||||||
settings: SkillReinjectSettings,
|
_settings: SkillReinjectSettings,
|
||||||
ctx: ExtensionContext,
|
ctx: ExtensionContext,
|
||||||
compactionEvent: SessionCompactEvent,
|
compactionEvent: SessionCompactEvent,
|
||||||
registeredSkills: readonly Pick<Skill, "name">[],
|
_registeredSkills: readonly Pick<Skill, "name">[],
|
||||||
): void {
|
): void {
|
||||||
state.pendingReinject = planReinject(
|
state.pendingReinject = planDeferredReinject(state, ctx, compactionEvent);
|
||||||
state,
|
|
||||||
settings,
|
|
||||||
ctx,
|
|
||||||
compactionEvent,
|
|
||||||
registeredSkills,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { planReinject } from "../src/reinject.js";
|
import { planDeferredReinject, planReinject } from "../src/reinject.js";
|
||||||
import { createDefaultSettings } from "../src/settings.js";
|
import { createDefaultSettings } from "../src/settings.js";
|
||||||
import { createInitialState, trackSkill } from "../src/state.js";
|
import { createInitialState, trackSkill } from "../src/state.js";
|
||||||
|
|
||||||
@@ -67,4 +67,32 @@ describe("B-002 pre-fix filter hypothesis", () => {
|
|||||||
|
|
||||||
expect(planned).toEqual([]);
|
expect(planned).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("defer plan includes skill absent from kept even when registered is empty", () => {
|
||||||
|
const state = createInitialState();
|
||||||
|
trackSkill(state, {
|
||||||
|
name: "fup-blame-commits",
|
||||||
|
filePath: "/home/user/.cursor/skills/fup-blame-commits/SKILL.md",
|
||||||
|
baseDir: "/home/user/.cursor/skills/fup-blame-commits",
|
||||||
|
source: "skill-block",
|
||||||
|
});
|
||||||
|
|
||||||
|
const branch = [
|
||||||
|
{
|
||||||
|
id: "keep-1",
|
||||||
|
type: "message",
|
||||||
|
message: { role: "user", content: "plain text after compact" },
|
||||||
|
},
|
||||||
|
] as never;
|
||||||
|
|
||||||
|
const planned = planDeferredReinject(
|
||||||
|
state,
|
||||||
|
{
|
||||||
|
sessionManager: { getBranch: () => branch },
|
||||||
|
} as never,
|
||||||
|
{ compactionEntry: { firstKeptEntryId: "keep-1" } } as never,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(planned).toEqual(["fup-blame-commits"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user