import * as NodeOS from "node:os"; import * as NodeServices from "@effect/platform-node/NodeServices";import type * as Crypto from "effect/Crypto";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Path from "effect/Path";import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";import { describe, expect, it } from "vite-plus/test";import type * as EffectAcpSchema from "effect-acp/schema";import type { CursorSettings } from "@t3tools/contracts";import { createModelCapabilities } from "@t3tools/shared/model"; import { buildCursorProviderSnapshot, buildCursorCapabilitiesFromConfigOptions, checkCursorProviderStatus, discoverCursorModelsViaAcp, makeCursorModelDiscovery, getCursorParameterizedModelPickerUnsupportedMessage, parseCursorAboutOutput, parseCursorCliConfigChannel, parseCursorVersionDate, resolveCursorAcpBaseModelId, resolveCursorAcpConfigUpdates,} from "./CursorProvider.ts";import { discoverCursorSkills, hasCursorSkillMention, probeCursorSkills, rewriteCursorSkillMentions,} from "../Drivers/CursorSkills.ts";import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; const runNode = <A, E>( effect: Effect.Effect< A, E, ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | FileSystem.FileSystem | Path.Path >,): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer))); // Closing the probe kills the agent with SIGTERM; Windows terminates the// process instead, so the mock never sees a signal to log.const windowsHost = HostProcessPlatform.defaultValue() === "win32"; const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () { const path = yield* Path.Path; return yield* path.fromFileUrl(new URL("../../../scripts/acp-mock-agent.ts", import.meta.url));}); function selectDescriptor( id: string, label: string, options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>,) { return { id, label, type: "select" as const, options: [...options], ...(options.find((option) => option.isDefault)?.id ? { currentValue: options.find((option) => option.isDefault)?.id } : {}), };} function booleanDescriptor(id: string, label: string, currentValue?: boolean) { return { id, label, type: "boolean" as const, ...(typeof currentValue === "boolean" ? { currentValue } : {}), };} const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* ( extraEnv?: Record<string, string>,) { const fileSystem = yield* FileSystem.FileSystem; const mockAgentPath = yield* resolveMockAgentPath(); const dir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-mock-", }); return writeFakeCli({ directory: dir, name: "fake-agent", env: extraEnv ?? {}, source: execScriptSource({ scriptPath: mockAgentPath }), });}); const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper")(function* () { const fileSystem = yield* FileSystem.FileSystem; const mockAgentPath = yield* resolveMockAgentPath(); const dir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-about-mock-", }); return writeFakeCli({ directory: dir, name: "fake-agent", source: [ 'if (process.argv[2] === "about") {', ' process.stdout.write("CLI Version 2026.04.09-f2b0fcd\\n");', ' process.stdout.write("User Email cursor@example.com\\n");', " process.exit(0);", "}", execScriptSource({ scriptPath: mockAgentPath }), ].join("\n"), });}); const waitForFileContent = Effect.fn("waitForFileContent")(function* ( filePath: string, attempts = 40,) { const fileSystem = yield* FileSystem.FileSystem; for (let attempt = 0; attempt < attempts; attempt += 1) { const content = yield* fileSystem .readFileString(filePath) .pipe(Effect.catch(() => Effect.void)); if (content !== undefined) { if (content.trim().length > 0) { return content; } } yield* Effect.sleep("50 millis"); } return yield* Effect.die(`Timed out waiting for file content at ${filePath}`);}); const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const tempDir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-status-env-", }); return { requestLogPath: path.join(tempDir, "requests.ndjson"), wrapperPath: yield* makeMockAgentWithAboutWrapper(), };}); const makeExitLogFixture = Effect.fn("makeExitLogFixture")(function* (prefix: string) { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const tempDir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix, }); const exitLogPath = path.join(tempDir, "exit.log"); return { exitLogPath, wrapperPath: yield* makeMockAgentWrapper({ T3_ACP_EXIT_LOG_PATH: exitLogPath, }), };}); const parameterizedGpt54ConfigOptions = [ { type: "select", currentValue: "gpt-5.4-medium-fast", options: [{ name: "GPT-5.4", value: "gpt-5.4-medium-fast" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "medium", options: [ { name: "None", value: "none" }, { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, { name: "Extra High", value: "extra-high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "select", currentValue: "272k", options: [ { name: "272K", value: "272k" }, { name: "1M", value: "1m" }, ], category: "model_config", id: "context", name: "Context", }, { type: "select", currentValue: "false", options: [ { name: "Off", value: "false" }, { name: "Fast", value: "true" }, ], category: "model_config", id: "fast", name: "Fast", },] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const parameterizedClaudeConfigOptions = [ { type: "select", currentValue: "claude-4.6-opus-high-thinking", options: [{ name: "Opus 4.6", value: "claude-4.6-opus-high-thinking" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "high", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "boolean", currentValue: true, category: "model_config", id: "thinking", name: "Thinking", },] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const parameterizedClaudeModelOptionConfigOptions = [ { type: "select", currentValue: "claude-opus-4-6", options: [{ name: "Opus 4.6", value: "claude-opus-4-6" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "high", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "select", currentValue: "max", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, { name: "Max", value: "max" }, ], category: "model_option", id: "effort", name: "Effort", }, { type: "select", currentValue: "true", options: [ { name: "Off", value: "false" }, { name: "Fast", value: "true" }, ], category: "model_config", id: "fast", name: "Fast", }, { type: "select", currentValue: "true", options: [ { name: "Off", value: "false" }, { name: ":icon-brain:", value: "true" }, ], category: "model_config", id: "thinking", name: "Thinking", },] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const baseCursorSettings: CursorSettings = { enabled: true, binaryPath: "cursor-agent", apiEndpoint: "", customModels: [],};const cursorAcpDiscoveryFailedMessage = [ "Cursor ACP model discovery failed.", "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.", "See https://cursor.com/docs/cli/installation.", "Check server logs for ACP details.",].join(" ");const missingCursorBinaryPath = "/definitely/not/installed/t3-cursor-agent";const cursorCliCommandMissingMessage = [ `Cursor CLI command \`${missingCursorBinaryPath}\` was not found.`, `Install or enable the Cursor CLI, make sure \`${missingCursorBinaryPath}\` is on PATH, then restart T3 Code.`, "See https://cursor.com/docs/cli/installation.",].join(" "); describe("Cursor skills", () => { it("discovers recursive project skills with project precedence", async () => await runNode( Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const userHome = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-home-", }); const workspace = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-workspace-", }); const writeSkill = Effect.fn("writeCursorSkill")(function* ( root: string, name: string, contents: string, ) { const skillDirectory = path.join(root, name); yield* fileSystem.makeDirectory(skillDirectory, { recursive: true }); yield* fileSystem.writeFileString(path.join(skillDirectory, "SKILL.md"), contents); }); yield* writeSkill( path.join(userHome, ".cursor", "skills"), "review", "---\ndescription: user review\n---\n", ); yield* writeSkill( path.join(workspace, ".agents", "skills", "nested"), "review", "---\nname: Review changes\ndescription: project review\n---\n", ); yield* writeSkill( path.join(workspace, ".cursor", "skills"), "internal", "---\nuser-invocable: false\n---\n", ); yield* writeSkill( path.join(workspace, ".cursor", "skills"), "oversized", "x".repeat(1_000_001), ); yield* fileSystem.makeDirectory(path.join(userHome, ".codex"), { recursive: true }); yield* fileSystem.writeFileString( path.join(userHome, ".codex", "skills"), "not a directory", ); const skills = yield* discoverCursorSkills(workspace, { HOME: userHome }); expect(skills).toEqual([ { name: "internal", path: path.join(workspace, ".cursor", "skills", "internal", "SKILL.md"), scope: "project", enabled: true, userInvocable: false, }, { name: "oversized", path: path.join(workspace, ".cursor", "skills", "oversized", "SKILL.md"), scope: "project", enabled: true, }, { name: "review", displayName: "Review changes", description: "project review", path: path.join(workspace, ".agents", "skills", "nested", "review", "SKILL.md"), scope: "project", enabled: true, }, ]); expect( (yield* probeCursorSkills(workspace, { HOME: userHome }).pipe(Effect.result))._tag, ).toBe("Failure"); }), )); it("treats a symlinked skill outside the root as a package boundary", async () => await runNode( Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const userHome = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-home-", }); const workspace = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-workspace-", }); const library = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-library-", }); const writeSkill = Effect.fn("writeCursorSkill")(function* ( directory: string, contents: string, ) { yield* fileSystem.makeDirectory(directory, { recursive: true }); yield* fileSystem.writeFileString(path.join(directory, "SKILL.md"), contents); }); // A skill package managed in a config repo and installed by symlink. // Its own SKILL.md must be discovered under the link name, but nothing // below the target may be walked. yield* writeSkill(path.join(library, "shared-review"), "---\ndescription: shared\n---\n"); yield* writeSkill(path.join(library, "shared-review", "hidden"), "---\n---\n"); const root = path.join(workspace, ".cursor", "skills"); yield* fileSystem.makeDirectory(root, { recursive: true }); yield* fileSystem.symlink(path.join(library, "shared-review"), path.join(root, "review")); const skills = yield* discoverCursorSkills(workspace, { HOME: userHome }); expect(skills).toEqual([ { name: "review", description: "shared", path: path.join(root, "review", "SKILL.md"), scope: "project", enabled: true, }, ]); expect( (yield* probeCursorSkills(workspace, { HOME: userHome }).pipe(Effect.result))._tag, ).toBe("Success"); }), )); it("rewrites only discovered skill mentions into Cursor slash invocations", () => { expect(hasCursorSkillMention("use $Review_Pr:V2 here")).toBe(true); expect(hasCursorSkillMention("please $review this")).toBe(true); expect( rewriteCursorSkillMentions("use $review, keep $HOME and 5$review", new Set(["review"])), ).toBe("use $review, keep $HOME and 5$review"); expect(rewriteCursorSkillMentions("please $review this", new Set(["review"]))).toBe( "please /review this", ); }); it("detects and invokes digit-leading Cursor skills without rewriting money", () => { const names = new Set(["2spec", "20k", "100M", "1e6"]); // Repeated presence checks must not carry a global-regex cursor. expect(hasCursorSkillMention("use $2spec here")).toBe(true); expect(hasCursorSkillMention("use $2spec here")).toBe(true); expect(rewriteCursorSkillMentions("use $2spec here", names)).toBe("use /2spec here"); expect(rewriteCursorSkillMentions("use $2spec here", new Set())).toBe("use $2spec here"); for (const text of [ "pay $20 tomorrow", "budget $20k here", "cost $100M total", "limit $1e6 here", ]) { expect(hasCursorSkillMention(text)).toBe(false); expect(rewriteCursorSkillMentions(text, names)).toBe(text); } });}); describe("buildCursorProviderSnapshot", () => { it("downgrades ready status to warning when ACP model discovery times out", () => { expect( buildCursorProviderSnapshot({ checkedAt: "2026-01-01T00:00:00.000Z", cursorSettings: baseCursorSettings, parsed: { version: "2026.04.09-f2b0fcd", status: "ready", auth: { status: "authenticated", type: "Team", label: "Cursor Team Subscription" }, }, discoveryWarning: "Cursor ACP model discovery timed out after 15000ms.", }), ).toMatchObject({ status: "warning", message: "Cursor ACP model discovery timed out after 15000ms.", models: [], }); }); it("preserves provider error state while appending discovery warnings", () => { expect( buildCursorProviderSnapshot({Lines 1–500 of 869. Load more
import * as NodeOS from "node:os"; import * as NodeServices from "@effect/platform-node/NodeServices"; import type * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import type * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; import { describe, expect, it } from "vite-plus/test"; import type * as EffectAcpSchema from "effect-acp/schema"; import type { CursorSettings } from "@t3tools/contracts"; import { createModelCapabilities } from "@t3tools/shared/model"; import { buildCursorProviderSnapshot, buildCursorCapabilitiesFromConfigOptions, checkCursorProviderStatus, discoverCursorModelsViaAcp, makeCursorModelDiscovery, getCursorParameterizedModelPickerUnsupportedMessage, parseCursorAboutOutput, parseCursorCliConfigChannel, parseCursorVersionDate, resolveCursorAcpBaseModelId, resolveCursorAcpConfigUpdates, } from "./CursorProvider.ts"; import { discoverCursorSkills, hasCursorSkillMention, probeCursorSkills, rewriteCursorSkillMentions, } from "../Drivers/CursorSkills.ts"; import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; const runNode = <A, E>( effect: Effect.Effect< A, E, ChildProcessSpawner.ChildProcessSpawner | Crypto.Crypto | FileSystem.FileSystem | Path.Path >, ): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer))); // Closing the probe kills the agent with SIGTERM; Windows terminates the // process instead, so the mock never sees a signal to log. const windowsHost = HostProcessPlatform.defaultValue() === "win32"; const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () { const path = yield* Path.Path; return yield* path.fromFileUrl(new URL("../../../scripts/acp-mock-agent.ts", import.meta.url)); }); function selectDescriptor( id: string, label: string, options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>, ) { return { id, label, type: "select" as const, options: [...options], ...(options.find((option) => option.isDefault)?.id ? { currentValue: options.find((option) => option.isDefault)?.id } : {}), }; } function booleanDescriptor(id: string, label: string, currentValue?: boolean) { return { id, label, type: "boolean" as const, ...(typeof currentValue === "boolean" ? { currentValue } : {}), }; } const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* ( extraEnv?: Record<string, string>, ) { const fileSystem = yield* FileSystem.FileSystem; const mockAgentPath = yield* resolveMockAgentPath(); const dir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-mock-", }); return writeFakeCli({ directory: dir, name: "fake-agent", env: extraEnv ?? {}, source: execScriptSource({ scriptPath: mockAgentPath }), }); }); const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper")(function* () { const fileSystem = yield* FileSystem.FileSystem; const mockAgentPath = yield* resolveMockAgentPath(); const dir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-about-mock-", }); return writeFakeCli({ directory: dir, name: "fake-agent", source: [ 'if (process.argv[2] === "about") {', ' process.stdout.write("CLI Version 2026.04.09-f2b0fcd\\n");', ' process.stdout.write("User Email cursor@example.com\\n");', " process.exit(0);", "}", execScriptSource({ scriptPath: mockAgentPath }), ].join("\n"), }); }); const waitForFileContent = Effect.fn("waitForFileContent")(function* ( filePath: string, attempts = 40, ) { const fileSystem = yield* FileSystem.FileSystem; for (let attempt = 0; attempt < attempts; attempt += 1) { const content = yield* fileSystem .readFileString(filePath) .pipe(Effect.catch(() => Effect.void)); if (content !== undefined) { if (content.trim().length > 0) { return content; } } yield* Effect.sleep("50 millis"); } return yield* Effect.die(`Timed out waiting for file content at ${filePath}`); }); const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const tempDir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-provider-status-env-", }); return { requestLogPath: path.join(tempDir, "requests.ndjson"), wrapperPath: yield* makeMockAgentWithAboutWrapper(), }; }); const makeExitLogFixture = Effect.fn("makeExitLogFixture")(function* (prefix: string) { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const tempDir = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix, }); const exitLogPath = path.join(tempDir, "exit.log"); return { exitLogPath, wrapperPath: yield* makeMockAgentWrapper({ T3_ACP_EXIT_LOG_PATH: exitLogPath, }), }; }); const parameterizedGpt54ConfigOptions = [ { type: "select", currentValue: "gpt-5.4-medium-fast", options: [{ name: "GPT-5.4", value: "gpt-5.4-medium-fast" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "medium", options: [ { name: "None", value: "none" }, { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, { name: "Extra High", value: "extra-high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "select", currentValue: "272k", options: [ { name: "272K", value: "272k" }, { name: "1M", value: "1m" }, ], category: "model_config", id: "context", name: "Context", }, { type: "select", currentValue: "false", options: [ { name: "Off", value: "false" }, { name: "Fast", value: "true" }, ], category: "model_config", id: "fast", name: "Fast", }, ] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const parameterizedClaudeConfigOptions = [ { type: "select", currentValue: "claude-4.6-opus-high-thinking", options: [{ name: "Opus 4.6", value: "claude-4.6-opus-high-thinking" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "high", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "boolean", currentValue: true, category: "model_config", id: "thinking", name: "Thinking", }, ] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const parameterizedClaudeModelOptionConfigOptions = [ { type: "select", currentValue: "claude-opus-4-6", options: [{ name: "Opus 4.6", value: "claude-opus-4-6" }], category: "model", id: "model", name: "Model", }, { type: "select", currentValue: "high", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, ], category: "thought_level", id: "reasoning", name: "Reasoning", }, { type: "select", currentValue: "max", options: [ { name: "Low", value: "low" }, { name: "Medium", value: "medium" }, { name: "High", value: "high" }, { name: "Max", value: "max" }, ], category: "model_option", id: "effort", name: "Effort", }, { type: "select", currentValue: "true", options: [ { name: "Off", value: "false" }, { name: "Fast", value: "true" }, ], category: "model_config", id: "fast", name: "Fast", }, { type: "select", currentValue: "true", options: [ { name: "Off", value: "false" }, { name: ":icon-brain:", value: "true" }, ], category: "model_config", id: "thinking", name: "Thinking", }, ] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>; const baseCursorSettings: CursorSettings = { enabled: true, binaryPath: "cursor-agent", apiEndpoint: "", customModels: [], }; const cursorAcpDiscoveryFailedMessage = [ "Cursor ACP model discovery failed.", "Cursor CLI setup may be incomplete; install or enable the Cursor CLI, restart T3 Code, and try again.", "See https://cursor.com/docs/cli/installation.", "Check server logs for ACP details.", ].join(" "); const missingCursorBinaryPath = "/definitely/not/installed/t3-cursor-agent"; const cursorCliCommandMissingMessage = [ `Cursor CLI command \`${missingCursorBinaryPath}\` was not found.`, `Install or enable the Cursor CLI, make sure \`${missingCursorBinaryPath}\` is on PATH, then restart T3 Code.`, "See https://cursor.com/docs/cli/installation.", ].join(" "); describe("Cursor skills", () => { it("discovers recursive project skills with project precedence", async () => await runNode( Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const userHome = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-home-", }); const workspace = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-workspace-", }); const writeSkill = Effect.fn("writeCursorSkill")(function* ( root: string, name: string, contents: string, ) { const skillDirectory = path.join(root, name); yield* fileSystem.makeDirectory(skillDirectory, { recursive: true }); yield* fileSystem.writeFileString(path.join(skillDirectory, "SKILL.md"), contents); }); yield* writeSkill( path.join(userHome, ".cursor", "skills"), "review", "---\ndescription: user review\n---\n", ); yield* writeSkill( path.join(workspace, ".agents", "skills", "nested"), "review", "---\nname: Review changes\ndescription: project review\n---\n", ); yield* writeSkill( path.join(workspace, ".cursor", "skills"), "internal", "---\nuser-invocable: false\n---\n", ); yield* writeSkill( path.join(workspace, ".cursor", "skills"), "oversized", "x".repeat(1_000_001), ); yield* fileSystem.makeDirectory(path.join(userHome, ".codex"), { recursive: true }); yield* fileSystem.writeFileString( path.join(userHome, ".codex", "skills"), "not a directory", ); const skills = yield* discoverCursorSkills(workspace, { HOME: userHome }); expect(skills).toEqual([ { name: "internal", path: path.join(workspace, ".cursor", "skills", "internal", "SKILL.md"), scope: "project", enabled: true, userInvocable: false, }, { name: "oversized", path: path.join(workspace, ".cursor", "skills", "oversized", "SKILL.md"), scope: "project", enabled: true, }, { name: "review", displayName: "Review changes", description: "project review", path: path.join(workspace, ".agents", "skills", "nested", "review", "SKILL.md"), scope: "project", enabled: true, }, ]); expect( (yield* probeCursorSkills(workspace, { HOME: userHome }).pipe(Effect.result))._tag, ).toBe("Failure"); }), )); it("treats a symlinked skill outside the root as a package boundary", async () => await runNode( Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const userHome = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-home-", }); const workspace = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-workspace-", }); const library = yield* fileSystem.makeTempDirectory({ directory: NodeOS.tmpdir(), prefix: "cursor-skills-library-", }); const writeSkill = Effect.fn("writeCursorSkill")(function* ( directory: string, contents: string, ) { yield* fileSystem.makeDirectory(directory, { recursive: true }); yield* fileSystem.writeFileString(path.join(directory, "SKILL.md"), contents); }); // A skill package managed in a config repo and installed by symlink. // Its own SKILL.md must be discovered under the link name, but nothing // below the target may be walked. yield* writeSkill(path.join(library, "shared-review"), "---\ndescription: shared\n---\n"); yield* writeSkill(path.join(library, "shared-review", "hidden"), "---\n---\n"); const root = path.join(workspace, ".cursor", "skills"); yield* fileSystem.makeDirectory(root, { recursive: true }); yield* fileSystem.symlink(path.join(library, "shared-review"), path.join(root, "review")); const skills = yield* discoverCursorSkills(workspace, { HOME: userHome }); expect(skills).toEqual([ { name: "review", description: "shared", path: path.join(root, "review", "SKILL.md"), scope: "project", enabled: true, }, ]); expect( (yield* probeCursorSkills(workspace, { HOME: userHome }).pipe(Effect.result))._tag, ).toBe("Success"); }), )); it("rewrites only discovered skill mentions into Cursor slash invocations", () => { expect(hasCursorSkillMention("use $Review_Pr:V2 here")).toBe(true); expect(hasCursorSkillMention("please $review this")).toBe(true); expect( rewriteCursorSkillMentions("use $review, keep $HOME and 5$review", new Set(["review"])), ).toBe("use $review, keep $HOME and 5$review"); expect(rewriteCursorSkillMentions("please $review this", new Set(["review"]))).toBe( "please /review this", ); }); it("detects and invokes digit-leading Cursor skills without rewriting money", () => { const names = new Set(["2spec", "20k", "100M", "1e6"]); // Repeated presence checks must not carry a global-regex cursor. expect(hasCursorSkillMention("use $2spec here")).toBe(true); expect(hasCursorSkillMention("use $2spec here")).toBe(true); expect(rewriteCursorSkillMentions("use $2spec here", names)).toBe("use /2spec here"); expect(rewriteCursorSkillMentions("use $2spec here", new Set())).toBe("use $2spec here"); for (const text of [ "pay $20 tomorrow", "budget $20k here", "cost $100M total", "limit $1e6 here", ]) { expect(hasCursorSkillMention(text)).toBe(false); expect(rewriteCursorSkillMentions(text, names)).toBe(text); } }); }); describe("buildCursorProviderSnapshot", () => { it("downgrades ready status to warning when ACP model discovery times out", () => { expect( buildCursorProviderSnapshot({ checkedAt: "2026-01-01T00:00:00.000Z", cursorSettings: baseCursorSettings, parsed: { version: "2026.04.09-f2b0fcd", status: "ready", auth: { status: "authenticated", type: "Team", label: "Cursor Team Subscription" }, }, discoveryWarning: "Cursor ACP model discovery timed out after 15000ms.", }), ).toMatchObject({ status: "warning", message: "Cursor ACP model discovery timed out after 15000ms.", models: [], }); }); it("preserves provider error state while appending discovery warnings", () => { expect( buildCursorProviderSnapshot({