Skip to content

Commit 6d8189b

Browse files
arul28claude
andauthored
Cursor Sdk Cleanup (#360)
* Refactor slash command discovery and extract shared modules Extract Cursor-specific slash command discovery from Claude/Codex implementations into dedicated modules. Pull markdown parsing, project discovery, and prompt expansion into shared services. Extract Linear issue helpers from laneService and chat context attachment logic into standalone shared modules with tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ship: iteration 1 — fix typecheck-desktop, address review comments - Type SAMPLE_ISSUE as LaneLinearIssue to fix description type mismatch - Remove dead candidateNames.has(target) branch (target never has / prefix) - Restrict activateRuntime to cursor provider only (was unintentionally including droid) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ship: iteration 2 — fix test-desktop shard 4 - Update cliLaunch.test.ts assertion: .cursor/skills is now first in ancestorSkillDirs ordering (matches agentSkillRoots.ts change) - Use Partial<LaneLinearIssue> instead of Partial<typeof SAMPLE_ISSUE> to allow string description overrides in test helper Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 03fbb34 commit 6d8189b

33 files changed

Lines changed: 1480 additions & 718 deletions

apps/ade-cli/package-lock.json

Lines changed: 47 additions & 47 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/ade-cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
},
2626
"dependencies": {
2727
"@anthropic-ai/claude-agent-sdk": "^0.2.139",
28-
"@cursor/sdk": "^1.0.9",
28+
"@cursor/sdk": "^1.0.13",
2929
"@linear/sdk": "^84.0.0",
3030
"@openai/codex": "0.130.0",
3131
"@opencode-ai/sdk": "^1.15.5",

apps/ade-cli/src/cli.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,6 +1919,39 @@ describe("ADE CLI", () => {
19191919
});
19201920
});
19211921

1922+
it("maps existing lane Linear issue linking to the lane action", () => {
1923+
const plan = buildCliPlan([
1924+
"lanes",
1925+
"link-linear-issue",
1926+
"lane-1",
1927+
"--linear-issue-json",
1928+
'{"id":"issue-1","identifier":"ADE-123","title":"Linked lane"}',
1929+
"--source",
1930+
"manual",
1931+
"--no-include-in-pr",
1932+
]);
1933+
1934+
expect(plan.kind).toBe("execute");
1935+
if (plan.kind !== "execute") return;
1936+
expect(plan.steps[0]?.params).toEqual({
1937+
name: "run_ade_action",
1938+
arguments: {
1939+
domain: "lane",
1940+
action: "linkLinearIssues",
1941+
args: {
1942+
laneId: "lane-1",
1943+
issues: [{
1944+
id: "issue-1",
1945+
identifier: "ADE-123",
1946+
title: "Linked lane",
1947+
}],
1948+
source: "manual",
1949+
includeInPr: false,
1950+
},
1951+
},
1952+
});
1953+
});
1954+
19221955
it("maps Linear quick view to the typed RPC tool", () => {
19231956
const plan = buildCliPlan(["linear", "quick-view", "--text"]);
19241957

apps/ade-cli/src/cli.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ const HELP_BY_COMMAND: Record<string, string> = {
921921
$ ade lanes show <lane> --text Inspect one lane status
922922
$ ade lanes create --name <name> Create a lane from the current project context
923923
$ ade lanes create --linear-issue-json '{...}' Create a lane linked to a Linear issue
924+
$ ade lanes link-linear-issue <lane> --linear-issue-json '{...}'
925+
Link an existing lane to a Linear issue
924926
$ ade lanes create --branch-name <branch> Override the auto-generated branch name
925927
$ ade lanes child --lane <parent> --name <name> Create a child lane under a parent
926928
$ ade lanes import --branch <branch> Register an existing branch/worktree
@@ -2379,6 +2381,42 @@ function buildLanePlan(args: string[]): CliPlan {
23792381
steps: [actionArgsListStep("result", "lane", "getChildren", [laneId])],
23802382
};
23812383
}
2384+
if (sub === "link-linear-issue" || sub === "link-linear" || sub === "linear-link") {
2385+
const laneId = requireValue(
2386+
readLaneId(args) ?? firstPositional(args),
2387+
"laneId",
2388+
);
2389+
const linearIssueJson = requireValue(
2390+
readValue(args, ["--linear-issue-json", "--issue-json"]),
2391+
"--linear-issue-json",
2392+
);
2393+
const parsed = parseJson(linearIssueJson, "--linear-issue-json");
2394+
const issues = Array.isArray(parsed) ? parsed : [parsed];
2395+
if (issues.length === 0 || issues.some((issue) => !isRecord(issue))) {
2396+
throw new CliUsageError("--linear-issue-json must decode to an object or array of objects.");
2397+
}
2398+
const input: JsonObject = {
2399+
laneId,
2400+
issues: issues as JsonObject[],
2401+
};
2402+
maybePut(input, "role", readValue(args, ["--role"]));
2403+
maybePut(input, "source", readValue(args, ["--source"]));
2404+
if (readFlag(args, ["--no-include-in-pr"])) input.includeInPr = false;
2405+
if (readFlag(args, ["--include-in-pr"])) input.includeInPr = true;
2406+
if (readFlag(args, ["--close-on-merge"])) input.closeOnMerge = true;
2407+
return {
2408+
kind: "execute",
2409+
label: "lane link Linear issue",
2410+
steps: [
2411+
actionStep(
2412+
"result",
2413+
"lane",
2414+
"linkLinearIssues",
2415+
collectGenericObjectArgs(args, input),
2416+
),
2417+
],
2418+
};
2419+
}
23822420
if (sub === "stack") {
23832421
const laneId = requireValue(
23842422
readLaneId(args) ?? firstPositional(args),

apps/ade-cli/src/tuiClient/__tests__/adeApi.test.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import os from "node:os";
33
import path from "node:path";
44
import { afterEach, describe, expect, it, vi } from "vitest";
55
import type { AgentChatEventEnvelope } from "../../../../desktop/src/shared/types/chat";
6-
import { cancelSteerMessage, createChatSession, DEFAULT_CODEX_REASONING_EFFORT, dispatchSteerMessage, discoverProjectSlashCommands, editSteerMessage, latestGoal, latestTokenStats, listLaneDiffStats, listPrsByLane, listTerminalSessions, sendChatMessage, signalTerminal, startClaudeTerminalSession, steerChatMessage } from "../adeApi";
6+
import { cancelSteerMessage, createChatSession, DEFAULT_CODEX_REASONING_EFFORT, dispatchSteerMessage, discoverProjectSlashCommands, editSteerMessage, getAvailableModels, latestGoal, latestTokenStats, listLaneDiffStats, listPrsByLane, listTerminalSessions, sendChatMessage, signalTerminal, startClaudeTerminalSession, steerChatMessage } from "../adeApi";
77
import type { AdeCodeConnection } from "../types";
88

99
const tmpPaths: string[] = [];
@@ -232,6 +232,51 @@ describe("discoverProjectSlashCommands", () => {
232232
expect.objectContaining({ name: "/ship" }),
233233
]));
234234
});
235+
236+
it("includes Cursor command files and subagents", () => {
237+
const projectRoot = makeTmpRoot("ade-code-cursor-command-");
238+
const commandsDir = path.join(projectRoot, ".cursor", "commands");
239+
const agentsDir = path.join(projectRoot, ".cursor", "agents");
240+
fs.mkdirSync(commandsDir, { recursive: true });
241+
fs.mkdirSync(agentsDir, { recursive: true });
242+
fs.writeFileSync(path.join(commandsDir, "review-code.md"), "Review code.\n");
243+
fs.writeFileSync(path.join(agentsDir, "verifier.md"), [
244+
"---",
245+
"description: Verify the change",
246+
"---",
247+
"",
248+
"Verify.",
249+
"",
250+
].join("\n"));
251+
252+
const commands = discoverProjectSlashCommands(projectRoot);
253+
expect(commands).toEqual(expect.arrayContaining([
254+
expect.objectContaining({ name: "/review-code", description: "Review code." }),
255+
expect.objectContaining({ name: "/verifier", description: "Verify the change" }),
256+
]));
257+
});
258+
});
259+
260+
describe("getAvailableModels", () => {
261+
it("activates dynamic Cursor model discovery for TUI model lists", async () => {
262+
const calls: Array<{ domain: string; action: string; args?: Record<string, unknown> }> = [];
263+
const connection = {
264+
action: vi.fn(async (domain: string, action: string, args?: Record<string, unknown>) => {
265+
calls.push({ domain, action, args });
266+
return [];
267+
}),
268+
} as any;
269+
270+
await getAvailableModels(connection, "cursor");
271+
272+
expect(calls).toEqual([
273+
{
274+
domain: "chat",
275+
action: "getAvailableModels",
276+
args: { provider: "cursor", activateRuntime: true },
277+
},
278+
]);
279+
});
235280
});
236281

237282
describe("createChatSession", () => {

apps/ade-cli/src/tuiClient/adeApi.ts

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ import type {
4444
PtySendToSessionResult,
4545
TerminalSessionSummary,
4646
} from "../../../desktop/src/shared/types";
47-
import { discoverClaudeSlashCommands } from "../../../desktop/src/main/services/chat/claudeSlashCommandDiscovery";
48-
import { discoverCodexSlashCommands } from "../../../desktop/src/main/services/chat/codexSlashCommandDiscovery";
47+
import { discoverAllProjectSlashCommands } from "../../../desktop/src/main/services/chat/projectSlashCommandDiscovery";
4948
import type { AdeCodeConnection, ChatHistorySnapshot, CreatedChat, NavigateRequest, NavigateResult } from "./types";
5049

5150
export const DEFAULT_CODEX_REASONING_EFFORT = "low";
@@ -286,26 +285,8 @@ export async function setClaudeOutputStyle(
286285
return await connection.action<AgentChatSession>("chat", "setClaudeOutputStyle", { sessionId, outputStyle });
287286
}
288287

289-
function slashCommandKey(value: string): string {
290-
return value.trim().toLowerCase();
291-
}
292-
293288
export function discoverProjectSlashCommands(workspaceRoot: string): AgentChatSlashCommand[] {
294-
const byName = new Map<string, AgentChatSlashCommand>();
295-
const add = (command: { name: string; description: string; argumentHint?: string }) => {
296-
const key = slashCommandKey(command.name);
297-
if (key === "/login") return;
298-
if (byName.has(key)) return;
299-
byName.set(key, {
300-
name: command.name,
301-
description: command.description,
302-
argumentHint: command.argumentHint,
303-
source: "sdk",
304-
});
305-
};
306-
for (const command of discoverClaudeSlashCommands(workspaceRoot)) add(command);
307-
for (const command of discoverCodexSlashCommands(workspaceRoot)) add(command);
308-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name, undefined, { sensitivity: "base" }));
289+
return discoverAllProjectSlashCommands(workspaceRoot);
309290
}
310291

311292
export async function getAvailableModels(
@@ -314,7 +295,7 @@ export async function getAvailableModels(
314295
): Promise<AgentChatModelInfo[]> {
315296
return await connection.action<AgentChatModelInfo[]>("chat", "getAvailableModels", {
316297
provider,
317-
activateRuntime: false,
298+
activateRuntime: provider === "cursor",
318299
});
319300
}
320301

apps/desktop/src/main/services/adeActions/registry.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ export const ADE_ACTION_ALLOWLIST: Partial<Record<AdeActionDomain, readonly stri
218218
"listRebaseSuggestions",
219219
"listTemplates",
220220
"listUnregisteredWorktrees",
221+
"linkLinearIssues",
221222
"oauthDecodeState",
222223
"oauthEncodeState",
223224
"oauthGenerateRedirectUris",

0 commit comments

Comments
 (0)