diff --git a/src/index.ts b/src/index.ts index 0d61010..e1661d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,8 +16,8 @@ import { } from "./compaction.js"; import { detectSlashSkill, matchReadPathToSkillWhenEnabled, parseSkillBlocksFromText, userMessageText } from "./detect.js"; import { + applyPendingReinjectAfterCompact, clearPendingReinjectOnUserPrompt, - enqueueDeferredReinjectFromCompact, planReinject, sendImmediateReinjectAllFollowUp, sendImmediateReinjectIdle, @@ -91,34 +91,36 @@ export default function skillReinject(pi: ExtensionAPI): void { function handleSessionCompact(event: SessionCompactEvent, ctx: ExtensionContext): void { const settings = readSettings(ctx); + const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills); + const planned = planReinject(state, settings, ctx, event, skills); const shouldReinject = consumeCompactionOnSessionCompact( compactionRuntime, state, state.sessionOverride, settings, ); + const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride); + + if (deliveryMode === "defer") { + applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned); + persistState(); + return; + } + + applyPendingReinjectAfterCompact(state, compactionRuntime, shouldReinject, planned); if (!shouldReinject) { persistState(); return; } - const skills = resolveRegisteredSkills(ctx.cwd, registeredSkills); - const deliveryMode = resolveDeliveryMode(settings, runtime, state.sessionIntegrationOverride); - if (deliveryMode === "defer") { - enqueueDeferredReinjectFromCompact(state, settings, ctx, event, skills); - persistState(); - return; - } - - const skillNames = planReinject(state, settings, ctx, event, skills); - if (skillNames.length === 0) { + if (planned.length === 0) { persistState(); return; } if (ctx.isIdle()) { - sendImmediateReinjectIdle(pi, skillNames, state, settings, skills, ctx); + sendImmediateReinjectIdle(pi, planned, state, settings, skills, ctx); } else { - sendImmediateReinjectAllFollowUp(pi, skillNames, state, settings, skills, ctx); + sendImmediateReinjectAllFollowUp(pi, planned, state, settings, skills, ctx); } persistState(); } diff --git a/src/reinject.ts b/src/reinject.ts index 60252b4..8799ff4 100644 --- a/src/reinject.ts +++ b/src/reinject.ts @@ -96,6 +96,25 @@ export function enqueueDeferredReinjectFromCompact( ); } +/** + * Update pending queue after session_compact; each compact recalculates or clears (SPEC §16.6). + * Manual compaction with default settings keeps stale pending until the next user prompt. + */ +export function applyPendingReinjectAfterCompact( + state: ExtensionState, + compactionRuntime: CompactionRuntime, + shouldReinject: boolean, + planned: readonly string[], +): void { + if (shouldReinject) { + state.pendingReinject = [...planned]; + return; + } + if (!compactionRuntime.clearPendingReinjectOnNextUserInput) { + state.pendingReinject = []; + } +} + /** Warn when re-injecting many skills; unlimited by default with soft warn above 3 (SPEC §15). */ export function maybeWarnManySkills( skillCount: number, diff --git a/test/reinject-double-compact.test.ts b/test/reinject-double-compact.test.ts new file mode 100644 index 0000000..36920c8 --- /dev/null +++ b/test/reinject-double-compact.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from "vitest"; +import { createCompactionRuntime } from "../src/compaction"; +import { applyPendingReinjectAfterCompact } from "../src/reinject"; +import { createInitialState } from "../src/state"; + +describe("applyPendingReinjectAfterCompact", () => { + it("replaces pending with a new plan on each compact", () => { + const runtime = createCompactionRuntime(); + const state = createInitialState(); + state.pendingReinject = ["alpha"]; + + applyPendingReinjectAfterCompact(state, runtime, true, ["beta", "gamma"]); + + expect(state.pendingReinject).toEqual(["beta", "gamma"]); + }); + + it("clears pending when reinject is skipped", () => { + const runtime = createCompactionRuntime(); + const state = createInitialState(); + state.pendingReinject = ["alpha"]; + + applyPendingReinjectAfterCompact(state, runtime, false, ["beta"]); + + expect(state.pendingReinject).toEqual([]); + }); + + it("keeps stale pending when manual compaction scheduled a user-prompt clear", () => { + const runtime = createCompactionRuntime(); + runtime.clearPendingReinjectOnNextUserInput = true; + const state = createInitialState(); + state.pendingReinject = ["alpha"]; + + applyPendingReinjectAfterCompact(state, runtime, false, ["beta"]); + + expect(state.pendingReinject).toEqual(["alpha"]); + }); +});