import assert from "node:assert/strict"; import test from "node:test"; import { formatSkillsForPrompt, type Skill, type SlashCommandInfo, type ToolInfo } from "@earendil-works/pi-coding-agent"; import { applyToolToggle, buildSettingItems, readStateFromBranch, replaceSkillsSection, type ContextState, } from "../extensions/context-control.js"; const sourceInfo = (path: string) => ({ path, source: "local", scope: "user", origin: "top-level", baseDir: path.replace(/\/[^/]+$/, ""), }) as const; const skill = (name: string): Skill => ({ name, description: `${name} description`, filePath: `/skills/${name}/SKILL.md`, baseDir: `/skills/${name}`, disableModelInvocation: false, sourceInfo: sourceInfo(`/skills/${name}/SKILL.md`), }); const tool = (name: string): ToolInfo => ({ name, description: `${name} tool`, parameters: {} as ToolInfo["parameters"], sourceInfo: sourceInfo(`/extensions/${name}.ts`), }); const command = (name: string): SlashCommandInfo => ({ name, source: "extension", description: `${name} command`, sourceInfo: sourceInfo(`/extensions/${name}.ts`), }); test("replaceSkillsSection keeps only provided skills", () => { const enabled = skill("enabled-skill"); const disabled = skill("disabled-skill"); const prompt = `prefix${formatSkillsForPrompt([enabled, disabled])}\nCurrent date: 2026-07-02`; const next = replaceSkillsSection(prompt, [enabled]); assert.match(next, /enabled-skill/); assert.doesNotMatch(next, /disabled-skill/); assert.match(next, /Current date: 2026-07-02/); }); test("replaceSkillsSection removes the section when no skills remain", () => { const prompt = `prefix${formatSkillsForPrompt([skill("only-skill")])}\nCurrent date: 2026-07-02`; const next = replaceSkillsSection(prompt, []); assert.doesNotMatch(next, //); assert.equal(next, "prefix\nCurrent date: 2026-07-02"); }); test("replaceSkillsSection leaves prompts without skills untouched", () => { const prompt = "plain system prompt"; assert.equal(replaceSkillsSection(prompt, [skill("unused")]), prompt); }); test("readStateFromBranch uses the latest valid state entry", () => { const first: ContextState = { disabledSkillNames: ["old"], disabledToolNames: ["old-tool"] }; const latest: ContextState = { disabledSkillNames: ["new"], disabledToolNames: ["new-tool"] }; assert.deepEqual( readStateFromBranch([ { type: "custom", customType: "context-control-state", data: first }, { type: "custom", customType: "other", data: { ignored: true } }, { type: "custom", customType: "context-control-state", data: { bad: true } }, { type: "custom", customType: "context-control-state", data: latest }, ]), latest, ); }); test("readStateFromBranch defaults to no disabled resources", () => { assert.deepEqual(readStateFromBranch([]), { disabledSkillNames: [], disabledToolNames: [] }); }); test("applyToolToggle removes disabled tools and restores enabled tools", () => { assert.deepEqual(applyToolToggle(["read", "custom"], "custom", true), ["read"]); assert.deepEqual(applyToolToggle(["read"], "custom", false), ["read", "custom"]); }); test("buildSettingItems marks skills, tools, and command-only extensions", () => { const items = buildSettingItems( [skill("tdd")], [tool("helper")], [command("exit")], ["read"], { disabledSkillNames: ["tdd"], disabledToolNames: [] }, ); assert.deepEqual( items.map(({ id, currentValue, values }) => ({ id, currentValue, values })), [ { id: "skill:tdd", currentValue: "disabled", values: ["enabled", "disabled"] }, { id: "tool:helper", currentValue: "disabled", values: ["enabled", "disabled"] }, { id: "command:exit", currentValue: "loaded", values: undefined }, ], ); });