1import {
2 PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS,
3 ProviderDriverKind,
4 ProviderInstanceId,
5 type ServerProvider,
6} from "@t3tools/contracts";
7import { formatProviderSkillDisplayName } from "@t3tools/shared/inlineSkills";
8import { describe, expect, it } from "vite-plus/test";
9
10import {
11 dedupeProviderSkillsByName,
12 getProviderSlashCommandsForSlashMenu,
13 getProviderSkillsForSlashMenu,
14 hasCompleteProviderWorkspaceSnapshot,
15 hasCurrentProviderWorkspaceSnapshot,
16 resolveProviderSkillsForCwd,
17 resolveProviderSlashCommandsForCwd,
18 resolveProviderSkillSourceKind,
19} from "./providerSkills.ts";
20
21const provider = {
22 instanceId: ProviderInstanceId.make("codex"),
23 driver: ProviderDriverKind.make("codex"),
24 enabled: true,
25 installed: true,
26 version: "1.0.0",
27 status: "ready",
28 auth: { status: "authenticated" },
29 checkedAt: "2026-01-01T00:00:00.000Z",
30 models: [],
31 slashCommands: [{ name: "global" }],
32 skills: [{ name: "global", path: "/global/SKILL.md", enabled: true }],
33 workspaceSnapshots: [
34 {
35 cwd: "/workspace/project-a",
36 checkedAt: "2026-01-01T00:01:00.000Z",
37 slashCommands: [{ name: "project" }],
38 skills: [{ name: "project", path: "/workspace/project-a/SKILL.md", enabled: true }],
39 },
40 ],
41} satisfies ServerProvider;
42
43describe("formatProviderSkillDisplayName", () => {
44 it("prefers the provider display name", () => {
45 expect(
46 formatProviderSkillDisplayName({
47 name: "review-follow-up",
48 displayName: "Review Follow-up",
49 }),
50 ).toBe("Review Follow-up");
51 });
52
53 it("falls back to a title-cased skill name", () => {
54 expect(
55 formatProviderSkillDisplayName({
56 name: "review-follow-up",
57 }),
58 ).toBe("Review Follow Up");
59 });
60});
61
62describe("dedupeProviderSkillsByName", () => {
63 it("keeps the first resolved skill and preserves unrelated skill order", () => {
64 const firstSkill = {
65 name: "branch-audit",
66 path: "/Users/matt/.codex/skills/branch-audit/SKILL.md",
67 enabled: true,
68 };
69 const otherSkill = {
70 name: "browser",
71 path: "/Users/matt/.agents/skills/browser/SKILL.md",
72 enabled: true,
73 };
74 const duplicateSkill = {
75 name: "Branch-Audit",
76 path: "/Users/matt/.agents/skills/branch-audit/SKILL.md",
77 enabled: true,
78 };
79
80 expect(dedupeProviderSkillsByName([firstSkill, otherSkill, duplicateSkill])).toEqual([
81 firstSkill,
82 otherSkill,
83 ]);
84 });
85});
86
87describe("getProviderSkillsForSlashMenu", () => {
88 it("keeps the skill alias when the provider also exposes it as a slash command", () => {
89 const askMatt = {
90 name: "ask-matt",
91 path: "/Users/matt/.agents/skills/ask-matt/SKILL.md",
92 enabled: true,
93 };
94 expect(getProviderSkillsForSlashMenu([askMatt], true).map((skill) => skill.name)).toEqual([
95 "ask-matt",
96 ]);
97 });
98
99 it("shows one row when enabled skills share a name", () => {
100 const skills = [
101 {
102 name: "babysit-pr",
103 path: "/Users/matt/.codex/skills/babysit-pr/SKILL.md",
104 enabled: true,
105 },
106 {
107 name: "browser",
108 path: "/Users/matt/.agents/skills/browser/SKILL.md",
109 enabled: true,
110 },
111 {
112 name: "babysit-pr",
113 path: "/Users/matt/.agents/skills/babysit-pr/SKILL.md",
114 enabled: true,
115 },
116 ];
117
118 expect(getProviderSkillsForSlashMenu(skills, true).map((skill) => skill.name)).toEqual([
119 "babysit-pr",
120 "browser",
121 ]);
122 });
123
124 it("keeps an enabled skill when a disabled duplicate appears first", () => {
125 const enabledSkill = {
126 name: "babysit-pr",
127 path: "/Users/matt/.agents/skills/babysit-pr/SKILL.md",
128 enabled: true,
129 };
130 const skills = [
131 {
132 name: "babysit-pr",
133 path: "/Users/matt/.codex/skills/babysit-pr/SKILL.md",
134 enabled: false,
135 },
136 enabledSkill,
137 ];
138
139 expect(getProviderSkillsForSlashMenu(skills, true)).toEqual([enabledSkill]);
140 });
141});
142
143describe("getProviderSkillsForSlashMenu", () => {
144 it("drops a skill the provider reserves for the agent", () => {
145 const skills = [
146 {
147 name: "legacy-system-context",
148 path: "/Users/matt/.claude/skills/legacy-system-context/SKILL.md",
149 enabled: true,
150 userInvocable: false,
151 },
152 {
153 name: "deploy",
154 path: "/Users/matt/.claude/skills/deploy/SKILL.md",
155 enabled: true,
156 // Reserved for the user, not the agent: still a valid pick.
157 userInvocationOnly: true,
158 },
159 ];
160
161 expect(getProviderSkillsForSlashMenu(skills, true).map((skill) => skill.name)).toEqual([
162 "deploy",
163 ]);
164 });
165});
166
167describe("getProviderSlashCommandsForSlashMenu", () => {
168 const commands = [
169 { name: "ask-matt", description: "Ask which skill fits your situation." },
170 { name: "compact", description: "Compact the conversation." },
171 ];
172 const skills = [
173 {
174 name: "ask-matt",
175 path: "/Users/matt/.agents/skills/ask-matt/SKILL.md",
176 enabled: true,
177 },
178 ];
179
180 it("lets the skill alias win when a provider command has the same name", () => {
181 expect(
182 getProviderSlashCommandsForSlashMenu(commands, skills).map((command) => command.name),
183 ).toEqual(["compact"]);
184 });
185
186 it("keeps the provider command when the matching skill alias is hidden", () => {
187 const visibleSkills = getProviderSkillsForSlashMenu(skills, false);
188
189 expect(
190 getProviderSlashCommandsForSlashMenu(commands, visibleSkills).map((command) => command.name),
191 ).toEqual(["ask-matt", "compact"]);
192 });
193});
194
195describe("resolveProviderSkillSourceKind", () => {
196 it("marks plugin-backed skills as app installs", () => {
197 expect(
198 resolveProviderSkillSourceKind({
199 path: "/Users/julius/.codex/plugins/cache/openai-curated/github/skills/gh-fix-ci/SKILL.md",
200 scope: "user",
201 }),
202 ).toBe("app");
203 });
204
205 it("maps standard scopes to source kinds", () => {
206 expect(
207 resolveProviderSkillSourceKind({
208 path: "/workspace/.codex/skills/review-follow-up/SKILL.md",
209 scope: "repo",
210 }),
211 ).toBe("repo");
212 expect(
213 resolveProviderSkillSourceKind({
214 path: "/workspace/.codex/skills/review-follow-up/SKILL.md",
215 scope: "project",
216 }),
217 ).toBe("project");
218 expect(
219 resolveProviderSkillSourceKind({
220 path: "/Users/julius/.agents/skills/agent-browser/SKILL.md",
221 scope: "user",
222 }),
223 ).toBe("personal");
224 expect(
225 resolveProviderSkillSourceKind({
226 path: "/usr/local/share/codex/skills/imagegen/SKILL.md",
227 scope: "system",
228 }),
229 ).toBe("system");
230 });
231
232 it("keeps unknown and missing scopes usable", () => {
233 expect(
234 resolveProviderSkillSourceKind({
235 path: "/opt/skills/team-review/SKILL.md",
236 scope: "team_shared",
237 }),
238 ).toBe("other");
239 expect(
240 resolveProviderSkillSourceKind({
241 path: "/opt/skills/team-review/SKILL.md",
242 }),
243 ).toBe("other");
244 });
245});
246
247describe("workspace provider snapshots", () => {
248 it("uses the cwd snapshot after a provider session has populated it", () => {
249 expect(resolveProviderSkillsForCwd(provider, "/workspace/project-a")).toEqual([
250 { name: "project", path: "/workspace/project-a/SKILL.md", enabled: true },
251 ]);
252 expect(resolveProviderSlashCommandsForCwd(provider, "/workspace/project-a")).toEqual([
253 { name: "project" },
254 ]);
255 });
256
257 it("keeps the machine snapshot before this cwd has a provider snapshot", () => {
258 expect(resolveProviderSkillsForCwd(provider, "/workspace/project-b")).toEqual(provider.skills);
259 expect(resolveProviderSlashCommandsForCwd(provider, null)).toEqual(provider.slashCommands);
260 });
261
262 it("uses partial workspace skills and commands while keeping discovery retryable", () => {
263 const partial = {
264 ...provider,
265 workspaceSnapshots: provider.workspaceSnapshots.map((snapshot) => ({
266 ...snapshot,
267 slashCommands: [{ name: "compact" }],
268 slashCommandsPending: true,
269 })),
270 } satisfies ServerProvider;
271 expect(resolveProviderSkillsForCwd(partial, "/workspace/project-a")).toEqual(
272 provider.workspaceSnapshots[0]?.skills,
273 );
274 expect(resolveProviderSlashCommandsForCwd(partial, "/workspace/project-a")).toEqual([
275 { name: "compact" },
276 ]);
277 expect(hasCompleteProviderWorkspaceSnapshot(partial, "/workspace/project-a")).toBe(false);
278 expect(hasCompleteProviderWorkspaceSnapshot(provider, "/workspace/project-a")).toBe(true);
279 expect(hasCompleteProviderWorkspaceSnapshot(provider, "/workspace/project-b")).toBe(false);
280 expect(hasCompleteProviderWorkspaceSnapshot(undefined, "/workspace/project-a")).toBe(false);
281 expect(hasCompleteProviderWorkspaceSnapshot(provider, null)).toBe(false);
282 });
283
284 it("asks for a rescan once the workspace snapshot outlives its TTL", () => {
285 const scannedAt = Date.parse("2026-01-01T00:01:00.000Z");
286 const cwd = "/workspace/project-a";
287 expect(hasCurrentProviderWorkspaceSnapshot(provider, cwd, scannedAt)).toBe(true);
288 expect(
289 hasCurrentProviderWorkspaceSnapshot(
290 provider,
291 cwd,
292 scannedAt + PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS - 1,
293 ),
294 ).toBe(true);
295 expect(
296 hasCurrentProviderWorkspaceSnapshot(
297 provider,
298 cwd,
299 scannedAt + PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS,
300 ),
301 ).toBe(false);
302 expect(hasCurrentProviderWorkspaceSnapshot(provider, "/workspace/project-b", scannedAt)).toBe(
303 false,
304 );
305 });
306});