Phase 14: deferred reinject regression tests — loose, strict, missing disk
test/reinject.test.ts gates B-002 defer-path filter and build behavior for requireRegistered and missing SKILL.md. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
|||||||
|
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
import { join } from "path";
|
||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { buildReinjectBlocks, tryConsumeDeferredReinject } from "../src/reinject";
|
||||||
|
import { createDefaultSettings } from "../src/settings";
|
||||||
|
import { createInitialState, trackSkill } from "../src/state";
|
||||||
|
|
||||||
|
const tempDirs: string[] = [];
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
for (const dir of tempDirs.splice(0)) {
|
||||||
|
rmSync(dir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function tempSkillDir(name: string): { filePath: string; baseDir: string } {
|
||||||
|
const root = mkdtempSync(join(tmpdir(), "pi-skill-reinject-deferred-"));
|
||||||
|
tempDirs.push(root);
|
||||||
|
const baseDir = join(root, name);
|
||||||
|
mkdirSync(baseDir, { recursive: true });
|
||||||
|
const filePath = join(baseDir, "SKILL.md");
|
||||||
|
writeFileSync(filePath, "# skill\n", "utf8");
|
||||||
|
return { filePath, baseDir };
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctxWithNotify(): { ctx: never; notify: ReturnType<typeof vi.fn> } {
|
||||||
|
const notify = vi.fn();
|
||||||
|
return { notify, ctx: { hasUI: true, ui: { notify } } as never };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("deferred reinject loose fallback", () => {
|
||||||
|
it("(a) unregistered on disk with requireRegistered=false → block + info notify", () => {
|
||||||
|
const { filePath, baseDir } = tempSkillDir("loose");
|
||||||
|
const state = createInitialState();
|
||||||
|
trackSkill(state, { name: "loose", filePath, baseDir, source: "slash" });
|
||||||
|
state.pendingReinject = ["loose"];
|
||||||
|
const { ctx, notify } = ctxWithNotify();
|
||||||
|
|
||||||
|
const result = tryConsumeDeferredReinject(state, createDefaultSettings(), [], ctx);
|
||||||
|
|
||||||
|
expect(result?.message?.content).toContain('<skill name="loose"');
|
||||||
|
expect(notify).toHaveBeenCalledWith('skill-reinject: re-injected "loose" from disk', "info");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(b) unregistered on disk with requireRegistered=true → skip + warn", () => {
|
||||||
|
const { filePath, baseDir } = tempSkillDir("strict");
|
||||||
|
const state = createInitialState();
|
||||||
|
trackSkill(state, { name: "strict", filePath, baseDir, source: "slash" });
|
||||||
|
const settings = createDefaultSettings();
|
||||||
|
settings.requireRegistered = true;
|
||||||
|
const { ctx, notify } = ctxWithNotify();
|
||||||
|
|
||||||
|
const blocks = buildReinjectBlocks(["strict"], state, settings, [], ctx);
|
||||||
|
|
||||||
|
expect(blocks).toEqual([]);
|
||||||
|
expect(notify).toHaveBeenCalledWith(
|
||||||
|
'skill-reinject: skipped "strict" — no longer registered',
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(c) missing filePath on disk → skip + warn", () => {
|
||||||
|
const state = createInitialState();
|
||||||
|
trackSkill(state, {
|
||||||
|
name: "missing",
|
||||||
|
filePath: "/no/such/SKILL.md",
|
||||||
|
baseDir: "/no/such",
|
||||||
|
source: "slash",
|
||||||
|
});
|
||||||
|
const { ctx, notify } = ctxWithNotify();
|
||||||
|
|
||||||
|
const blocks = buildReinjectBlocks(["missing"], state, createDefaultSettings(), [], ctx);
|
||||||
|
|
||||||
|
expect(blocks).toEqual([]);
|
||||||
|
expect(notify).toHaveBeenCalledWith(
|
||||||
|
'skill-reinject: skipped "missing" — SKILL.md not found on disk',
|
||||||
|
"warning",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user