From 25318e05999b1a8e29c1c1aa3fa4f126587d0f35 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Fri, 8 May 2026 11:49:52 +0300 Subject: [PATCH 01/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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/29] 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 () => { From ca9899d1de9093040d507e13520aab704c5578a9 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Tue, 12 May 2026 18:59:41 +0300 Subject: [PATCH 21/29] fix(ai-config): leftover change for config re-prompt call --- packages/cli/lib/commands/ai-config.ts | 2 +- spec/unit/ai-config-spec.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 7b7ccd230..71d040008 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -141,7 +141,7 @@ const command: CommandModule = { if (!agents.length) { Util.log("No AI configuration selected. Skipping."); } - await configure(agents, assistants); + await configure(agents, assistants, false); } }; diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index f5f36e36c..0b15c8a2e 100644 --- a/spec/unit/ai-config-spec.ts +++ b/spec/unit/ai-config-spec.ts @@ -331,6 +331,7 @@ describe("Unit - ai-config command", () => { 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(InquirerWrapper.checkbox).toHaveBeenCalledTimes(2); expect( (Util.log as jasmine.Spy).calls.allArgs() .filter(([msg]) => String(msg).includes("Skipping")) From d0fdb37c726d551f728e6206f1e5dedd86d8b3bf Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 19:54:11 +0300 Subject: [PATCH 22/29] update: Update README.md --- CHANGELOG.md | 1 + README.md | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 149d76ccd..4480ada13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * ci(npm): OIDC/Trusted publishing workflow & node update by @turbobobbytraykov in https://github.com/IgniteUI/igniteui-cli/pull/1654 * build(deps): bump postcss from 8.5.8 to 8.5.12 in the npm_and_yarn group across 1 directory by @dependabot[bot] in https://github.com/IgniteUI/igniteui-cli/pull/1670 * chore(igx-ts,igr-ts,igc-ts): bump igniteui package deps by @damyanpetev in https://github.com/IgniteUI/igniteui-cli/pull/1673 +* feat(ai-config): The ai-config command now enables users to select and configure an AI coding assistant integration for their Ignite UI project. Based on the selected provider, the command adds the required skills, instruction files, and MCP configuration to streamline AI-assisted development workflows. by @Marina-L-Stoyanova https://github.com/IgniteUI/igniteui-cli/pull/1684 # 15.0.0 (2026-04-22) diff --git a/README.md b/README.md index eb747c84f..689cc9e82 100644 --- a/README.md +++ b/README.md @@ -151,13 +151,17 @@ To configure Ignite UI AI tooling — MCP servers and AI coding skills — run: ig ai-config ``` -You will be prompted to select which AI tools to configure (Claude and Generic are selected by default). You can also pass agents directly: +You will be prompted with two selections: +- **AI agents** — which tools to generate skill and instruction files for (Generic and Claude are selected by default) +- **Coding assistants** — which assistants to configure MCP servers for (general `.mcp.json` is selected by default, compatible with Claude Code, VS Code, and others) + +You can also pass options directly: ```bash -ig ai-config --agents claude copilot generic +ig ai-config --agents claude copilot generic --assistants vscode cursor ``` -This creates or updates `.vscode/mcp.json` with entries for the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers (existing servers are preserved), copies AI coding skill files from installed Ignite UI packages, and generates agent-specific instruction files (e.g. `CLAUDE.md`, `AGENTS.md`). +This creates or updates the assistant-specific MCP config file (e.g. `.mcp.json`, `.vscode/mcp.json`, `.cursor/mcp.json`) with entries for the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers (existing servers are preserved), copies AI coding skill files from installed Ignite UI packages, and generates agent-specific instruction files (e.g. `CLAUDE.md`, `AGENTS.md`). The `ig new` command also prompts for AI tool configuration as part of project creation. From ee7bc1ea148e2c0f9b0d7c1ab41759b8d4f54146 Mon Sep 17 00:00:00 2001 From: Marina Stoyanova Date: Tue, 12 May 2026 19:57:22 +0300 Subject: [PATCH 23/29] Update packages/core/util/mcp-config.ts Co-authored-by: Damyan Petev --- packages/core/util/mcp-config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index dc22b954e..6d2971a61 100644 --- a/packages/core/util/mcp-config.ts +++ b/packages/core/util/mcp-config.ts @@ -93,4 +93,3 @@ export function addMcpServers( return modified; } - From b15c512fe1cf48b5f1f1766b45e6fe288fad51b0 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 22:04:55 +0300 Subject: [PATCH 24/29] fix(ai-config): update assistant terminology from "general" to "generic" --- packages/cli/lib/commands/ai-config.ts | 2 +- packages/cli/lib/commands/new.ts | 18 ++++++++---------- packages/core/util/mcp-config.ts | 8 ++++---- .../src/cli-config/ai-config-schema.json | 6 +++--- .../ng-schematics/src/cli-config/index_spec.ts | 2 +- spec/acceptance/help-spec.ts | 4 ++-- spec/unit/ai-config-spec.ts | 4 ++-- 7 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 71d040008..66e28a16d 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[] = [], assistants: AiCodi const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; -const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["general"]; +const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["generic"]; const AI_AGENT_CHECKBOX_CHOICES = [ { value: "none", name: "None (skip skills and instructions)" }, diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index eb2e491e3..bf0eff7f7 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -60,7 +60,6 @@ const command: NewCommandType = { type: "string" }) .option("agents", { - alias: "a", describe: "AI agents/tools to generate configuration files for", choices: [...AI_AGENT_CHOICES, "none"] as string[], type: "array" @@ -156,15 +155,6 @@ const command: NewCommandType = { cd14: theme }); - const config = projTemplate.generateConfig(argv.name, theme); - for (const templatePath of projTemplate.templatePaths) { - await Util.processTemplates(templatePath, path.join(process.cwd(), argv.name), - config, projTemplate.delimiters, false); - } - - Util.log(Util.greenCheck() + " Project Created"); - - process.chdir(argv.name); const rawAgents = argv.agents as string[] | undefined; const filteredAgents = rawAgents?.filter(a => a !== "none") as AIAgentTarget[] | undefined; @@ -173,6 +163,14 @@ const command: NewCommandType = { } process.chdir(".."); + const config = projTemplate.generateConfig(argv.name, theme); + for (const templatePath of projTemplate.templatePaths) { + await Util.processTemplates(templatePath, path.join(process.cwd(), argv.name), + config, projTemplate.delimiters, false); + } + + Util.log(Util.greenCheck() + " Project Created"); + if (!argv.skipInstall) { process.chdir(argv.name); await PackageManager.installPackages(); diff --git a/packages/core/util/mcp-config.ts b/packages/core/util/mcp-config.ts index 6d2971a61..081b27328 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 = ["general", "vscode", "cursor", "gemini", "junie"] as const; +export const AI_ASSISTANT_CHOICES = ["generic", "vscode", "cursor", "gemini", "junie"] as const; export type AiCodingAssistant = typeof AI_ASSISTANT_CHOICES[number]; interface AssistantMcpConfig { @@ -16,7 +16,7 @@ interface AssistantMcpConfig { } export const AI_ASSISTANT_LABELS: Record = { - "general": ".mcp.json (general for Claude Code, VS Code, and other assistants)", + "generic": ".mcp.json (generic for Claude Code, VS Code, and other assistants)", "vscode": "VS Code (GitHub Copilot)", "cursor": "Cursor", "gemini": "Gemini", @@ -24,7 +24,7 @@ export const AI_ASSISTANT_LABELS: Record = { }; export const AI_ASSISTANT_MCP_CONFIGS: Record = { - "general": { mcpFilePath: ".mcp.json", rootKey: "mcpServers" }, + "generic": { 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" }, @@ -50,7 +50,7 @@ const IGNITEUI_MCP_SERVERS: Record = { * @returns whether the file was modified */ export function addMcpServers( - assistant: AiCodingAssistant = "vscode", + assistant: AiCodingAssistant, additionalServers?: Record ): boolean { const { mcpFilePath, rootKey } = AI_ASSISTANT_MCP_CONFIGS[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 107b86926..4cdd16ce9 100644 --- a/packages/ng-schematics/src/cli-config/ai-config-schema.json +++ b/packages/ng-schematics/src/cli-config/ai-config-schema.json @@ -34,10 +34,10 @@ "assistants": { "type": "array", "description": "Coding assistant(s) to configure MCP servers for.", - "default": ["general"], + "default": ["generic"], "items": { "type": "string", - "enum": ["none", "general", "vscode", "cursor", "gemini", "junie"] + "enum": ["none", "generic", "vscode", "cursor", "gemini", "junie"] }, "x-prompt": { "message": "Which coding assistants should MCP servers be configured for?", @@ -45,7 +45,7 @@ "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": "generic", "label": ".mcp.json (generic for Claude Code, VS Code, and other assistants)" }, { "value": "vscode", "label": "VS Code (GitHub Copilot)" }, { "value": "cursor", "label": "Cursor" }, { "value": "gemini", "label": "Gemini" }, diff --git a/packages/ng-schematics/src/cli-config/index_spec.ts b/packages/ng-schematics/src/cli-config/index_spec.ts index bbd98e77c..0f9d2a202 100644 --- a/packages/ng-schematics/src/cli-config/index_spec.ts +++ b/packages/ng-schematics/src/cli-config/index_spec.ts @@ -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: ["general"] }, tree); + await runner.runSchematic("ai-config", { assistants: ["generic"] }, tree); const filePath = "/.mcp.json"; expect(tree.exists(filePath)).toBeTruthy(); diff --git a/spec/acceptance/help-spec.ts b/spec/acceptance/help-spec.ts index f2bb1f91c..b0551b64d 100644 --- a/spec/acceptance/help-spec.ts +++ b/spec/acceptance/help-spec.ts @@ -70,11 +70,11 @@ describe("Help command", () => { [boolean] --skip-install, --si Do not install packages after scaffolding [boolean] --template Project template [string] - -a, --agents AI agents/tools to generate configuration files for + --agents AI agents/tools to generate configuration files for [array] [choices: "generic", "claude", "copilot", "cursor", "codex", "windsurf", "gemini", "junie", "none"] --assistants Coding assistant(s) to configure MCP servers for - [array] [choices: "general", "vscode", "cursor", "gemini", "junie", + [array] [choices: "generic", "vscode", "cursor", "gemini", "junie", "none"] Examples: diff --git a/spec/unit/ai-config-spec.ts b/spec/unit/ai-config-spec.ts index 0b15c8a2e..915bd481c 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: general" })); + expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: generic, claude; assistant: generic" })); }); it("logs skipping and does not post analytics when none is selected", async () => { @@ -399,7 +399,7 @@ describe("Unit - ai-config command", () => { spyOn(Util, "canPrompt").and.returnValue(true); spyOn(InquirerWrapper, "checkbox").and.returnValues( Promise.resolve(["claude"]), - Promise.resolve(["general"]) + Promise.resolve(["generic"]) ); await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); From 559e74b1062a3018f24f0f439d83b65db2b4625a Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Tue, 12 May 2026 23:05:44 +0300 Subject: [PATCH 25/29] fix(ai-config): update configure function to handle "none" option and improve logging --- packages/cli/lib/commands/ai-config.ts | 66 ++++++++++++-------------- packages/cli/lib/commands/new.ts | 6 +-- spec/unit/new-spec.ts | 2 +- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 66e28a16d..7fe524452 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -29,20 +29,33 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents: AIAgentTarget[] = [], assistants: AiCodingAssistant[] = [], prompt = true, skills = true): Promise { - if (!agents.length && prompt) { +export async function configure(agents: (AIAgentTarget | "none")[] = [], assistants: (AiCodingAssistant | "none")[] = [], prompt = true, skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { + if (!agents.includes("none") && !agents.length && prompt) { agents = await promptForAgents(); } - if (!assistants.length && prompt) { + if (!assistants.includes("none") && !assistants.length && prompt) { assistants = await promptForAssistant(); } - configureMCP(assistants); - if (skills) { - configureSkills(agents); + const resolvedAgents: AIAgentTarget[] = agents.includes("none") ? [] : agents as AIAgentTarget[]; + const resolvedAssistants: AiCodingAssistant[] = assistants.includes("none") ? [] : assistants as AiCodingAssistant[]; + + if (!resolvedAssistants.length) { + Util.log("No MCP configuration selected. Skipping."); + } + configureMCP(resolvedAssistants); + + if (!resolvedAgents.length) { + Util.log("No AI configuration selected. Skipping."); + } else { + if (skills) { + configureSkills(resolvedAgents); + } + copyAgentInstructionFiles(resolvedAgents); } - copyAgentInstructionFiles(agents); + + return { agents: resolvedAgents, assistants: resolvedAssistants }; } const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; @@ -67,28 +80,28 @@ const AI_ASSISTANT_CHECKBOX_CHOICES = [ })) ]; -export async function promptForAgents(): Promise { - let selected: AIAgentTarget[] = AI_AGENT_CHECKBOX_DEFAULTS; +export async function promptForAgents(): Promise<(AIAgentTarget | "none")[]> { + let selected: (AIAgentTarget | "none")[] = AI_AGENT_CHECKBOX_DEFAULTS; if (Util.canPrompt()) { const result = await InquirerWrapper.checkbox({ message: "Which AI agents do you want to generate skills and instructions for?", required: true, choices: AI_AGENT_CHECKBOX_CHOICES }); - selected = result.includes("none") ? [] : result as AIAgentTarget[]; + selected = result as (AIAgentTarget | "none")[]; } return selected; } -export async function promptForAssistant(): Promise { - let selected: AiCodingAssistant[] = AI_ASSISTANT_CHECKBOX_DEFAULTS; +export async function promptForAssistant(): Promise<(AiCodingAssistant | "none")[]> { + let selected: (AiCodingAssistant | "none")[] = 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[]; + selected = result as (AiCodingAssistant | "none")[]; } return selected; } @@ -99,7 +112,6 @@ const command: CommandModule = { builder: (yargs) => yargs .usage("") .option("agents", { - alias: "a", describe: "AI agents/tools to generate configuration files for", choices: [...AI_AGENT_CHOICES, "none"] as string[], type: "array" @@ -110,38 +122,20 @@ const command: CommandModule = { type: "array" }), async handler(argv: ArgumentsCamelCase) { - 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[]; + const agents = (argv.agents ?? []) as (AIAgentTarget | "none")[]; + const assistants = (argv.assistants ?? []) as (AiCodingAssistant | "none")[]; GoogleAnalytics.post({ t: "screenview", cd: "Ai Config" }); - if (!agentNoneSelected && !agents.length) { - agents = await promptForAgents(); - } - if (!assistantNoneSelected && !assistants.length) { - assistants = await promptForAssistant(); - } + const result = await configure(agents, assistants); GoogleAnalytics.post({ t: "event", ec: "$ig ai-config", - ea: `agent: ${agents?.join(", ") || "none"}; assistant: ${assistants?.join(", ") || "none"}` + ea: `agent: ${result.agents.join(", ") || "none"}; assistant: ${result.assistants.join(", ") || "none"}` }); - - if (!assistants.length) { - Util.log("No MCP configuration selected. Skipping."); - } - - if (!agents.length) { - Util.log("No AI configuration selected. Skipping."); - } - await configure(agents, assistants, false); } }; diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index bf0eff7f7..473c382c0 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -156,11 +156,7 @@ const command: NewCommandType = { }); process.chdir(argv.name); - 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, argv.assistants as AiCodingAssistant[] | undefined); - } + await configure(argv.agents as (AIAgentTarget | "none")[] ?? [], argv.assistants as (AiCodingAssistant | "none")[] ?? []); process.chdir(".."); const config = projTemplate.generateConfig(argv.name, theme); diff --git a/spec/unit/new-spec.ts b/spec/unit/new-spec.ts index 28295577e..5c7e62c33 100644 --- a/spec/unit/new-spec.ts +++ b/spec/unit/new-spec.ts @@ -36,7 +36,7 @@ describe("Unit - New command", () => { spyOn(Util, "execSync"); spyOn(process, "chdir"); spyOn(PackageManager, "installPackages"); - spyOn(aiConfig, "configure").and.returnValue(Promise.resolve()); + spyOn(aiConfig, "configure").and.returnValue(Promise.resolve({ agents: [], assistants: [] })); spyOn(Util, "directoryExists").and.returnValue(false); }); From f68207cb04137ccf70fadfd23453716e26de8d24 Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 13 May 2026 09:24:30 +0300 Subject: [PATCH 26/29] fix(new-command): reorder project directory change and configuration setup for clarity --- packages/cli/lib/commands/new.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/lib/commands/new.ts b/packages/cli/lib/commands/new.ts index 473c382c0..ff0f467e5 100644 --- a/packages/cli/lib/commands/new.ts +++ b/packages/cli/lib/commands/new.ts @@ -155,16 +155,16 @@ const command: NewCommandType = { cd14: theme }); - process.chdir(argv.name); - await configure(argv.agents as (AIAgentTarget | "none")[] ?? [], argv.assistants as (AiCodingAssistant | "none")[] ?? []); - process.chdir(".."); - const config = projTemplate.generateConfig(argv.name, theme); for (const templatePath of projTemplate.templatePaths) { await Util.processTemplates(templatePath, path.join(process.cwd(), argv.name), config, projTemplate.delimiters, false); } + process.chdir(argv.name); + await configure(argv.agents as (AIAgentTarget | "none")[], argv.assistants as (AiCodingAssistant | "none")[]); + process.chdir(".."); + Util.log(Util.greenCheck() + " Project Created"); if (!argv.skipInstall) { From 8d849066081767a2e07bef5abbb382a54df20a4e Mon Sep 17 00:00:00 2001 From: mstoyanova Date: Wed, 13 May 2026 10:36:32 +0300 Subject: [PATCH 27/29] fix(ai-config): simplify agent and assistant configuration prompts --- CHANGELOG.md | 6 +++++- packages/cli/lib/commands/ai-config.ts | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4480ada13..b2ae7c551 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 15.1.0 + +## What's Changed +* feat(ai-config): The ai-config command now enables users to select and configure an AI coding assistant integration for their Ignite UI project. Based on the selected provider, the command adds the required skills, instruction files, and MCP configuration to streamline AI-assisted development workflows. The `new` command now accepts `--agents` (for adding skills and instruction files) and `--assistants`(for adding mcp configuration) options, allowing users to configure AI integration directly during project creation. When using the step-by-step wizard, users are automatically prompted to select their preferred AI agents and coding assistants after the project structure is generated. The Angular schematics package includes a dedicated `ai-config` schematic that runs automatically during `ng new` with Ignite UI and can also be invoked standalone via `ng generate @igniteui/angular-schematics:ai-config` to add AI configuration to existing Angular projects. by @Marina-L-Stoyanova https://github.com/IgniteUI/igniteui-cli/pull/1684 + # 15.0.1 (2026-04-28) ## What's Changed @@ -8,7 +13,6 @@ * ci(npm): OIDC/Trusted publishing workflow & node update by @turbobobbytraykov in https://github.com/IgniteUI/igniteui-cli/pull/1654 * build(deps): bump postcss from 8.5.8 to 8.5.12 in the npm_and_yarn group across 1 directory by @dependabot[bot] in https://github.com/IgniteUI/igniteui-cli/pull/1670 * chore(igx-ts,igr-ts,igc-ts): bump igniteui package deps by @damyanpetev in https://github.com/IgniteUI/igniteui-cli/pull/1673 -* feat(ai-config): The ai-config command now enables users to select and configure an AI coding assistant integration for their Ignite UI project. Based on the selected provider, the command adds the required skills, instruction files, and MCP configuration to streamline AI-assisted development workflows. by @Marina-L-Stoyanova https://github.com/IgniteUI/igniteui-cli/pull/1684 # 15.0.0 (2026-04-22) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 7fe524452..83367aa45 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -30,11 +30,11 @@ export function configureSkills(agents: AIAgentTarget[]): void { } export async function configure(agents: (AIAgentTarget | "none")[] = [], assistants: (AiCodingAssistant | "none")[] = [], prompt = true, skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { - if (!agents.includes("none") && !agents.length && prompt) { + if (!agents.length && prompt) { agents = await promptForAgents(); } - if (!assistants.includes("none") && !assistants.length && prompt) { + if (!assistants.length && prompt) { assistants = await promptForAssistant(); } From df06219ae3bdd127251a03956bf596001f1bd2a8 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Wed, 13 May 2026 15:58:54 +0300 Subject: [PATCH 28/29] chore(ai-configure: remove redundant prompt param --- packages/cli/lib/commands/ai-config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 83367aa45..0b22e6a24 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 | "none")[] = [], assistants: (AiCodingAssistant | "none")[] = [], prompt = true, skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { - if (!agents.length && prompt) { +export async function configure(agents: (AIAgentTarget | "none")[] = [], assistants: (AiCodingAssistant | "none")[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { + if (!agents.length) { agents = await promptForAgents(); } - if (!assistants.length && prompt) { + if (!assistants.length) { assistants = await promptForAssistant(); } From 88ce66fd9d8b63397e081e8be60e79061f9264a4 Mon Sep 17 00:00:00 2001 From: damyanpetev Date: Wed, 13 May 2026 16:10:50 +0300 Subject: [PATCH 29/29] chore: alias repeated types --- packages/cli/lib/commands/ai-config.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/cli/lib/commands/ai-config.ts b/packages/cli/lib/commands/ai-config.ts index 0b22e6a24..496dd825a 100644 --- a/packages/cli/lib/commands/ai-config.ts +++ b/packages/cli/lib/commands/ai-config.ts @@ -29,7 +29,10 @@ export function configureSkills(agents: AIAgentTarget[]): void { } } -export async function configure(agents: (AIAgentTarget | "none")[] = [], assistants: (AiCodingAssistant | "none")[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { +type AIAgentOption = AIAgentTarget | "none"; +type AIAssistantOption = AiCodingAssistant | "none"; + +export async function configure(agents: AIAgentOption[] = [], assistants: AIAssistantOption[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> { if (!agents.length) { agents = await promptForAgents(); } @@ -59,7 +62,6 @@ export async function configure(agents: (AIAgentTarget | "none")[] = [], assista } const AI_AGENT_CHECKBOX_DEFAULTS: AIAgentTarget[] = ["generic", "claude"]; - const AI_ASSISTANT_CHECKBOX_DEFAULTS: AiCodingAssistant[] = ["generic"]; const AI_AGENT_CHECKBOX_CHOICES = [ @@ -80,28 +82,28 @@ const AI_ASSISTANT_CHECKBOX_CHOICES = [ })) ]; -export async function promptForAgents(): Promise<(AIAgentTarget | "none")[]> { - let selected: (AIAgentTarget | "none")[] = AI_AGENT_CHECKBOX_DEFAULTS; +export async function promptForAgents(): Promise { + let selected: AIAgentOption[] = AI_AGENT_CHECKBOX_DEFAULTS; if (Util.canPrompt()) { const result = await InquirerWrapper.checkbox({ message: "Which AI agents do you want to generate skills and instructions for?", required: true, choices: AI_AGENT_CHECKBOX_CHOICES }); - selected = result as (AIAgentTarget | "none")[]; + selected = result as AIAgentOption[]; } return selected; } -export async function promptForAssistant(): Promise<(AiCodingAssistant | "none")[]> { - let selected: (AiCodingAssistant | "none")[] = AI_ASSISTANT_CHECKBOX_DEFAULTS; +export async function promptForAssistant(): Promise { + let selected: AIAssistantOption[] = 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 as (AiCodingAssistant | "none")[]; + selected = result as AIAssistantOption[]; } return selected; } @@ -122,8 +124,8 @@ const command: CommandModule = { type: "array" }), async handler(argv: ArgumentsCamelCase) { - const agents = (argv.agents ?? []) as (AIAgentTarget | "none")[]; - const assistants = (argv.assistants ?? []) as (AiCodingAssistant | "none")[]; + const agents = (argv.agents ?? []) as AIAgentOption[]; + const assistants = (argv.assistants ?? []) as AIAssistantOption[]; GoogleAnalytics.post({ t: "screenview", cd: "Ai Config"