From 25318e05999b1a8e29c1c1aa3fa4f126587d0f35 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Fri, 8 May 2026 11:49:52 +0300 Subject: [PATCH 01/20] feat: enhance MCP configuration for multiple coding assistants and update related prompts Co-authored-by: Copilot --- packages/cli/lib/commands/ai-config.ts | 53 +++++++++++--- packages/core/util/mcp-config.ts | 30 ++++++-- .../src/cli-config/ai-config-schema.json | 21 +++++- .../ng-schematics/src/cli-config/index.ts | 13 ++-- .../src/cli-config/index_spec.ts | 32 +++++++++ spec/unit/ai-config-spec.ts | 71 ++++++++++++++++++- 6 files changed, 191 insertions(+), 29 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 20e697d39..289fda4d1 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,14 +1,15 @@ -import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; +import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; -export function configureMCP(): void { - const modified = addMcpServers(VS_CODE_MCP_PATH); +export function configureMCP(assistant: AiCodingAssistant = "vscode"): void { + const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant]; + const modified = addMcpServers(assistant); if (!modified) { - Util.log(` Ignite UI MCP servers already configured in ${VS_CODE_MCP_PATH}`); + Util.log(` Ignite UI MCP servers already configured in ${mcpFilePath}`); return; } - Util.log(Util.greenCheck() + ` MCP servers configured in ${VS_CODE_MCP_PATH}`); + Util.log(Util.greenCheck() + ` MCP servers configured in ${mcpFilePath}`); } export function configureSkills(agents: AIAgentTarget[]): void { @@ -26,20 +27,31 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents?: AIAgentTarget[], skills = true): Promise { +export async function configure(agents?: AIAgentTarget[], skills = true, assistant: AiCodingAssistant = "vscode"): Promise { if (!agents?.length) { agents = await promptForAgents(); } if (!agents.length) return; - configureMCP(); + configureMCP(assistant); if (skills) { configureSkills(agents); } copyAgentInstructionFiles(agents); } const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; + +const AI_ASSISTANT_CHOICES = Object.keys(AI_ASSISTANT_MCP_CONFIGS) as AiCodingAssistant[]; + +const AI_ASSISTANT_LABELS: Record = { + "vscode": "VS Code (GitHub Copilot)", + "cursor": "Cursor", + "claude-code": "Claude Code", + "gemini": "Gemini", + "junie": "JetBrains Junie", +}; + const AI_AGENT_CHECKBOX_CHOICES = [ - { value: "none", name: "None (skip AI configuration)" }, + { value: "none", name: "None (skip skills and instructions)" }, ...AI_AGENT_CHOICES.map(agent => ({ value: agent, name: AI_AGENT_LABELS[agent], @@ -51,7 +63,7 @@ export async function promptForAgents(): Promise { let selected: AIAgentTarget[] = AI_AGENT_CHECKBOX_DEFAULTS; if (Util.canPrompt()) { const result = await InquirerWrapper.checkbox({ - message: "Which AI tools do you want to generate configuration files for?", + message: "Which AI agents do you want to generate skills and instructions for?", required: true, choices: AI_AGENT_CHECKBOX_CHOICES }); @@ -60,6 +72,14 @@ export async function promptForAgents(): Promise { return selected; } +export async function promptForAssistant(): Promise { + const selected = await InquirerWrapper.select({ + message: "Which coding assistant should MCP servers be configured for?", + choices: AI_ASSISTANT_CHOICES.map(a => ({ value: a, name: AI_ASSISTANT_LABELS[a] })) + }); + return selected as AiCodingAssistant; +} + const command: CommandModule = { command: "ai-config", describe: "Configures Ignite UI AI tooling (MCP servers, AI coding skills and instructions)", @@ -70,23 +90,36 @@ const command: CommandModule = { describe: "AI agents/tools to generate configuration files for", choices: AI_AGENT_CHOICES, type: "array" + }) + .option("assistant", { + describe: "Coding assistant to configure MCP servers for", + choices: AI_ASSISTANT_CHOICES, + type: "string" }), async handler(argv: ArgumentsCamelCase) { let agents = argv.agent as AIAgentTarget[] | undefined; + let assistant = argv.assistant as AiCodingAssistant | undefined; + GoogleAnalytics.post({ t: "screenview", cd: "Ai Config" }); + if (!assistant) { + assistant = await promptForAssistant(); + } if (!agents?.length) { agents = await promptForAgents(); } + GoogleAnalytics.post({ t: "event", ec: "$ig ai-config", - ea: `agent: ${agents.join(", ")}` + ea: `agent: ${agents.join(", ") || "none"}` }); + configureMCP(assistant); + if (!agents.length) { Util.log("No AI configuration selected. Skipping."); return; diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index bf41928df..cd72518f8 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -7,6 +7,21 @@ export interface McpServerEntry { args: string[]; } +export type AiCodingAssistant = "vscode" | "cursor" | "claude-code" | "gemini" | "junie"; + +interface AssistantMcpConfig { + mcpFilePath: string; + rootKey: "servers" | "mcpServers"; +} + +export const AI_ASSISTANT_MCP_CONFIGS: Record = { + "vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" }, + "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, + "claude-code": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, + "gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" }, + "junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" }, +}; + const IGNITEUI_MCP_SERVERS: Record = { "igniteui-cli": { command: "npx", @@ -18,18 +33,18 @@ const IGNITEUI_MCP_SERVERS: Record = { } }; -export const VS_CODE_MCP_PATH = ".vscode/mcp.json"; - /** - * Reads .vscode/mcp.json, ensures all IgniteUI MCP servers are present, + * Reads the assistant-specific MCP config file, ensures all IgniteUI MCP servers are present, * optionally adds additional servers. Creates the file if it doesn't exist. + * @param assistant target AI coding assistant (defaults to "vscode") * @param additionalServers optional extra servers to include alongside the built-in ones * @returns whether the file was modified */ export function addMcpServers( - mcpFilePath: string, + assistant: AiCodingAssistant = "vscode", additionalServers?: Record ): boolean { + const { mcpFilePath, rootKey } = AI_ASSISTANT_MCP_CONFIGS[assistant]; const fileSystem = App.container.get(FS_TOKEN); const servers = { ...additionalServers, ...IGNITEUI_MCP_SERVERS }; @@ -44,12 +59,12 @@ export function addMcpServers( if (Object.keys(servers).length === 0) { return false; } - fileSystem.writeFile(mcpFilePath, JSON.stringify({ servers }, null, 2) + "\n"); + fileSystem.writeFile(mcpFilePath, JSON.stringify({ [rootKey]: servers }, null, 2) + "\n"); return true; } const parsed = jsonc.parse(existingContent); - const existing = parsed.servers ?? {}; + const existing = parsed[rootKey] ?? {}; const formattingOptions: jsonc.FormattingOptions = { tabSize: 2, insertSpaces: true }; let text = existingContent; @@ -57,7 +72,7 @@ export function addMcpServers( for (const [key, value] of Object.entries(servers)) { if (!existing[key]) { - const edits = jsonc.modify(text, ["servers", key], value, { formattingOptions }); + const edits = jsonc.modify(text, [rootKey, key], value, { formattingOptions }); text = jsonc.applyEdits(text, edits); modified = true; } @@ -69,3 +84,4 @@ export function addMcpServers( return modified; } + diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 6e68dc9fd..d524b5cb9 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -15,11 +15,11 @@ "enum": ["none", "claude", "copilot", "cursor", "codex", "windsurf", "gemini", "junie", "generic"] }, "x-prompt": { - "message": "Which AI tools do you want to generate configuration files for?", + "message": "Which AI agents do you want to generate skills and instructions for?", "type": "list", "multiselect": true, "items": [ - { "value": "none", "label": "None (skip AI configuration)" }, + { "value": "none", "label": "None (skip skills and instructions)" }, { "value": "generic", "label": "Generic (Adding .agents/skills and AGENTS.md)" }, { "value": "claude", "label": "Claude (Adding .claude/skills and CLAUDE.md)" }, { "value": "copilot", "label": "Copilot (Adding .github/skills and copilot-instructions.md)" }, @@ -30,6 +30,23 @@ { "value": "junie", "label": "Junie (Adding .junie/skills and .junie/guidelines.md)" } ] } + }, + "assistant": { + "type": "string", + "description": "Coding assistant to configure MCP servers for.", + "default": "vscode", + "enum": ["vscode", "cursor", "claude-code", "gemini", "junie"], + "x-prompt": { + "message": "Which coding assistant should MCP servers be configured for?", + "type": "list", + "items": [ + { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, + { "value": "cursor", "label": "Cursor" }, + { "value": "claude-code", "label": "Claude Code" }, + { "value": "gemini", "label": "Gemini" }, + { "value": "junie", "label": "JetBrains Junie" } + ] + } } } } diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index 15c72be7c..f9e4653fa 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; import { RunSchematicTask } from "@angular-devkit/schematics/tasks"; -import { addClassToBody, addMcpServers, AIAgentTarget, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; +import { addClassToBody, addMcpServers, AIAgentTarget, AiCodingAssistant, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; @@ -127,7 +127,7 @@ function appInit(tree: Tree) { setVirtual(tree); } -function aiConfig({ init, agents }: { init: boolean; agents: AIAgentTarget[] }): Rule { +function aiConfig({ init, agents, assistant = "vscode" }: { init: boolean; agents: AIAgentTarget[]; assistant?: AiCodingAssistant }): Rule { return (tree: Tree) => { if (init) { appInit(tree); @@ -142,18 +142,15 @@ function aiConfig({ init, agents }: { init: boolean; agents: AIAgentTarget[] }): } }; - addMcpServers(VS_CODE_MCP_PATH, angularCliServer); + addMcpServers(assistant, angularCliServer); }; } /** Standalone `ai-config` schematic entry */ -export function addAIConfig(options: { agents?: AIAgentTarget[] } = {}): Rule { +export function addAIConfig(options: { agents?: AIAgentTarget[]; assistant?: AiCodingAssistant } = {}): Rule { const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[]; const agents = selected.includes("none" as any) ? [] : selected; - if (!agents.length) { - return (tree: Tree) => tree; - } - return aiConfig({ init: true, agents }); + return aiConfig({ init: true, agents, assistant: options.assistant }); } export default function (): Rule { diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index 726a42241..0ea278219 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -432,5 +432,37 @@ export const appConfig: ApplicationConfig = { expect(aiSkillsModule.copyAISkillsToProject).toHaveBeenCalledWith(["claude", "cursor"]); expect(aiSkillsModule.copyAgentInstructionFiles).toHaveBeenCalledWith(["claude", "cursor"]); }); + + it("should default MCP config to .vscode/mcp.json with servers key", async () => { + await runner.runSchematic("ai-config", {}, tree); + + const mcpFilePath = "/.vscode/mcp.json"; + expect(tree.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.servers).toBeDefined(); + expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); + }); + + it("should write to .cursor/mcp.json with mcpServers key when assistant is cursor", async () => { + await runner.runSchematic("ai-config", { assistant: "cursor" }, tree); + + const mcpFilePath = "/.cursor/mcp.json"; + expect(tree.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.mcpServers).toBeDefined(); + expect(content.mcpServers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); + expect(content.mcpServers["angular-cli"]).toEqual({ command: "npx", args: ["-y", "@angular/cli", "mcp"] }); + expect(content.servers).toBeUndefined(); + }); + + it("should write to .mcp.json when assistant is claude-code", async () => { + await runner.runSchematic("ai-config", { assistant: "claude-code" }, tree); + + const mcpFilePath = "/.mcp.json"; + expect(tree.exists(mcpFilePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(mcpFilePath)); + expect(content.mcpServers["igniteui-cli"]).toBeDefined(); + expect(content.mcpServers["angular-cli"]).toBeDefined(); + }); }); }); diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 740d7847b..9bb62dbc3 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -50,6 +50,19 @@ describe("Unit - ai-config command", () => { expect((config.servers as any)[IGNITEUI_THEMING_SERVER_KEY]).toEqual(igniteuiThemingServer); }); + it("creates config with mcpServers key for non-vscode assistants", () => { + const mockFs = createMockFs(); + App.container.set(FS_TOKEN, mockFs); + + configureMCP("cursor"); + + expect(mockFs.writeFile).toHaveBeenCalledWith(".cursor/mcp.json", jasmine.any(String)); + const config = writtenConfig(mockFs); + expect((config.mcpServers as any)[IGNITEUI_SERVER_KEY]).toEqual(igniteuiServer); + expect((config.mcpServers as any)[IGNITEUI_THEMING_SERVER_KEY]).toEqual(igniteuiThemingServer); + expect(config.servers).toBeUndefined(); + }); + it("adds both servers when file exists but servers object is empty", () => { const mockFs = createMockFs(JSON.stringify({ servers: {} })); App.container.set(FS_TOKEN, mockFs); @@ -265,11 +278,12 @@ describe("Unit - ai-config command", () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); expect(InquirerWrapper.checkbox).toHaveBeenCalledWith(jasmine.objectContaining({ - message: "Which AI tools do you want to generate configuration files for?", + message: "Which AI agents do you want to generate skills and instructions for?", required: true })); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); @@ -291,6 +305,7 @@ describe("Unit - ai-config command", () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -299,15 +314,30 @@ describe("Unit - ai-config command", () => { expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: " })); }); + it("still configures MCP when none is selected for skills", async () => { + const mockFs = createMockFs(); + App.container.set(FS_TOKEN, mockFs); + spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); + + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); + + expect(mockFs.writeFile).toHaveBeenCalled(); + // TODO: toHaveBeenCalledWith check for mcp.json + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none" })); + }); + it("configures multiple agents when selected interactively", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude", "cursor"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); expect(InquirerWrapper.checkbox).toHaveBeenCalledWith(jasmine.objectContaining({ - message: "Which AI tools do you want to generate configuration files for?" + message: "Which AI agents do you want to generate skills and instructions for?" })); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: claude, cursor" })); }); @@ -315,11 +345,48 @@ describe("Unit - ai-config command", () => { it("skips prompt when --agent is provided", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(InquirerWrapper, "checkbox"); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", agent: ["cursor"] }); expect(InquirerWrapper.checkbox).not.toHaveBeenCalled(); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: cursor" })); }); + + it("skips assistant prompt when --assistant is provided", async () => { + App.container.set(FS_TOKEN, createMockFs()); + spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); + spyOn(InquirerWrapper, "select"); + + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistant: "cursor" }); + + expect(InquirerWrapper.select).not.toHaveBeenCalled(); + expect((createMockFs().writeFile as jasmine.Spy).calls || true).toBeTruthy(); + }); + + it("prompts for assistant with correct message", async () => { + App.container.set(FS_TOKEN, createMockFs()); + spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("cursor")); + + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); + + expect(InquirerWrapper.select).toHaveBeenCalledWith(jasmine.objectContaining({ + message: "Which coding assistant should MCP servers be configured for?" + })); + }); + + it("writes to correct config path for selected assistant", async () => { + const mockFs = createMockFs(); + App.container.set(FS_TOKEN, mockFs); + spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); + spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("claude-code")); + + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); + + expect(mockFs.writeFile).toHaveBeenCalledWith(".mcp.json", jasmine.any(String)); + const config = writtenConfig(mockFs); + expect((config.mcpServers as any)[IGNITEUI_SERVER_KEY]).toEqual(igniteuiServer); + }); }); }); From 322523ebb4310b5c3cc158292cc3f4c05c9e58a1 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Fri, 8 May 2026 12:42:19 +0300 Subject: [PATCH 02/20] feat: update MCP configuration to support multiple coding assistants and enhance related prompts Co-authored-by: Copilot --- packages/cli/lib/commands/ai-config.ts | 57 ++++++++++++------- .../src/cli-config/ai-config-schema.json | 15 +++-- .../ng-schematics/src/cli-config/index.ts | 12 ++-- .../src/cli-config/index_spec.ts | 4 +- spec/unit/ai-config-spec.ts | 49 +++++++++------- 5 files changed, 85 insertions(+), 52 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 289fda4d1..531a7c575 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,15 +1,17 @@ import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; -export function configureMCP(assistant: AiCodingAssistant = "vscode"): void { - const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant]; - const modified = addMcpServers(assistant); - - if (!modified) { - Util.log(` Ignite UI MCP servers already configured in ${mcpFilePath}`); - return; +export function configureMCP(assistants: AiCodingAssistant[] = ["vscode"]): void { + for (const assistant of assistants) { + const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant]; + const modified = addMcpServers(assistant); + + if (!modified) { + Util.log(` Ignite UI MCP servers already configured in ${mcpFilePath}`); + } else { + Util.log(Util.greenCheck() + ` MCP servers configured in ${mcpFilePath}`); + } } - Util.log(Util.greenCheck() + ` MCP servers configured in ${mcpFilePath}`); } export function configureSkills(agents: AIAgentTarget[]): void { @@ -27,12 +29,12 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents?: AIAgentTarget[], skills = true, assistant: AiCodingAssistant = "vscode"): Promise { +export async function configure(agents?: AIAgentTarget[], skills = true, assistants: AiCodingAssistant[] = ["vscode"]): Promise { if (!agents?.length) { agents = await promptForAgents(); } if (!agents.length) return; - configureMCP(assistant); + configureMCP(assistants); if (skills) { configureSkills(agents); } @@ -59,6 +61,15 @@ const AI_AGENT_CHECKBOX_CHOICES = [ })) ]; +const AI_ASSISTANT_CHECKBOX_CHOICES = [ + { value: "none", name: "None (skip MCP configuration)" }, + ...AI_ASSISTANT_CHOICES.map(a => ({ + value: a, + name: AI_ASSISTANT_LABELS[a], + checked: a === "vscode" + })) +]; + export async function promptForAgents(): Promise { let selected: AIAgentTarget[] = AI_AGENT_CHECKBOX_DEFAULTS; if (Util.canPrompt()) { @@ -72,12 +83,14 @@ export async function promptForAgents(): Promise { return selected; } -export async function promptForAssistant(): Promise { - const selected = await InquirerWrapper.select({ - message: "Which coding assistant should MCP servers be configured for?", - choices: AI_ASSISTANT_CHOICES.map(a => ({ value: a, name: AI_ASSISTANT_LABELS[a] })) +export async function promptForAssistant(): Promise { + // TODO: check Util.canPrompt() and assign defaults + const selected = await InquirerWrapper.checkbox({ + message: "Which coding assistants should MCP servers be configured for?", + required: true, + choices: AI_ASSISTANT_CHECKBOX_CHOICES }); - return selected as AiCodingAssistant; + return selected.includes("none") ? [] : selected as AiCodingAssistant[]; } const command: CommandModule = { @@ -92,21 +105,21 @@ const command: CommandModule = { type: "array" }) .option("assistant", { - describe: "Coding assistant to configure MCP servers for", + describe: "Coding assistant(s) to configure MCP servers for", choices: AI_ASSISTANT_CHOICES, - type: "string" + type: "array" }), async handler(argv: ArgumentsCamelCase) { let agents = argv.agent as AIAgentTarget[] | undefined; - let assistant = argv.assistant as AiCodingAssistant | undefined; + let assistants = argv.assistant as AiCodingAssistant[] | undefined; GoogleAnalytics.post({ t: "screenview", cd: "Ai Config" }); - if (!assistant) { - assistant = await promptForAssistant(); + if (!assistants?.length) { + assistants = await promptForAssistant(); } if (!agents?.length) { agents = await promptForAgents(); @@ -118,7 +131,9 @@ const command: CommandModule = { ea: `agent: ${agents.join(", ") || "none"}` }); - configureMCP(assistant); + if (assistants.length) { + configureMCP(assistants); + } if (!agents.length) { Util.log("No AI configuration selected. Skipping."); diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index d524b5cb9..8f176d9f3 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -32,14 +32,19 @@ } }, "assistant": { - "type": "string", - "description": "Coding assistant to configure MCP servers for.", - "default": "vscode", - "enum": ["vscode", "cursor", "claude-code", "gemini", "junie"], + "type": "array", + "description": "Coding assistant(s) to configure MCP servers for.", + "default": ["vscode"], + "items": { + "type": "string", + "enum": ["none", "vscode", "cursor", "claude-code", "gemini", "junie"] + }, "x-prompt": { - "message": "Which coding assistant should MCP servers be configured for?", + "message": "Which coding assistants should MCP servers be configured for?", "type": "list", + "multiselect": true, "items": [ + { "value": "none", "label": "None (skip MCP configuration)" }, { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, { "value": "cursor", "label": "Cursor" }, { "value": "claude-code", "label": "Claude Code" }, diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index f9e4653fa..ae4d922dc 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -127,7 +127,7 @@ function appInit(tree: Tree) { setVirtual(tree); } -function aiConfig({ init, agents, assistant = "vscode" }: { init: boolean; agents: AIAgentTarget[]; assistant?: AiCodingAssistant }): Rule { +function aiConfig({ init, agents, assistants = ["vscode"] }: { init: boolean; agents: AIAgentTarget[]; assistants?: AiCodingAssistant[] }): Rule { return (tree: Tree) => { if (init) { appInit(tree); @@ -142,15 +142,19 @@ function aiConfig({ init, agents, assistant = "vscode" }: { init: boolean; agent } }; - addMcpServers(assistant, angularCliServer); + for (const assistant of assistants) { + addMcpServers(assistant, angularCliServer); + } }; } /** Standalone `ai-config` schematic entry */ -export function addAIConfig(options: { agents?: AIAgentTarget[]; assistant?: AiCodingAssistant } = {}): Rule { +export function addAIConfig(options: { agents?: AIAgentTarget[]; /* TODO: assistants */ assistant?: AiCodingAssistant[] } = {}): Rule { const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[]; const agents = selected.includes("none" as any) ? [] : selected; - return aiConfig({ init: true, agents, assistant: options.assistant }); + const selectedAssistants = options.assistant?.length ? options.assistant : ["vscode"] as AiCodingAssistant[]; + const assistants = selectedAssistants.includes("none" as any) ? [] : selectedAssistants; + return aiConfig({ init: true, agents, assistants }); } export default function (): Rule { diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index 0ea278219..7a7c93f5e 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -444,7 +444,7 @@ export const appConfig: ApplicationConfig = { }); it("should write to .cursor/mcp.json with mcpServers key when assistant is cursor", async () => { - await runner.runSchematic("ai-config", { assistant: "cursor" }, tree); + await runner.runSchematic("ai-config", { assistant: ["cursor"] }, tree); const mcpFilePath = "/.cursor/mcp.json"; expect(tree.exists(mcpFilePath)).toBeTruthy(); @@ -456,7 +456,7 @@ export const appConfig: ApplicationConfig = { }); it("should write to .mcp.json when assistant is claude-code", async () => { - await runner.runSchematic("ai-config", { assistant: "claude-code" }, tree); + await runner.runSchematic("ai-config", { assistant: ["claude-code"] }, tree); const mcpFilePath = "/.mcp.json"; expect(tree.exists(mcpFilePath)).toBeTruthy(); diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 9bb62dbc3..07485fdbb 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -54,7 +54,7 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); - configureMCP("cursor"); + configureMCP(["cursor"]); expect(mockFs.writeFile).toHaveBeenCalledWith(".cursor/mcp.json", jasmine.any(String)); const config = writtenConfig(mockFs); @@ -277,8 +277,10 @@ describe("Unit - ai-config command", () => { it("prompts for agents when --agent is not provided", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); - spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); + spyOn(InquirerWrapper, "checkbox").and.returnValues( + Promise.resolve(["vscode"]), + Promise.resolve(["claude"]) + ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -317,8 +319,10 @@ describe("Unit - ai-config command", () => { it("still configures MCP when none is selected for skills", async () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); - spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); + spyOn(InquirerWrapper, "checkbox").and.returnValues( + Promise.resolve(["vscode"]), + Promise.resolve(["none"]) + ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -331,8 +335,10 @@ describe("Unit - ai-config command", () => { it("configures multiple agents when selected interactively", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); - spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude", "cursor"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); + spyOn(InquirerWrapper, "checkbox").and.returnValues( + Promise.resolve(["vscode"]), + Promise.resolve(["claude", "cursor"]) + ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -344,43 +350,46 @@ describe("Unit - ai-config command", () => { it("skips prompt when --agent is provided", async () => { App.container.set(FS_TOKEN, createMockFs()); - spyOn(InquirerWrapper, "checkbox"); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); + spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["vscode"])); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", agent: ["cursor"] }); - expect(InquirerWrapper.checkbox).not.toHaveBeenCalled(); + expect(InquirerWrapper.checkbox).not.toHaveBeenCalledWith(jasmine.objectContaining({ + message: "Which AI agents do you want to generate skills and instructions for?" + })); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: cursor" })); }); it("skips assistant prompt when --assistant is provided", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); - spyOn(InquirerWrapper, "select"); - await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistant: "cursor" }); + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistant: ["cursor"] }); - expect(InquirerWrapper.select).not.toHaveBeenCalled(); - expect((createMockFs().writeFile as jasmine.Spy).calls || true).toBeTruthy(); + expect(InquirerWrapper.checkbox).toHaveBeenCalledTimes(1); }); it("prompts for assistant with correct message", async () => { App.container.set(FS_TOKEN, createMockFs()); - spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("cursor")); + spyOn(InquirerWrapper, "checkbox").and.returnValues( + Promise.resolve(["cursor"]), + Promise.resolve(["claude"]) + ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); - expect(InquirerWrapper.select).toHaveBeenCalledWith(jasmine.objectContaining({ - message: "Which coding assistant should MCP servers be configured for?" + expect(InquirerWrapper.checkbox).toHaveBeenCalledWith(jasmine.objectContaining({ + message: "Which coding assistants should MCP servers be configured for?" })); }); it("writes to correct config path for selected assistant", async () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); - spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("claude-code")); + spyOn(InquirerWrapper, "checkbox").and.returnValues( + Promise.resolve(["claude-code"]), + Promise.resolve(["none"]) + ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); From 5ae1e914d8a81acd151273349442a1bdecd03fee Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Mon, 11 May 2026 16:50:24 +0300 Subject: [PATCH 03/20] feat: enhance AI configuration by adding assistant labels and improving agent selection logic --- packages/cli/lib/commands/ai-config.ts | 52 ++++++++----------- packages/core/util/mcp-config.ts | 13 ++++- .../ng-schematics/src/cli-config/index.ts | 2 +- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 531a7c575..bb27e1306 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,4 +1,4 @@ -import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS } from "@igniteui/cli-core"; +import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS, AI_ASSISTANT_CHOICES, AI_ASSISTANT_LABELS } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; export function configureMCP(assistants: AiCodingAssistant[] = ["vscode"]): void { @@ -30,11 +30,8 @@ export function configureSkills(agents: AIAgentTarget[]): void { } export async function configure(agents?: AIAgentTarget[], skills = true, assistants: AiCodingAssistant[] = ["vscode"]): Promise { - if (!agents?.length) { - agents = await promptForAgents(); - } - if (!agents.length) return; configureMCP(assistants); + if (!agents?.length) return; if (skills) { configureSkills(agents); } @@ -42,15 +39,7 @@ export async function configure(agents?: AIAgentTarget[], skills = true, assista } const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; -const AI_ASSISTANT_CHOICES = Object.keys(AI_ASSISTANT_MCP_CONFIGS) as AiCodingAssistant[]; - -const AI_ASSISTANT_LABELS: Record = { - "vscode": "VS Code (GitHub Copilot)", - "cursor": "Cursor", - "claude-code": "Claude Code", - "gemini": "Gemini", - "junie": "JetBrains Junie", -}; +const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["vscode", "claude-code"]; const AI_AGENT_CHECKBOX_CHOICES = [ { value: "none", name: "None (skip skills and instructions)" }, @@ -66,7 +55,7 @@ const AI_ASSISTANT_CHECKBOX_CHOICES = [ ...AI_ASSISTANT_CHOICES.map(a => ({ value: a, name: AI_ASSISTANT_LABELS[a], - checked: a === "vscode" + checked: AI_ASSISTANT_CHECKBOX_DEFAULTS.includes(a) })) ]; @@ -84,13 +73,16 @@ export async function promptForAgents(): Promise { } export async function promptForAssistant(): Promise { - // TODO: check Util.canPrompt() and assign defaults - const selected = await InquirerWrapper.checkbox({ - message: "Which coding assistants should MCP servers be configured for?", - required: true, - choices: AI_ASSISTANT_CHECKBOX_CHOICES - }); - return selected.includes("none") ? [] : selected as AiCodingAssistant[]; + let selected: AiCodingAssistant[] = AI_ASSISTANT_CHECKBOX_DEFAULTS; + if (Util.canPrompt()) { + const result = await InquirerWrapper.checkbox({ + message: "Which coding assistants should MCP servers be configured for?", + required: true, + choices: AI_ASSISTANT_CHECKBOX_CHOICES + }); + selected = result.includes("none") ? [] : result as AiCodingAssistant[]; + } + return selected; } const command: CommandModule = { @@ -112,34 +104,34 @@ const command: CommandModule = { async handler(argv: ArgumentsCamelCase) { let agents = argv.agent as AIAgentTarget[] | undefined; let assistants = argv.assistant as AiCodingAssistant[] | undefined; - GoogleAnalytics.post({ t: "screenview", cd: "Ai Config" }); - if (!assistants?.length) { - assistants = await promptForAssistant(); - } if (!agents?.length) { agents = await promptForAgents(); } + if (!assistants?.length) { + assistants = await promptForAssistant(); + } GoogleAnalytics.post({ t: "event", ec: "$ig ai-config", - ea: `agent: ${agents.join(", ") || "none"}` + ea: `agent: ${agents?.join(", ") || "none"}; assistant: ${assistants?.join(", ") || "none"}` }); - if (assistants.length) { - configureMCP(assistants); + if (!assistants.length) { + Util.log("No MCP configuration selected. Skipping."); + return; } if (!agents.length) { Util.log("No AI configuration selected. Skipping."); return; } - await configure(agents); + await configure(agents, true, assistants); } }; diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index cd72518f8..f01ad99b0 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -7,17 +7,26 @@ export interface McpServerEntry { args: string[]; } -export type AiCodingAssistant = "vscode" | "cursor" | "claude-code" | "gemini" | "junie"; +export const AI_ASSISTANT_CHOICES = ["vscode", "claude-code", "cursor", "gemini", "junie"] as const; +export type AiCodingAssistant = typeof AI_ASSISTANT_CHOICES[number]; interface AssistantMcpConfig { mcpFilePath: string; rootKey: "servers" | "mcpServers"; } +export const AI_ASSISTANT_LABELS: Record = { + "vscode": "VS Code (GitHub Copilot)", + "claude-code": "Claude Code", + "cursor": "Cursor", + "gemini": "Gemini", + "junie": "JetBrains Junie", +}; + export const AI_ASSISTANT_MCP_CONFIGS: Record = { "vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" }, - "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, "claude-code": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, + "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, "gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" }, "junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" }, }; diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index ae4d922dc..a6a04d546 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -2,7 +2,7 @@ import * as ts from "typescript"; import { DependencyNotFoundException } from "@angular-devkit/core"; import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics"; import { RunSchematicTask } from "@angular-devkit/schematics/tasks"; -import { addClassToBody, addMcpServers, AIAgentTarget, AiCodingAssistant, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils, VS_CODE_MCP_PATH } from "@igniteui/cli-core"; +import { addClassToBody, addMcpServers, AIAgentTarget, AiCodingAssistant, App, copyAgentInstructionFiles, copyAISkillsToProject, FormatSettings, McpServerEntry, NPM_ANGULAR, resolvePackage, TEMPLATE_MANAGER, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core"; import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates"; import { createCliConfig } from "../utils/cli-config"; import { setVirtual } from "../utils/NgFileSystem"; From 11b80bb4da80e815dafc7ba283a50d3ca1606754 Mon Sep 17 00:00:00 2001 From: Marina Stoyanova Date: Mon, 11 May 2026 16:50:56 +0300 Subject: [PATCH 04/20] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- spec/unit/ai-config-spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 07485fdbb..fdbaf9bd3 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -313,7 +313,7 @@ describe("Unit - ai-config command", () => { expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("Skipping")); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: " })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: none" })); }); it("still configures MCP when none is selected for skills", async () => { From 8e8fb4523010576569e870a43e80cc0ad2358e02 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Mon, 11 May 2026 18:43:43 +0300 Subject: [PATCH 05/20] feat: update AI configuration to include assistant selection and modify related commands --- .vscode/launch.json | 7 +++---- packages/cli/lib/commands/ai-config.ts | 12 ++++++++++-- packages/cli/lib/commands/new.ts | 4 ++-- spec/unit/ai-config-spec.ts | 12 ++++++------ 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index e3596beae..8b2d31748 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -80,12 +80,11 @@ "program": "${workspaceRoot}/packages/cli/bin/execute.js", "console": "externalTerminal", "preLaunchTask": "build", - "outFiles": [ "${workspaceRoot}/lib/**/*.js", - "${workspaceRoot}/spec/**/*.js" ], "args": [ "new", "angularproj", - "--framework=angular" + "--framework=angular", + "--skip-install" ] }, { @@ -298,7 +297,7 @@ "preLaunchTask": "build", "outFiles": ["${workspaceFolder}/**/*.js"], "args": [ - "list" + "ai-config" ] }, { "type": "node", diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index bb27e1306..0e0c95209 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -29,14 +29,22 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents?: AIAgentTarget[], skills = true, assistants: AiCodingAssistant[] = ["vscode"]): Promise { +export async function configure(agents?: AIAgentTarget[], skills = true, assistants?: AiCodingAssistant[]): Promise { + if (!agents?.length) { + agents = await promptForAgents(); + } + + if (!assistants?.length) { + assistants = await promptForAssistant(); + } configureMCP(assistants); - if (!agents?.length) return; + if (skills) { configureSkills(agents); } copyAgentInstructionFiles(agents); } + const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["vscode", "claude-code"]; diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 9e1c94618..5803f979e 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -1,4 +1,4 @@ -import { AI_AGENT_CHOICES, AIAgentTarget, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core"; +import { AI_AGENT_CHOICES, AIAgentTarget, AiCodingAssistant, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core"; import * as path from "path"; import { PromptSession } from "./../PromptSession"; import { NewCommandType, PositionalArgs } from "./types"; @@ -166,7 +166,7 @@ const command: NewCommandType = { } process.chdir(argv.name); - await configure(argv.agents as AIAgentTarget[] | undefined); + await configure(argv.agents as AIAgentTarget[] | undefined, true, argv.assistants as AiCodingAssistant[] | undefined); process.chdir(".."); if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) { diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index fdbaf9bd3..ca242ed6f 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -289,7 +289,7 @@ describe("Unit - ai-config command", () => { required: true })); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: claude" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: claude; assistant: vscode" })); }); it("uses defaults without prompting when canPrompt returns false", async () => { @@ -300,7 +300,7 @@ describe("Unit - ai-config command", () => { await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); expect(InquirerWrapper.checkbox).not.toHaveBeenCalled(); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: generic, claude" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: generic, claude; assistant: vscode, claude-code" })); }); it("logs skipping and does not post analytics when none is selected", async () => { @@ -313,7 +313,7 @@ describe("Unit - ai-config command", () => { expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("Skipping")); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: none" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: none; assistant: none" })); }); it("still configures MCP when none is selected for skills", async () => { @@ -329,7 +329,7 @@ describe("Unit - ai-config command", () => { expect(mockFs.writeFile).toHaveBeenCalled(); // TODO: toHaveBeenCalledWith check for mcp.json expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none; assistant: none" })); }); it("configures multiple agents when selected interactively", async () => { @@ -345,7 +345,7 @@ describe("Unit - ai-config command", () => { expect(InquirerWrapper.checkbox).toHaveBeenCalledWith(jasmine.objectContaining({ message: "Which AI agents do you want to generate skills and instructions for?" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: claude, cursor" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: claude, cursor; assistant: vscode" })); }); it("skips prompt when --agent is provided", async () => { @@ -357,7 +357,7 @@ describe("Unit - ai-config command", () => { expect(InquirerWrapper.checkbox).not.toHaveBeenCalledWith(jasmine.objectContaining({ message: "Which AI agents do you want to generate skills and instructions for?" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: cursor" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: cursor; assistant: vscode" })); }); it("skips assistant prompt when --assistant is provided", async () => { From 6c408a833644dc4afecb53b293602885aea32f98 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 11:13:23 +0300 Subject: [PATCH 06/20] fix: ai-config tests --- spec/unit/ai-config-spec.ts | 31 +++++++++++++++++-------------- spec/unit/new-spec.ts | 8 ++++---- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index ca242ed6f..46548611b 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -278,8 +278,8 @@ describe("Unit - ai-config command", () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( - Promise.resolve(["vscode"]), - Promise.resolve(["claude"]) + Promise.resolve(["claude"]), + Promise.resolve(["vscode"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -307,7 +307,6 @@ describe("Unit - ai-config command", () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["none"])); - spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("vscode")); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -319,25 +318,25 @@ describe("Unit - ai-config command", () => { it("still configures MCP when none is selected for skills", async () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); + spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( - Promise.resolve(["vscode"]), - Promise.resolve(["none"]) + Promise.resolve(["none"]), + Promise.resolve(["vscode"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); - expect(mockFs.writeFile).toHaveBeenCalled(); - // TODO: toHaveBeenCalledWith check for mcp.json expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none; assistant: none" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none; assistant: vscode" })); + expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("Skipping")); }); it("configures multiple agents when selected interactively", async () => { App.container.set(FS_TOKEN, createMockFs()); spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( - Promise.resolve(["vscode"]), - Promise.resolve(["claude", "cursor"]) + Promise.resolve(["claude", "cursor"]), + Promise.resolve(["vscode"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -350,6 +349,7 @@ describe("Unit - ai-config command", () => { it("skips prompt when --agent is provided", async () => { App.container.set(FS_TOKEN, createMockFs()); + spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["vscode"])); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", agent: ["cursor"] }); @@ -362,6 +362,7 @@ describe("Unit - ai-config command", () => { it("skips assistant prompt when --assistant is provided", async () => { App.container.set(FS_TOKEN, createMockFs()); + spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistant: ["cursor"] }); @@ -371,9 +372,10 @@ describe("Unit - ai-config command", () => { it("prompts for assistant with correct message", async () => { App.container.set(FS_TOKEN, createMockFs()); + spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( - Promise.resolve(["cursor"]), - Promise.resolve(["claude"]) + Promise.resolve(["claude"]), + Promise.resolve(["vscode"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); @@ -386,9 +388,10 @@ describe("Unit - ai-config command", () => { it("writes to correct config path for selected assistant", async () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); + spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( - Promise.resolve(["claude-code"]), - Promise.resolve(["none"]) + Promise.resolve(["claude"]), + Promise.resolve(["claude-code"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); diff --git a/spec/unit/new-spec.ts b/spec/unit/new-spec.ts index 77d8376ec..dcab10627 100644 --- a/spec/unit/new-spec.ts +++ b/spec/unit/new-spec.ts @@ -408,7 +408,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", agents: ["claude", "cursor"], _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(["claude", "cursor"]); + expect(configureSpy).toHaveBeenCalledWith(["claude", "cursor"], true, undefined); }); it("calls configure with undefined when --agents is not provided", async () => { @@ -416,7 +416,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(undefined); + expect(configureSpy).toHaveBeenCalledWith(undefined, true, undefined); }); it("calls configure with single agent", async () => { @@ -424,7 +424,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", agents: ["generic"], _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(["generic"]); + expect(configureSpy).toHaveBeenCalledWith(["generic"], true, undefined); }); it("calls configure after project creation and package install", async () => { @@ -469,7 +469,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", skipInstall: true, agents: ["claude"], _: ["new"], $0: "new" }); expect(PackageManager.installPackages).not.toHaveBeenCalled(); - expect(configureSpy).toHaveBeenCalledWith(["claude"]); + expect(configureSpy).toHaveBeenCalledWith(["claude"], true, undefined); }); it("does not call configure when project creation fails (bad name)", async () => { From 39bba3426bcd34c4a5eb5172da34d17f896b8a12 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 14:08:51 +0300 Subject: [PATCH 07/20] feat: update AI assistant configuration to include 'general' and modify related commands --- packages/cli/lib/commands/ai-config.ts | 22 +++++++++++-------- packages/cli/lib/commands/new.ts | 15 ++++++++++--- packages/core/util/mcp-config.ts | 6 ++--- .../ng-schematics/src/cli-config/index.ts | 8 ++++--- spec/acceptance/help-spec.ts | 5 ++++- spec/unit/ai-config-spec.ts | 8 +++---- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 0e0c95209..e4b08427a 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -47,7 +47,7 @@ export async function configure(agents?: AIAgentTarget[], skills = true, assista const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; -const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["vscode", "claude-code"]; +const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["general"]; const AI_AGENT_CHECKBOX_CHOICES = [ { value: "none", name: "None (skip skills and instructions)" }, @@ -98,29 +98,33 @@ const command: CommandModule = { describe: "Configures Ignite UI AI tooling (MCP servers, AI coding skills and instructions)", builder: (yargs) => yargs .usage("") - .option("agent", { + .option("agents", { alias: "a", describe: "AI agents/tools to generate configuration files for", - choices: AI_AGENT_CHOICES, + choices: [...AI_AGENT_CHOICES, "none"] as string[], type: "array" }) - .option("assistant", { + .option("assistants", { describe: "Coding assistant(s) to configure MCP servers for", - choices: AI_ASSISTANT_CHOICES, + choices: [...AI_ASSISTANT_CHOICES, "none"] as string[], type: "array" }), async handler(argv: ArgumentsCamelCase) { - let agents = argv.agent as AIAgentTarget[] | undefined; - let assistants = argv.assistant as AiCodingAssistant[] | undefined; + const rawAgents = argv.agents as string[] | undefined; + const rawAssistants = argv.assistants as string[] | undefined; + const agentNoneSelected = rawAgents ? rawAgents.indexOf("none") !== -1 : false; + const assistantNoneSelected = rawAssistants ? rawAssistants.indexOf("none") !== -1 : false; + let agents = (rawAgents?.filter(a => a !== "none") ?? []) as AIAgentTarget[]; + let assistants = (rawAssistants?.filter(a => a !== "none") ?? []) as AiCodingAssistant[]; GoogleAnalytics.post({ t: "screenview", cd: "Ai Config" }); - if (!agents?.length) { + if (!agentNoneSelected && !agents.length) { agents = await promptForAgents(); } - if (!assistants?.length) { + if (!assistantNoneSelected && !assistants.length) { assistants = await promptForAssistant(); } diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 5803f979e..7e82e133b 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -1,4 +1,4 @@ -import { AI_AGENT_CHOICES, AIAgentTarget, AiCodingAssistant, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core"; +import { AI_AGENT_CHOICES, AIAgentTarget, AI_ASSISTANT_CHOICES, AiCodingAssistant, GoogleAnalytics, PackageManager, ProjectConfig, ProjectLibrary, Util } from "@igniteui/cli-core"; import * as path from "path"; import { PromptSession } from "./../PromptSession"; import { NewCommandType, PositionalArgs } from "./types"; @@ -63,7 +63,12 @@ const command: NewCommandType = { .option("agents", { alias: "a", describe: "AI agents/tools to generate configuration files for", - choices: AI_AGENT_CHOICES, + choices: [...AI_AGENT_CHOICES, "none"] as string[], + type: "array" + }) + .option("assistants", { + describe: "Coding assistant(s) to configure MCP servers for", + choices: [...AI_ASSISTANT_CHOICES, "none"] as string[], type: "array" }) .example("$0 new my-app", "Scaffold a new project interactively") @@ -166,7 +171,11 @@ const command: NewCommandType = { } process.chdir(argv.name); - await configure(argv.agents as AIAgentTarget[] | undefined, true, argv.assistants as AiCodingAssistant[] | undefined); + const rawAgents = argv.agents as string[] | undefined; + const filteredAgents = rawAgents?.filter(a => a !== "none") as AIAgentTarget[] | undefined; + if (rawAgents == null || rawAgents.indexOf("none") === -1 || filteredAgents?.length) { + await configure(filteredAgents, true, argv.assistants as AiCodingAssistant[] | undefined); + } process.chdir(".."); if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) { diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index f01ad99b0..d3304cb0b 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -7,7 +7,7 @@ export interface McpServerEntry { args: string[]; } -export const AI_ASSISTANT_CHOICES = ["vscode", "claude-code", "cursor", "gemini", "junie"] as const; +export const AI_ASSISTANT_CHOICES = ["general", "vscode", "cursor", "gemini", "junie"] as const; export type AiCodingAssistant = typeof AI_ASSISTANT_CHOICES[number]; interface AssistantMcpConfig { @@ -16,16 +16,16 @@ interface AssistantMcpConfig { } export const AI_ASSISTANT_LABELS: Record = { + "general": "mcp.json (general for Claude Code, VS Code, and other assistants)", "vscode": "VS Code (GitHub Copilot)", - "claude-code": "Claude Code", "cursor": "Cursor", "gemini": "Gemini", "junie": "JetBrains Junie", }; export const AI_ASSISTANT_MCP_CONFIGS: Record = { + "general": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, "vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" }, - "claude-code": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, "gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" }, "junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" }, diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index a6a04d546..e2bac5ed4 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -149,11 +149,13 @@ function aiConfig({ init, agents, assistants = ["vscode"] }: { init: boolean; ag } /** Standalone `ai-config` schematic entry */ -export function addAIConfig(options: { agents?: AIAgentTarget[]; /* TODO: assistants */ assistant?: AiCodingAssistant[] } = {}): Rule { +export function addAIConfig(options: { agents?: AIAgentTarget[]; /* TODO: assistants */ assistant?: string[] } = {}): Rule { const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[]; const agents = selected.includes("none" as any) ? [] : selected; - const selectedAssistants = options.assistant?.length ? options.assistant : ["vscode"] as AiCodingAssistant[]; - const assistants = selectedAssistants.includes("none" as any) ? [] : selectedAssistants; + const selectedAssistants = options.assistant?.length ? options.assistant : ["vscode"]; + const assistants = selectedAssistants + .filter(a => a !== "none") + .map(a => a === "claude-code" ? "general" : a) as AiCodingAssistant[]; return aiConfig({ init: true, agents, assistants }); } diff --git a/spec/acceptance/help-spec.ts b/spec/acceptance/help-spec.ts index 846fecc44..f2bb1f91c 100644 --- a/spec/acceptance/help-spec.ts +++ b/spec/acceptance/help-spec.ts @@ -72,7 +72,10 @@ describe("Help command", () => { --template Project template [string] -a, --agents AI agents/tools to generate configuration files for [array] [choices: "generic", "claude", "copilot", "cursor", "codex", - "windsurf", "gemini", "junie"] + "windsurf", "gemini", "junie", "none"] + --assistants Coding assistant(s) to configure MCP servers for + [array] [choices: "general", "vscode", "cursor", "gemini", "junie", + "none"] Examples: ig new my-app Scaffold a new project interactively diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 46548611b..1873ce9ef 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -300,7 +300,7 @@ describe("Unit - ai-config command", () => { await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); expect(InquirerWrapper.checkbox).not.toHaveBeenCalled(); - expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: generic, claude; assistant: vscode, claude-code" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: generic, claude; assistant: general" })); }); it("logs skipping and does not post analytics when none is selected", async () => { @@ -352,7 +352,7 @@ describe("Unit - ai-config command", () => { spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["vscode"])); - await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", agent: ["cursor"] }); + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", agents: ["cursor"] }); expect(InquirerWrapper.checkbox).not.toHaveBeenCalledWith(jasmine.objectContaining({ message: "Which AI agents do you want to generate skills and instructions for?" @@ -365,7 +365,7 @@ describe("Unit - ai-config command", () => { spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValue(Promise.resolve(["claude"])); - await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistant: ["cursor"] }); + await aiConfig.default.handler({ _: ["ai-config"], $0: "ig", assistants: ["cursor"] }); expect(InquirerWrapper.checkbox).toHaveBeenCalledTimes(1); }); @@ -391,7 +391,7 @@ describe("Unit - ai-config command", () => { spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( Promise.resolve(["claude"]), - Promise.resolve(["claude-code"]) + Promise.resolve(["general"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); From d50be4561aa3ea2afb9b4a45cc9a03945fe2e0cb Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 14:19:37 +0300 Subject: [PATCH 08/20] fix: adjust order of operations in new command to configure before package installation --- packages/cli/lib/commands/new.ts | 11 ++++++----- spec/unit/new-spec.ts | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 7e82e133b..8ba68c2ea 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -164,11 +164,6 @@ const command: NewCommandType = { Util.log(Util.greenCheck() + " Project Created"); - if (!argv.skipInstall) { - process.chdir(argv.name); - await PackageManager.installPackages(); - process.chdir(".."); - } process.chdir(argv.name); const rawAgents = argv.agents as string[] | undefined; @@ -178,6 +173,12 @@ const command: NewCommandType = { } process.chdir(".."); + if (!argv.skipInstall) { + process.chdir(argv.name); + await PackageManager.installPackages(); + process.chdir(".."); + } + if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) { Util.gitInit(process.cwd(), argv.name); } diff --git a/spec/unit/new-spec.ts b/spec/unit/new-spec.ts index dcab10627..27ec60d44 100644 --- a/spec/unit/new-spec.ts +++ b/spec/unit/new-spec.ts @@ -427,7 +427,7 @@ describe("Unit - New command", () => { expect(configureSpy).toHaveBeenCalledWith(["generic"], true, undefined); }); - it("calls configure after project creation and package install", async () => { + it("calls configure before package install", async () => { createProjectMocks(); const callOrder: string[] = []; (PackageManager.installPackages as jasmine.Spy).and.callFake(() => { @@ -441,7 +441,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", agents: ["claude"], _: ["new"], $0: "new" }); - expect(callOrder).toEqual(["install", "configure"]); + expect(callOrder).toEqual(["configure", "install"]); }); it("calls configure from within the project directory", async () => { From 2f7b429e7c25717b18a32c54b83fb4dd843aa492 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 14:25:30 +0300 Subject: [PATCH 09/20] fix: update launch configuration to change argument from 'ai-config' to 'list' --- .vscode/launch.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8b2d31748..9be8ccd85 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -297,7 +297,7 @@ "preLaunchTask": "build", "outFiles": ["${workspaceFolder}/**/*.js"], "args": [ - "ai-config" + "list" ] }, { "type": "node", From 00758bb6206b2e079ff7ffd0e4c33e25b3358d38 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 14:43:33 +0300 Subject: [PATCH 10/20] fix: update AI assistant labels for clarity and consistency --- packages/core/util/ai-skills.ts | 2 +- packages/core/util/mcp-config.ts | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/core/util/ai-skills.ts b/packages/core/util/ai-skills.ts index 52326cbc1..34cb5f7f7 100644 --- a/packages/core/util/ai-skills.ts +++ b/packages/core/util/ai-skills.ts @@ -35,7 +35,7 @@ const AI_AGENT_INSTRUCTION_FILES: Record = { }; export const AI_AGENT_LABELS: Record = { - generic: "Generic (Adding .agents/skills and AGENTS.md)", + generic: "Generic (Adding .agents/skills and AGENTS.md general for most assistants)", claude: "Claude (Adding .claude/skills and CLAUDE.md)", copilot: "Copilot (Adding .github/skills and copilot-instructions.md)", cursor: "Cursor (Adding .cursor/skills and .cursor/rules/cursor.mdc)", diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index d3304cb0b..dc22b954e 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -16,19 +16,19 @@ interface AssistantMcpConfig { } export const AI_ASSISTANT_LABELS: Record = { - "general": "mcp.json (general for Claude Code, VS Code, and other assistants)", - "vscode": "VS Code (GitHub Copilot)", - "cursor": "Cursor", - "gemini": "Gemini", - "junie": "JetBrains Junie", + "general": ".mcp.json (general for Claude Code, VS Code, and other assistants)", + "vscode": "VS Code (GitHub Copilot)", + "cursor": "Cursor", + "gemini": "Gemini", + "junie": "JetBrains Junie", }; export const AI_ASSISTANT_MCP_CONFIGS: Record = { - "general": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, - "vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" }, - "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, - "gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" }, - "junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" }, + "general": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, + "vscode": { mcpFilePath: ".vscode/mcp.json", rootKey: "servers" }, + "cursor": { mcpFilePath: ".cursor/mcp.json", rootKey: "mcpServers" }, + "gemini": { mcpFilePath: ".gemini/settings.json", rootKey: "mcpServers" }, + "junie": { mcpFilePath: ".junie/mcp/mcp.json", rootKey: "mcpServers" }, }; const IGNITEUI_MCP_SERVERS: Record = { From b11904955f1dbcbe29708771e819fb8fe476a360 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 14:54:43 +0300 Subject: [PATCH 11/20] fix: update addAIConfig function to correct assistants parameter and default value --- packages/ng-schematics/src/cli-config/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index e2bac5ed4..e28ac0814 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -149,10 +149,10 @@ function aiConfig({ init, agents, assistants = ["vscode"] }: { init: boolean; ag } /** Standalone `ai-config` schematic entry */ -export function addAIConfig(options: { agents?: AIAgentTarget[]; /* TODO: assistants */ assistant?: string[] } = {}): Rule { +export function addAIConfig(options: { agents?: AIAgentTarget[]; assistants?: string[] } = {}): Rule { const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[]; const agents = selected.includes("none" as any) ? [] : selected; - const selectedAssistants = options.assistant?.length ? options.assistant : ["vscode"]; + const selectedAssistants = options.assistants?.length ? options.assistants : []; const assistants = selectedAssistants .filter(a => a !== "none") .map(a => a === "claude-code" ? "general" : a) as AiCodingAssistant[]; From 934ba2b8d1e25040d548d7d4c4183d4e2babb901 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 15:23:38 +0300 Subject: [PATCH 12/20] fix: rename 'assistant' to 'assistants' in AI config schema and update related tests --- .../src/cli-config/ai-config-schema.json | 2 +- .../src/cli-config/index_spec.ts | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 8f176d9f3..3bfbd58b7 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -31,7 +31,7 @@ ] } }, - "assistant": { + "assistants": { "type": "array", "description": "Coding assistant(s) to configure MCP servers for.", "default": ["vscode"], diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index 7a7c93f5e..c69a2ccfe 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -436,19 +436,19 @@ export const appConfig: ApplicationConfig = { it("should default MCP config to .vscode/mcp.json with servers key", async () => { await runner.runSchematic("ai-config", {}, tree); - const mcpFilePath = "/.vscode/mcp.json"; - expect(tree.exists(mcpFilePath)).toBeTruthy(); - const content = JSON.parse(tree.readContent(mcpFilePath)); + const filePath = "/.vscode/mcp.json"; + expect(tree.exists(filePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(filePath)); expect(content.servers).toBeDefined(); expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); }); it("should write to .cursor/mcp.json with mcpServers key when assistant is cursor", async () => { - await runner.runSchematic("ai-config", { assistant: ["cursor"] }, tree); + await runner.runSchematic("ai-config", { assistants: ["cursor"] }, tree); - const mcpFilePath = "/.cursor/mcp.json"; - expect(tree.exists(mcpFilePath)).toBeTruthy(); - const content = JSON.parse(tree.readContent(mcpFilePath)); + const filePath = "/.cursor/mcp.json"; + expect(tree.exists(filePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(filePath)); expect(content.mcpServers).toBeDefined(); expect(content.mcpServers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); expect(content.mcpServers["angular-cli"]).toEqual({ command: "npx", args: ["-y", "@angular/cli", "mcp"] }); @@ -456,11 +456,11 @@ export const appConfig: ApplicationConfig = { }); it("should write to .mcp.json when assistant is claude-code", async () => { - await runner.runSchematic("ai-config", { assistant: ["claude-code"] }, tree); + await runner.runSchematic("ai-config", { assistants: ["claude-code"] }, tree); - const mcpFilePath = "/.mcp.json"; - expect(tree.exists(mcpFilePath)).toBeTruthy(); - const content = JSON.parse(tree.readContent(mcpFilePath)); + const filePath = "/.mcp.json"; + expect(tree.exists(filePath)).toBeTruthy(); + const content = JSON.parse(tree.readContent(filePath)); expect(content.mcpServers["igniteui-cli"]).toBeDefined(); expect(content.mcpServers["angular-cli"]).toBeDefined(); }); From 9af062fce4a31ce48c58437a2bcf511770ca5429 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 15:56:50 +0300 Subject: [PATCH 13/20] chore: agent skills descriptions --- packages/core/util/ai-skills.ts | 16 ++++++++-------- .../src/cli-config/ai-config-schema.json | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/core/util/ai-skills.ts b/packages/core/util/ai-skills.ts index 34cb5f7f7..76cfd4476 100644 --- a/packages/core/util/ai-skills.ts +++ b/packages/core/util/ai-skills.ts @@ -35,14 +35,14 @@ const AI_AGENT_INSTRUCTION_FILES: Record = { }; export const AI_AGENT_LABELS: Record = { - generic: "Generic (Adding .agents/skills and AGENTS.md general for most assistants)", - claude: "Claude (Adding .claude/skills and CLAUDE.md)", - copilot: "Copilot (Adding .github/skills and copilot-instructions.md)", - cursor: "Cursor (Adding .cursor/skills and .cursor/rules/cursor.mdc)", - codex: "Codex (Adding .codex/skills and .codex/instructions.md)", - windsurf: "Windsurf (Adding .windsurf/skills and .windsurf/rules/guidelines.md)", - gemini: "Gemini (Adding .gemini/skills and .gemini/GEMINI.md)", - junie: "Junie (Adding .junie/skills and .junie/guidelines.md)" + generic: "Generic (Add .agents/skills and AGENTS.md general for most assistants)", + claude: "Claude (Add .claude/skills and CLAUDE.md)", + copilot: "Copilot (Add .github/skills and copilot-instructions.md)", + cursor: "Cursor (Add .cursor/skills and .cursor/rules/cursor.mdc)", + codex: "Codex (Add .codex/skills and .codex/instructions.md)", + windsurf: "Windsurf (Add .windsurf/skills and .windsurf/rules/guidelines.md)", + gemini: "Gemini (Add .gemini/skills and .gemini/GEMINI.md)", + junie: "Junie (Add .junie/skills and .junie/guidelines.md)" }; /** diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 3bfbd58b7..49ef63a07 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -20,14 +20,14 @@ "multiselect": true, "items": [ { "value": "none", "label": "None (skip skills and instructions)" }, - { "value": "generic", "label": "Generic (Adding .agents/skills and AGENTS.md)" }, - { "value": "claude", "label": "Claude (Adding .claude/skills and CLAUDE.md)" }, - { "value": "copilot", "label": "Copilot (Adding .github/skills and copilot-instructions.md)" }, - { "value": "cursor", "label": "Cursor (Adding .cursor/skills and .cursor/rules/cursor.mdc)" }, - { "value": "codex", "label": "Codex (Adding .codex/skills and .codex/instructions.md)" }, - { "value": "windsurf", "label": "Windsurf (Adding .windsurf/skills and .windsurf/rules/guidelines.md)" }, - { "value": "gemini", "label": "Gemini (Adding .gemini/skills and .gemini/GEMINI.md)" }, - { "value": "junie", "label": "Junie (Adding .junie/skills and .junie/guidelines.md)" } + { "value": "generic", "label": "Generic (Add .agents/skills and AGENTS.md general for most assistants)" }, + { "value": "claude", "label": "Claude (Add .claude/skills and CLAUDE.md)" }, + { "value": "copilot", "label": "Copilot (Add .github/skills and copilot-instructions.md)" }, + { "value": "cursor", "label": "Cursor (Add .cursor/skills and .cursor/rules/cursor.mdc)" }, + { "value": "codex", "label": "Codex (Add .codex/skills and .codex/instructions.md)" }, + { "value": "windsurf", "label": "Windsurf (Add .windsurf/skills and .windsurf/rules/guidelines.md)" }, + { "value": "gemini", "label": "Gemini (Add .gemini/skills and .gemini/GEMINI.md)" }, + { "value": "junie", "label": "Junie (Add .junie/skills and .junie/guidelines.md)" } ] } }, From 13205445b462a4af5ef0f37e8482a22d14a27b38 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 16:08:34 +0300 Subject: [PATCH 14/20] fix: update configureMCP function to require assistants parameter and adjust default values in schema --- packages/cli/lib/commands/ai-config.ts | 2 +- .../src/cli-config/ai-config-schema.json | 15 +++++++-------- spec/unit/ai-config-spec.ts | 12 ++++++------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index e4b08427a..659613fad 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -1,7 +1,7 @@ import { addMcpServers, AI_AGENT_LABELS, AI_AGENT_CHOICES, AIAgentTarget, copyAgentInstructionFiles, copyAISkillsToProject, GoogleAnalytics, InquirerWrapper, Util, AiCodingAssistant, AI_ASSISTANT_MCP_CONFIGS, AI_ASSISTANT_CHOICES, AI_ASSISTANT_LABELS } from "@igniteui/cli-core"; import { ArgumentsCamelCase, CommandModule } from "yargs"; -export function configureMCP(assistants: AiCodingAssistant[] = ["vscode"]): void { +export function configureMCP(assistants: AiCodingAssistant[]): void { for (const assistant of assistants) { const { mcpFilePath } = AI_ASSISTANT_MCP_CONFIGS[assistant]; const modified = addMcpServers(assistant); diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 49ef63a07..10f479a89 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -34,22 +34,21 @@ "assistants": { "type": "array", "description": "Coding assistant(s) to configure MCP servers for.", - "default": ["vscode"], + "default": ["general"], "items": { "type": "string", - "enum": ["none", "vscode", "cursor", "claude-code", "gemini", "junie"] + "enum": ["none", "general", "vscode", "cursor", "gemini", "junie"] }, "x-prompt": { "message": "Which coding assistants should MCP servers be configured for?", "type": "list", "multiselect": true, "items": [ - { "value": "none", "label": "None (skip MCP configuration)" }, - { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, - { "value": "cursor", "label": "Cursor" }, - { "value": "claude-code", "label": "Claude Code" }, - { "value": "gemini", "label": "Gemini" }, - { "value": "junie", "label": "JetBrains Junie" } + { "value": "general", "label": ".mcp.json (general for Claude Code, VS Code, and other assistants)" }, + { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, + { "value": "cursor", "label": "Cursor" }, + { "value": "gemini", "label": "Gemini" }, + { "value": "junie", "label": "JetBrains Junie" } ] } } diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 1873ce9ef..38beba333 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -42,7 +42,7 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -67,7 +67,7 @@ describe("Unit - ai-config command", () => { const mockFs = createMockFs(JSON.stringify({ servers: {} })); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -81,7 +81,7 @@ describe("Unit - ai-config command", () => { })); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -95,7 +95,7 @@ describe("Unit - ai-config command", () => { })); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); @@ -112,7 +112,7 @@ describe("Unit - ai-config command", () => { })); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).not.toHaveBeenCalled(); expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("already configured")); @@ -125,7 +125,7 @@ describe("Unit - ai-config command", () => { })); App.container.set(FS_TOKEN, mockFs); - configureMCP(); + configureMCP(["vscode"]); expect(mockFs.writeFile).toHaveBeenCalled(); const config = writtenConfig(mockFs); From a8d5fd191e06b8738ac819a039941448e4cd7ee3 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 16:23:05 +0300 Subject: [PATCH 15/20] fix: update ai-config schematic tests to use correct assistants parameter and adjust mcp file paths --- .../ng-schematics/src/cli-config/index_spec.ts | 14 +++++++------- packages/ng-schematics/src/ng-new/index_spec.ts | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index c69a2ccfe..bbd98e77c 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -337,7 +337,7 @@ export const appConfig: ApplicationConfig = { }); it("should create .vscode/mcp.json with igniteui and angular-cli servers when file does not exist", async () => { - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); expect(tree.exists(mcpFilePath)).toBeTruthy(); const content = JSON.parse(tree.readContent(mcpFilePath)); @@ -349,7 +349,7 @@ export const appConfig: ApplicationConfig = { it("should add all three servers to existing .vscode/mcp.json that has no servers", async () => { tree.create(mcpFilePath, JSON.stringify({ servers: {} })); - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); const content = JSON.parse(tree.readContent(mcpFilePath)); expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); @@ -364,7 +364,7 @@ export const appConfig: ApplicationConfig = { } })); - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); const content = JSON.parse(tree.readContent(mcpFilePath)); expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); @@ -382,7 +382,7 @@ export const appConfig: ApplicationConfig = { }; tree.create(mcpFilePath, JSON.stringify(existing)); - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); const content = JSON.parse(tree.readContent(mcpFilePath)); expect(content).toEqual(existing); @@ -395,7 +395,7 @@ export const appConfig: ApplicationConfig = { } })); - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); const content = JSON.parse(tree.readContent(mcpFilePath)); expect(content.servers["other-server"]).toEqual({ command: "node", args: ["server.js"] }); @@ -434,7 +434,7 @@ export const appConfig: ApplicationConfig = { }); it("should default MCP config to .vscode/mcp.json with servers key", async () => { - await runner.runSchematic("ai-config", {}, tree); + await runner.runSchematic("ai-config", { assistants: ["vscode"] }, tree); const filePath = "/.vscode/mcp.json"; expect(tree.exists(filePath)).toBeTruthy(); @@ -456,7 +456,7 @@ export const appConfig: ApplicationConfig = { }); it("should write to .mcp.json when assistant is claude-code", async () => { - await runner.runSchematic("ai-config", { assistants: ["claude-code"] }, tree); + await runner.runSchematic("ai-config", { assistants: ["general"] }, tree); const filePath = "/.mcp.json"; expect(tree.exists(filePath)).toBeTruthy(); diff --git a/packages/ng-schematics/src/ng-new/index_spec.ts b/packages/ng-schematics/src/ng-new/index_spec.ts index 62d9f0225..b0f2bcc90 100644 --- a/packages/ng-schematics/src/ng-new/index_spec.ts +++ b/packages/ng-schematics/src/ng-new/index_spec.ts @@ -224,7 +224,7 @@ describe("Schematics ng-new", () => { describe("addAIConfig via ng-new", () => { const workingDirectory = "my-test-project"; - const mcpFilePath = `${workingDirectory}/.vscode/mcp.json`; + const mcpFilePath = `${workingDirectory}/.mcp.json`; function setupAndRun(runner: SchematicTestRunner, myTree: Tree): Promise { spyOn(AppProjectSchematic, "default").and.returnValue((currentTree: Tree, _context: SchematicContext) => { @@ -239,7 +239,7 @@ describe("Schematics ng-new", () => { return runner.runSchematic("ng-new", { version: "8.0.3", name: workingDirectory, skipInstall: true, skipGit: true }, myTree); } - it("should create .vscode/mcp.json with both servers during ng-new", async () => { + it("should create .mcp.json with both servers during ng-new", async () => { const runner = new SchematicTestRunner("schematics", collectionPath); const myTree = Tree.empty(); @@ -247,8 +247,8 @@ describe("Schematics ng-new", () => { expect(e.exists(mcpFilePath)).toBeTruthy(); const content = JSON.parse(e.readContent(mcpFilePath)); - expect(content.servers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); - expect(content.servers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); + expect(content.mcpServers["igniteui-cli"]).toEqual({ command: "npx", args: ["-y", "igniteui-cli", "mcp"] }); + expect(content.mcpServers["igniteui-theming"]).toEqual({ command: "npx", args: ["-y", "igniteui-theming", "igniteui-theming-mcp"] }); }); }); }); From 67a679de1dae4f297e667914518447997d82132e Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 16:49:02 +0300 Subject: [PATCH 16/20] chore: formatting --- packages/cli/lib/commands/new.ts | 2 +- .../ng-schematics/src/cli-config/ai-config-schema.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 8ba68c2ea..9cb0e8b9e 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -178,7 +178,7 @@ const command: NewCommandType = { await PackageManager.installPackages(); process.chdir(".."); } - + if (!argv["skip-git"] && !ProjectConfig.getConfig().skipGit) { Util.gitInit(process.cwd(), argv.name); } diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 10f479a89..70254f07a 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -44,11 +44,11 @@ "type": "list", "multiselect": true, "items": [ - { "value": "general", "label": ".mcp.json (general for Claude Code, VS Code, and other assistants)" }, - { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, - { "value": "cursor", "label": "Cursor" }, - { "value": "gemini", "label": "Gemini" }, - { "value": "junie", "label": "JetBrains Junie" } + { "value": "general", "label": ".mcp.json (general for Claude Code, VS Code, and other assistants)" }, + { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, + { "value": "cursor", "label": "Cursor" }, + { "value": "gemini", "label": "Gemini" }, + { "value": "junie", "label": "JetBrains Junie" } ] } } From 80ba2620a5b16f5871e90b2afd1f8c991573ca0c Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 17:10:02 +0300 Subject: [PATCH 17/20] refactor(schematics): leftover default param --- packages/ng-schematics/src/cli-config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index e28ac0814..e96fd1604 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -127,7 +127,7 @@ function appInit(tree: Tree) { setVirtual(tree); } -function aiConfig({ init, agents, assistants = ["vscode"] }: { init: boolean; agents: AIAgentTarget[]; assistants?: AiCodingAssistant[] }): Rule { +function aiConfig({ init, agents, assistants }: { init: boolean; agents: AIAgentTarget[]; assistants: AiCodingAssistant[] }): Rule { return (tree: Tree) => { if (init) { appInit(tree); From 396880653ffc3c75159a30e32be457215ce70323 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 17:11:17 +0300 Subject: [PATCH 18/20] fix(schematics): mcp config none handling --- packages/ng-schematics/src/cli-config/ai-config-schema.json | 1 + packages/ng-schematics/src/cli-config/index.ts | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ng-schematics/src/cli-config/ai-config-schema.json b/packages/ng-schematics/src/cli-config/ai-config-schema.json index 70254f07a..107b86926 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -44,6 +44,7 @@ "type": "list", "multiselect": true, "items": [ + { "value": "none", "label": "None (skip MCP configuration)" }, { "value": "general", "label": ".mcp.json (general for Claude Code, VS Code, and other assistants)" }, { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, { "value": "cursor", "label": "Cursor" }, diff --git a/packages/ng-schematics/src/cli-config/index.ts b/packages/ng-schematics/src/cli-config/index.ts index e96fd1604..803d8db47 100644 --- a/packages/ng-schematics/src/cli-config/index.ts +++ b/packages/ng-schematics/src/cli-config/index.ts @@ -153,9 +153,7 @@ export function addAIConfig(options: { agents?: AIAgentTarget[]; assistants?: st const selected = options.agents?.length ? options.agents : [] as AIAgentTarget[]; const agents = selected.includes("none" as any) ? [] : selected; const selectedAssistants = options.assistants?.length ? options.assistants : []; - const assistants = selectedAssistants - .filter(a => a !== "none") - .map(a => a === "claude-code" ? "general" : a) as AiCodingAssistant[]; + const assistants = (selectedAssistants.includes("none")? [] : selectedAssistants) as AiCodingAssistant[]; return aiConfig({ init: true, agents, assistants }); } From 487bb0a7afe5f0312188c3fbab36308a703bec02 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 17:20:04 +0300 Subject: [PATCH 19/20] refactor: move skill flag param at end --- packages/cli/lib/commands/ai-config.ts | 4 ++-- packages/cli/lib/commands/new.ts | 2 +- spec/unit/new-spec.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 659613fad..0633cfe15 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -29,7 +29,7 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents?: AIAgentTarget[], skills = true, assistants?: AiCodingAssistant[]): Promise { +export async function configure(agents?: AIAgentTarget[], assistants?: AiCodingAssistant[], skills = true): Promise { if (!agents?.length) { agents = await promptForAgents(); } @@ -143,7 +143,7 @@ const command: CommandModule = { Util.log("No AI configuration selected. Skipping."); return; } - await configure(agents, true, assistants); + await configure(agents, assistants); } }; diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 9cb0e8b9e..0f46f182b 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -169,7 +169,7 @@ const command: NewCommandType = { const rawAgents = argv.agents as string[] | undefined; const filteredAgents = rawAgents?.filter(a => a !== "none") as AIAgentTarget[] | undefined; if (rawAgents == null || rawAgents.indexOf("none") === -1 || filteredAgents?.length) { - await configure(filteredAgents, true, argv.assistants as AiCodingAssistant[] | undefined); + await configure(filteredAgents, argv.assistants as AiCodingAssistant[] | undefined); } process.chdir(".."); diff --git a/spec/unit/new-spec.ts b/spec/unit/new-spec.ts index 27ec60d44..a14001bcb 100644 --- a/spec/unit/new-spec.ts +++ b/spec/unit/new-spec.ts @@ -408,7 +408,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", agents: ["claude", "cursor"], _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(["claude", "cursor"], true, undefined); + expect(configureSpy).toHaveBeenCalledWith(["claude", "cursor"], undefined); }); it("calls configure with undefined when --agents is not provided", async () => { @@ -416,7 +416,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(undefined, true, undefined); + expect(configureSpy).toHaveBeenCalledWith(undefined, undefined); }); it("calls configure with single agent", async () => { @@ -424,7 +424,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", agents: ["generic"], _: ["new"], $0: "new" }); - expect(configureSpy).toHaveBeenCalledWith(["generic"], true, undefined); + expect(configureSpy).toHaveBeenCalledWith(["generic"], undefined); }); it("calls configure before package install", async () => { @@ -469,7 +469,7 @@ describe("Unit - New command", () => { await newCmd.handler({ name: "Test", framework: "jq", skipInstall: true, agents: ["claude"], _: ["new"], $0: "new" }); expect(PackageManager.installPackages).not.toHaveBeenCalled(); - expect(configureSpy).toHaveBeenCalledWith(["claude"], true, undefined); + expect(configureSpy).toHaveBeenCalledWith(["claude"], undefined); }); it("does not call configure when project creation fails (bad name)", async () => { From 3a0962d4fae9eb08f4311cc7ac187c4bfb4c9831 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 17:56:34 +0300 Subject: [PATCH 20/20] fix(ai-config): still config mcp w/ none agents selected --- packages/cli/lib/commands/ai-config.ts | 8 +++----- spec/unit/ai-config-spec.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 0633cfe15..7b7ccd230 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -29,12 +29,12 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents?: AIAgentTarget[], assistants?: AiCodingAssistant[], skills = true): Promise { - if (!agents?.length) { +export async function configure(agents: AIAgentTarget[] = [], assistants: AiCodingAssistant[] = [], prompt = true, skills = true): Promise { + if (!agents.length && prompt) { agents = await promptForAgents(); } - if (!assistants?.length) { + if (!assistants.length && prompt) { assistants = await promptForAssistant(); } configureMCP(assistants); @@ -136,12 +136,10 @@ const command: CommandModule = { if (!assistants.length) { Util.log("No MCP configuration selected. Skipping."); - return; } if (!agents.length) { Util.log("No AI configuration selected. Skipping."); - return; } await configure(agents, assistants); } diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 38beba333..f5f36e36c 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -326,9 +326,16 @@ describe("Unit - ai-config command", () => { await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); + expect(mockFs.writeFile).toHaveBeenCalled(); + const config = writtenConfig(mockFs); + expect(config.servers).toBeDefined(); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "Ai Config" })); expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ ea: "agent: none; assistant: vscode" })); - expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("Skipping")); + expect( + (Util.log as jasmine.Spy).calls.allArgs() + .filter(([msg]) => String(msg).includes("Skipping")) + ).toHaveSize(1); + expect(Util.log).toHaveBeenCalledWith("No AI configuration selected. Skipping."); }); it("configures multiple agents when selected interactively", async () => {