Skip to content

Commit e99b5a8

Browse files
authored
Merge branch 'master' into dkalinov/mcp-db-update
2 parents b6e28ba + f16bc87 commit e99b5a8

17 files changed

Lines changed: 574 additions & 411 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
# 15.1.1
2-
* Update(ai-config): Adding AI coding assistance integration for Blazor projects.
1+
# 15.1.1 (2026-05-18)
2+
3+
## What's Changed
4+
* Updated `ig ai-config` command:
5+
- Added AI coding assistance integration for Blazor projects.
6+
- Now accepts a `--framework` / `-f` option for explicit framework specification. When omitted, the command still attempts to auto-detect the framework, but if detection fails it now also prompts the user for selection (in TTY).
37

48
# 15.1.0 (2026-05-13)
59

packages/cli/lib/PromptSession.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ export class PromptSession extends BasePromptSession {
3838
await upgrade.upgrade({ skipInstall: true, _: ["upgrade"], $0: "upgrade" });
3939
}
4040

41-
protected override async configureAI(): Promise<void> {
42-
await aiConfigure();
41+
protected override async configureAI(frameworkId: string): Promise<void> {
42+
await aiConfigure(frameworkId);
4343
}
4444

4545
protected override templateSelectedTask(type: "component" | "view" = "component"): Task<PromptTaskContext> {

packages/cli/lib/commands/ai-config.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
1-
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";
1+
import {
2+
addMcpServers,
3+
AI_AGENT_LABELS,
4+
AI_AGENT_CHOICES,
5+
type AIAgentTarget,
6+
copyAgentInstructionFiles,
7+
copyAISkillsToProject,
8+
GoogleAnalytics,
9+
InquirerWrapper,
10+
Util,
11+
type AiCodingAssistant,
12+
AI_ASSISTANT_MCP_CONFIGS,
13+
AI_ASSISTANT_CHOICES,
14+
AI_ASSISTANT_LABELS,
15+
detectFramework,
16+
App,
17+
type BaseTemplateManager,
18+
TEMPLATE_MANAGER,
19+
} from "@igniteui/cli-core";
220
import { ArgumentsCamelCase, CommandModule } from "yargs";
321

422
export function configureMCP(assistants: AiCodingAssistant[]): void {
@@ -14,8 +32,8 @@ export function configureMCP(assistants: AiCodingAssistant[]): void {
1432
}
1533
}
1634

17-
export function configureSkills(agents: AIAgentTarget[]): void {
18-
const result = copyAISkillsToProject(agents);
35+
export function configureSkills(agents: AIAgentTarget[], framework: string): void {
36+
const result = copyAISkillsToProject(agents, framework);
1937
if (result.found === 0) {
2038
Util.warn("No AI skill files found. Make sure packages are installed (npm install) " +
2139
"and your Ignite UI packages are up-to-date.", "yellow");
@@ -32,7 +50,12 @@ export function configureSkills(agents: AIAgentTarget[]): void {
3250
type AIAgentOption = AIAgentTarget | "none";
3351
type AIAssistantOption = AiCodingAssistant | "none";
3452

35-
export async function configure(agents: AIAgentOption[] = [], assistants: AIAssistantOption[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> {
53+
export async function configure(framework: string, agents: AIAgentOption[] = [], assistants: AIAssistantOption[] = [], skills = true): Promise<{ agents: AIAgentTarget[], assistants: AiCodingAssistant[] }> {
54+
if (framework === "jquery") {
55+
// currently not supported
56+
return { agents: [], assistants: [] };
57+
}
58+
3659
if (!agents.length) {
3760
agents = await promptForAgents();
3861
}
@@ -53,9 +76,9 @@ export async function configure(agents: AIAgentOption[] = [], assistants: AIAssi
5376
Util.log("No AI configuration selected. Skipping.");
5477
} else {
5578
if (skills) {
56-
configureSkills(resolvedAgents);
79+
configureSkills(resolvedAgents, framework);
5780
}
58-
copyAgentInstructionFiles(resolvedAgents);
81+
copyAgentInstructionFiles(resolvedAgents, framework);
5982
}
6083

6184
return { agents: resolvedAgents, assistants: resolvedAssistants };
@@ -82,7 +105,7 @@ const AI_ASSISTANT_CHECKBOX_CHOICES = [
82105
}))
83106
];
84107

85-
export async function promptForAgents(): Promise<AIAgentOption[]> {
108+
async function promptForAgents(): Promise<AIAgentOption[]> {
86109
let selected: AIAgentOption[] = AI_AGENT_CHECKBOX_DEFAULTS;
87110
if (Util.canPrompt()) {
88111
const result = await InquirerWrapper.checkbox({
@@ -95,7 +118,7 @@ export async function promptForAgents(): Promise<AIAgentOption[]> {
95118
return selected;
96119
}
97120

98-
export async function promptForAssistant(): Promise<AIAssistantOption[]> {
121+
async function promptForAssistant(): Promise<AIAssistantOption[]> {
99122
let selected: AIAssistantOption[] = AI_ASSISTANT_CHECKBOX_DEFAULTS;
100123
if (Util.canPrompt()) {
101124
const result = await InquirerWrapper.checkbox({
@@ -108,6 +131,23 @@ export async function promptForAssistant(): Promise<AIAssistantOption[]> {
108131
return selected;
109132
}
110133

134+
/** delayed call so it's not immediate on module import for testing purposes */
135+
function getTemplateManager(): BaseTemplateManager {
136+
return App.container.get<BaseTemplateManager>(TEMPLATE_MANAGER);
137+
}
138+
139+
/** Separate from the PromptSession prompt due to step by step config */
140+
async function promptForFrameworkId(): Promise<string> {
141+
const tm = getTemplateManager();
142+
const frameRes: string = await InquirerWrapper.select({
143+
name: "framework",
144+
message: "Choose framework:",
145+
choices: tm.getFrameworkNames(true),
146+
default: "Angular"
147+
});
148+
return tm.getFrameworkByName(frameRes).id;
149+
}
150+
111151
const command: CommandModule = {
112152
command: "ai-config",
113153
describe: "Configures Ignite UI AI tooling (MCP servers, AI coding skills and instructions)",
@@ -122,6 +162,12 @@ const command: CommandModule = {
122162
describe: "Coding assistant(s) to configure MCP servers for",
123163
choices: [...AI_ASSISTANT_CHOICES, "none"] as string[],
124164
type: "array"
165+
})
166+
.option("framework", {
167+
alias: "f",
168+
describe: "Manually set project framework to configure AI for.",
169+
choices: getTemplateManager()?.getFrameworkIds(true),
170+
type: "string"
125171
}),
126172
async handler(argv: ArgumentsCamelCase) {
127173
const agents = (argv.agents ?? []) as AIAgentOption[];
@@ -131,12 +177,30 @@ const command: CommandModule = {
131177
cd: "Ai Config"
132178
});
133179

134-
const result = await configure(agents, assistants);
180+
let framework: string = argv.framework as string ?? detectFramework();
181+
if (!framework) {
182+
Util.log("Framework not provided and couldn't detect project from config or structure.");
183+
if (Util.canPrompt()) {
184+
framework = await promptForFrameworkId();
185+
} else {
186+
return Util.error("Please provide --framework argument.", "red");
187+
}
188+
}
189+
if (!getTemplateManager()?.getFrameworkById(framework)) {
190+
return Util.error("Framework not supported", "red");
191+
}
192+
193+
if (framework === "jquery") {
194+
Util.log("AI Config currently not available for jQuery projects.");
195+
}
196+
197+
const result = await configure(framework, agents, assistants);
135198

136199
GoogleAnalytics.post({
137200
t: "event",
138201
ec: "$ig ai-config",
139-
ea: `agent: ${result.agents.join(", ") || "none"}; assistant: ${result.assistants.join(", ") || "none"}`
202+
ea: `agent: ${result.agents.join(", ") || "none"}; assistant: ${result.assistants.join(", ") || "none"}`,
203+
cd1: framework
140204
});
141205
}
142206
};

packages/cli/lib/commands/new.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ const command: NewCommandType = {
162162
}
163163

164164
process.chdir(argv.name);
165-
await configure(argv.agents as (AIAgentTarget | "none")[], argv.assistants as (AiCodingAssistant | "none")[]);
165+
await configure(argv.framework, argv.agents as (AIAgentTarget | "none")[], argv.assistants as (AiCodingAssistant | "none")[]);
166166
process.chdir("..");
167167

168168
Util.log(Util.greenCheck() + " Project Created");

packages/core/prompt/BasePromptSession.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export abstract class BasePromptSession {
7373
}
7474
// move cwd to project folder
7575
process.chdir(projectName);
76-
await this.configureAI();
76+
await this.configureAI(framework.id);
7777
}
7878
await this.chooseActionLoop(projLibrary);
7979
//TODO: restore cwd?
@@ -102,7 +102,7 @@ export abstract class BasePromptSession {
102102
protected abstract upgradePackages();
103103

104104
/** Configure Ignite UI AI tooling (MCP servers and AI coding skills) for the project */
105-
protected abstract configureAI(): Promise<void>;
105+
protected abstract configureAI(frameworkId: string): Promise<void>;
106106

107107
/**
108108
* Get user name and set template's extra configurations if any

packages/core/util/ai-skills.ts

Lines changed: 10 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ import type { BaseTemplateManager } from "../templates";
33
import { FS_TOKEN, IFileSystem } from "../types/FileSystem";
44
import { NPM_ANGULAR, NPM_REACT, NPM_WEBCOMPONENTS, resolvePackage, UPGRADEABLE_PACKAGES } from "../update/package-resolve";
55
import { App } from "./App";
6-
import { detectBlazorFromCsproj, detectFrameworkFromPackageJson } from "./detect-framework";
76
import { FsFileSystem } from "./FileSystem";
87
import { TEMPLATE_MANAGER } from "./GlobalConstants";
9-
import { ProjectConfig } from "./ProjectConfig";
108
import { Util } from "./Util";
119

1210
export const AI_AGENT_CHOICES = ["generic", "claude", "copilot", "cursor", "codex", "windsurf", "gemini", "junie"] as const;
@@ -86,26 +84,10 @@ function resolveTemplateFilesDir(framework: string): string | null {
8684
* Ignite UI packages that are relevant to the project's detected framework.
8785
* Falls back to the bundled template skills when no npm package is installed.
8886
*/
89-
function resolveSkillsRoots(): string[] {
87+
function resolveSkillsRoots(framework: string): string[] {
9088
const fs = App.container.get<IFileSystem>(FS_TOKEN);
9189
const roots: string[] = [];
9290

93-
let framework: string | null = null;
94-
try {
95-
if (ProjectConfig.hasLocalConfig()) {
96-
framework = ProjectConfig.getConfig().project?.framework?.toLowerCase() ?? null;
97-
}
98-
} catch { /* config not readable – fall through to scan all */ }
99-
100-
// Blazor has no npm package — when explicitly configured, skip npm scanning
101-
if (framework === "blazor") {
102-
const filesDir = resolveTemplateFilesDir(framework);
103-
if (filesDir) {
104-
roots.push(path.join(filesDir, AI_SKILLS_DIR_NAME));
105-
}
106-
return roots;
107-
}
108-
10991
const allPkgKeys = Object.keys(UPGRADEABLE_PACKAGES);
11092
let candidates = new Set<string>();
11193
if (framework === "angular") {
@@ -129,13 +111,9 @@ function resolveSkillsRoots(): string[] {
129111

130112
if (!roots.length) {
131113
// if no root discovered, take the root from the appropriate project template files:
132-
// Try Blazor (.csproj) detection only as a last resort, after npm scanning found nothing
133-
framework ??= detectBlazorFromCsproj() ? "blazor" : detectFrameworkFromPackageJson();
134-
if (framework) {
135-
const filesDir = resolveTemplateFilesDir(framework);
136-
if (filesDir) {
137-
roots.push(path.join(filesDir, AI_SKILLS_DIR_NAME));
138-
}
114+
const filesDir = resolveTemplateFilesDir(framework);
115+
if (filesDir) {
116+
roots.push(path.join(filesDir, AI_SKILLS_DIR_NAME));
139117
}
140118
}
141119

@@ -147,14 +125,14 @@ function resolveSkillsRoots(): string[] {
147125
* skills directories for each of the given AI agents.
148126
* @param agents – list of AI agent targets to copy skills for
149127
*/
150-
export function copyAISkillsToProject(agents: AIAgentTarget[]): AISkillsCopyResult {
128+
export function copyAISkillsToProject(agents: AIAgentTarget[], framework: string): AISkillsCopyResult {
151129
const result: AISkillsCopyResult = { found: 0, skipped: 0, failed: 0 };
152130
// Source reads (glob + readFile) always use physical FS - skill files can
153131
// come from sources outside the project virtual tree (external/global package):
154132
const srcFs = new FsFileSystem();
155133
// Destination writes respect the App FS (which may be virtual):
156134
const destFs = App.container.get<IFileSystem>(FS_TOKEN);
157-
const skillsRoots = resolveSkillsRoots();
135+
const skillsRoots = resolveSkillsRoots(framework);
158136

159137
if (!skillsRoots.length) {
160138
return result;
@@ -208,20 +186,8 @@ export function copyAISkillsToProject(agents: AIAgentTarget[]): AISkillsCopyResu
208186
* Resolves the AGENTS.md source file content from the bundled project template files.
209187
* AGENTS.md lives only in the template files/ directory, not in npm packages.
210188
*/
211-
function resolveAgentsContent(): string | null {
212-
let framework: string | null = null;
213-
try {
214-
if (ProjectConfig.hasLocalConfig()) {
215-
framework = ProjectConfig.getConfig().project?.framework?.toLowerCase() ?? null;
216-
}
217-
} catch { /* fall through */ }
218-
framework ??= detectBlazorFromCsproj() ? "blazor" : detectFrameworkFromPackageJson();
219-
220-
if (!framework) {
221-
return null;
222-
}
223-
224-
const filesDir = resolveTemplateFilesDir(framework);
189+
function resolveAgentsContent(framework: string): string | null {
190+
const filesDir = resolveTemplateFilesDir(framework.toLowerCase());
225191
if (!filesDir) {
226192
return null;
227193
}
@@ -238,8 +204,8 @@ function resolveAgentsContent(): string | null {
238204
* each of the given agents.
239205
* @param agents – list of AI agent targets to create instruction files for
240206
*/
241-
export function copyAgentInstructionFiles(agents: AIAgentTarget[]): void {
242-
const content = resolveAgentsContent();
207+
export function copyAgentInstructionFiles(agents: AIAgentTarget[], framework: string): void {
208+
const content = resolveAgentsContent(framework);
243209
if (!content) {
244210
return;
245211
}

0 commit comments

Comments
 (0)