Phase 14: split kept-window filter from registry gate — defer planning stage

filterSkillsNeedingReinjectByKept tracks absent skills without registeredNames; immediate path keeps the combined filter.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 17:28:32 +07:00
parent aca68e73ee
commit fe25e606b8
2 changed files with 35 additions and 7 deletions
+17 -7
View File
@@ -56,17 +56,27 @@ export function skillsPresentInKeptWindow(
return present; return present;
} }
/** Tracked skills absent from kept window — defer planning stage without registry filter (Phase 14 / B-002). */
export function filterSkillsNeedingReinjectByKept(
tracked: readonly TrackedSkill[],
keptPresent: ReadonlySet<string>,
): string[] {
const needing: string[] = [];
for (const skill of tracked) {
if (!keptPresent.has(skill.name)) {
needing.push(skill.name);
}
}
return needing;
}
/** Tracked skills missing from kept window but still registered (SPEC §5.2, §6.4). */ /** Tracked skills missing from kept window but still registered (SPEC §5.2, §6.4). */
export function filterSkillsNeedingReinject( export function filterSkillsNeedingReinject(
tracked: readonly TrackedSkill[], tracked: readonly TrackedSkill[],
keptPresent: ReadonlySet<string>, keptPresent: ReadonlySet<string>,
registeredNames: ReadonlySet<string>, registeredNames: ReadonlySet<string>,
): string[] { ): string[] {
const needing: string[] = []; return filterSkillsNeedingReinjectByKept(tracked, keptPresent).filter((name) =>
for (const skill of tracked) { registeredNames.has(name),
if (registeredNames.has(skill.name) && !keptPresent.has(skill.name)) { );
needing.push(skill.name);
}
}
return needing;
} }
+18
View File
@@ -2,6 +2,7 @@ import type { SessionEntry } from "@earendil-works/pi-coding-agent";
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { import {
filterSkillsNeedingReinject, filterSkillsNeedingReinject,
filterSkillsNeedingReinjectByKept,
getKeptEntries, getKeptEntries,
skillsPresentInKeptWindow, skillsPresentInKeptWindow,
} from "../src/kept"; } from "../src/kept";
@@ -95,6 +96,23 @@ describe("skillsPresentInKeptWindow", () => {
}); });
}); });
describe("filterSkillsNeedingReinjectByKept", () => {
it("returns tracked skills absent from kept window regardless of registration", () => {
const keptPresent = new Set(["alpha"]);
expect(filterSkillsNeedingReinjectByKept(trackedSkills, keptPresent)).toEqual(["beta"]);
});
it("preserves tracked order including unregistered names", () => {
const keptPresent = new Set<string>();
expect(filterSkillsNeedingReinjectByKept(trackedSkills, keptPresent)).toEqual(["alpha", "beta"]);
});
it("returns empty when all tracked skills are in kept window", () => {
const keptPresent = new Set(["alpha", "beta"]);
expect(filterSkillsNeedingReinjectByKept(trackedSkills, keptPresent)).toEqual([]);
});
});
describe("filterSkillsNeedingReinject", () => { describe("filterSkillsNeedingReinject", () => {
it("returns registered tracked skills absent from kept window", () => { it("returns registered tracked skills absent from kept window", () => {
const keptPresent = new Set(["alpha"]); const keptPresent = new Set(["alpha"]);