36cafc4405
Covers layered settings merge, incremental global file writes, and delivery-mode resolution per SPEC §6.5.3. Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
4.7 KiB
TypeScript
162 lines
4.7 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
import { tmpdir } from "os";
|
|
import { join } from "path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
DEFAULT_SKILL_REINJECT_SETTINGS,
|
|
SKILL_REINJECT_SETTINGS_KEY,
|
|
createDefaultSettings,
|
|
effectiveEnabled,
|
|
effectiveIntegration,
|
|
mergeSkillReinjectIntoSettingsFile,
|
|
mergeSkillReinjectSettings,
|
|
parseSkillReinjectPartial,
|
|
} from "../src/settings";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(() => {
|
|
for (const dir of tempDirs.splice(0)) {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
function tempSettingsPath(): string {
|
|
const dir = mkdtempSync(join(tmpdir(), "pi-skill-reinject-settings-"));
|
|
tempDirs.push(dir);
|
|
return join(dir, "settings.json");
|
|
}
|
|
|
|
describe("createDefaultSettings", () => {
|
|
it("matches SPEC §7.3 defaults", () => {
|
|
expect(createDefaultSettings()).toEqual(DEFAULT_SKILL_REINJECT_SETTINGS);
|
|
});
|
|
});
|
|
|
|
describe("parseSkillReinjectPartial", () => {
|
|
it("ignores invalid shapes and fields", () => {
|
|
expect(parseSkillReinjectPartial(null)).toEqual({});
|
|
expect(parseSkillReinjectPartial([])).toEqual({});
|
|
expect(
|
|
parseSkillReinjectPartial({
|
|
enabled: "yes",
|
|
autoCompactIntegration: "bogus",
|
|
extra: true,
|
|
}),
|
|
).toEqual({});
|
|
});
|
|
|
|
it("keeps valid fields only", () => {
|
|
expect(
|
|
parseSkillReinjectPartial({
|
|
enabled: true,
|
|
suffix: "custom",
|
|
}),
|
|
).toEqual({ enabled: true, suffix: "custom" });
|
|
});
|
|
});
|
|
|
|
describe("mergeSkillReinjectSettings", () => {
|
|
it("applies global then project overrides on defaults", () => {
|
|
expect(
|
|
mergeSkillReinjectSettings({ enabled: true }, { trackReadPaths: false }),
|
|
).toEqual({
|
|
...DEFAULT_SKILL_REINJECT_SETTINGS,
|
|
enabled: true,
|
|
trackReadPaths: false,
|
|
});
|
|
});
|
|
|
|
it("lets project win over global for the same field", () => {
|
|
expect(
|
|
mergeSkillReinjectSettings({ enabled: true }, { enabled: false }),
|
|
).toEqual({
|
|
...DEFAULT_SKILL_REINJECT_SETTINGS,
|
|
enabled: false,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("mergeSkillReinjectIntoSettingsFile", () => {
|
|
it("merges skillReinject fields across successive writes", () => {
|
|
const path = tempSettingsPath();
|
|
mergeSkillReinjectIntoSettingsFile(path, { enabled: true });
|
|
mergeSkillReinjectIntoSettingsFile(path, { triggerTurn: true });
|
|
|
|
const saved = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
|
|
expect(saved[SKILL_REINJECT_SETTINGS_KEY]).toEqual({
|
|
enabled: true,
|
|
triggerTurn: true,
|
|
});
|
|
});
|
|
|
|
it("preserves existing top-level keys", () => {
|
|
const path = tempSettingsPath();
|
|
writeFileSync(
|
|
path,
|
|
`${JSON.stringify({ theme: "light", compaction: { enabled: false } }, null, 2)}\n`,
|
|
"utf8",
|
|
);
|
|
mergeSkillReinjectIntoSettingsFile(path, { enabled: true, suffix: "x" });
|
|
|
|
const saved = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
|
|
expect(saved.theme).toBe("light");
|
|
expect(saved.compaction).toEqual({ enabled: false });
|
|
expect(saved[SKILL_REINJECT_SETTINGS_KEY]).toEqual({
|
|
enabled: true,
|
|
suffix: "x",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("effectiveEnabled", () => {
|
|
const settings = createDefaultSettings();
|
|
|
|
it("uses session override when set", () => {
|
|
expect(effectiveEnabled(true, settings)).toBe(true);
|
|
expect(effectiveEnabled(false, { ...settings, enabled: true })).toBe(false);
|
|
});
|
|
|
|
it("falls back to global enabled when override is null", () => {
|
|
expect(effectiveEnabled(null, settings)).toBe(false);
|
|
expect(effectiveEnabled(null, { ...settings, enabled: true })).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("effectiveIntegration", () => {
|
|
const base = createDefaultSettings();
|
|
|
|
it("honors explicit defer and immediate", () => {
|
|
expect(
|
|
effectiveIntegration({ ...base, autoCompactIntegration: "defer" }, false),
|
|
).toBe("defer");
|
|
expect(
|
|
effectiveIntegration({ ...base, autoCompactIntegration: "immediate" }, true),
|
|
).toBe("immediate");
|
|
});
|
|
|
|
it("uses triggerTurn when integration is off", () => {
|
|
expect(
|
|
effectiveIntegration({ ...base, autoCompactIntegration: "off", triggerTurn: false }, true),
|
|
).toBe("defer");
|
|
expect(
|
|
effectiveIntegration({ ...base, autoCompactIntegration: "off", triggerTurn: true }, true),
|
|
).toBe("immediate");
|
|
});
|
|
|
|
it("defers when auto mode detects pi-auto-compact", () => {
|
|
expect(effectiveIntegration(base, true)).toBe("defer");
|
|
});
|
|
|
|
it("uses triggerTurn in auto mode without pi-auto-compact", () => {
|
|
expect(effectiveIntegration({ ...base, triggerTurn: false }, false)).toBe("defer");
|
|
expect(effectiveIntegration({ ...base, triggerTurn: true }, false)).toBe("immediate");
|
|
});
|
|
|
|
it("prefers session integration override", () => {
|
|
expect(
|
|
effectiveIntegration({ ...base, autoCompactIntegration: "immediate" }, false, "defer"),
|
|
).toBe("defer");
|
|
});
|
|
});
|