Skip to content

Commit 57e253c

Browse files
CopilotMarina-L-Stoyanovadamyanpetev
authored
feat(ai-config): add AI agent skills copy functionality (#1502)
Co-authored-by: Marina Stoyanova <Marina-L-Stoyanova@users.noreply.github.com> Co-authored-by: damyanpetev <damyanpetev@users.noreply.github.com>
1 parent f0c04c5 commit 57e253c

14 files changed

Lines changed: 436 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ All scaffolded projects now include AI-ready configuration files to enhance the
2727
* **CLAUDE.md** for Claude Code integration ([#1546](https://github.com/IgniteUI/igniteui-cli/pull/1546))
2828
* **VS Code MCP configuration** (`mcp.json`) pre-configured with `angular-cli`, `igniteui-cli`, and `igniteui-theming` MCP servers ([#1563](https://github.com/IgniteUI/igniteui-cli/pull/1563))
2929
* **Claude skills** for component usage, theming customization, and bundle size optimization ([#1573](https://github.com/IgniteUI/igniteui-cli/pull/1573))
30+
* **`ig ai-config` CLI command** to configure AI tooling in an existing project: writes `.vscode/mcp.json` with `igniteui-cli` and `igniteui-theming` MCP servers and copies AI coding skill files from installed Ignite UI packages to `.claude/skills/` ([#1502](https://github.com/IgniteUI/igniteui-cli/pull/1502))
31+
* **AI skills auto-copy on `ng add`**: the `cli-config` Angular schematic now automatically copies skill files from the installed Ignite UI package's `skills/` directory into `.claude/skills/` as part of the `ng add igniteui-angular` flow ([#1502](https://github.com/IgniteUI/igniteui-cli/pull/1502))
3032

3133
---
3234

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ ig start
145145

146146
## Configure AI Tooling
147147

148-
To automatically configure Ignite UI MCP servers for VS Code, run:
148+
To automatically configure Ignite UI AI tooling - MCP servers and AI coding skills, run:
149149

150150
```bash
151151
ig ai-config
152152
```
153153

154-
This creates or updates `.vscode/mcp.json` in the current project with entries for both the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers. Existing servers in the file are preserved. New projects are created with AI tooling configuration out of the box.
154+
This creates or updates `.vscode/mcp.json` in the current project with entries for both the [Ignite UI MCP](#mcp-server) and `igniteui-theming` MCP servers. Existing servers in the file are preserved. It also copies any AI coding skill files from installed Ignite UI packages into the project. New projects are created with AI tooling configuration out of the box.
155155

156156
## MCP Server
157157

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FsFileSystem, GoogleAnalytics, IFileSystem, Util } from "@igniteui/cli-core";
1+
import { copyAISkillsToProject, FsFileSystem, GoogleAnalytics, IFileSystem, Util } from "@igniteui/cli-core";
22
import { ArgumentsCamelCase, CommandModule } from "yargs";
33
import * as path from "path";
44

@@ -63,13 +63,24 @@ export function configureMCP(fileSystem: IFileSystem = new FsFileSystem()): void
6363
Util.log(Util.greenCheck() + ` MCP servers configured in ${configPath}`);
6464
}
6565

66+
export function configureSkills(): void {
67+
const result = copyAISkillsToProject();
68+
if (result === "copied") {
69+
Util.log(Util.greenCheck() + " AI skills added to the project.");
70+
} else {
71+
Util.warn("No AI skill files found. Make sure packages are installed (npm install) " +
72+
"and your Ignite UI package includes a skills/ directory.", "yellow");
73+
}
74+
}
75+
6676
export function configure(fileSystem: IFileSystem = new FsFileSystem()): void {
6777
configureMCP(fileSystem);
78+
configureSkills();
6879
}
6980

7081
const command: CommandModule = {
7182
command: "ai-config",
72-
describe: "Configure Ignite UI AI tooling (MCP servers)",
83+
describe: "Configure Ignite UI AI tooling (MCP servers and AI coding skills)",
7384
builder: (yargs) => yargs.usage(""),
7485
async handler(_argv: ArgumentsCamelCase) {
7586
GoogleAnalytics.post({

packages/core/prompt/BasePromptSession.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export abstract class BasePromptSession {
9898
/** Upgrade packages to use private Infragistics feed */
9999
protected abstract upgradePackages();
100100

101-
/** Configure Ignite UI AI tooling (MCP servers) for the project */
101+
/** Configure Ignite UI AI tooling (MCP servers and AI coding skills) for the project */
102102
protected abstract configureAI(): Promise<void>;
103103

104104
/**

packages/core/prompt/InquirerWrapper.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { checkbox, input, select, Separator } from '@inquirer/prompts';
1+
import { checkbox, confirm, input, select, Separator } from '@inquirer/prompts';
22
import { Context } from '@inquirer/type';
33

44
// ref - node_modules\@inquirer\input\dist\cjs\types\index.d.ts - bc for some reason this is not publicly exported
@@ -32,4 +32,8 @@ export class InquirerWrapper {
3232
public static async checkbox(message: InputConfig & { choices: (string | Separator)[] }, context?: Context): Promise<string[]> {
3333
return checkbox(message, context);
3434
}
35+
36+
public static async confirm(message: { message: string; default?: boolean }, context?: Context): Promise<boolean> {
37+
return confirm(message, context);
38+
}
3539
}

packages/core/util/ai-skills.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import * as path from "path";
2+
import { App } from "./App";
3+
import { IFileSystem, FS_TOKEN } from "../types/FileSystem";
4+
import { ProjectConfig } from "./ProjectConfig";
5+
import { Util } from "./Util";
6+
import { NPM_ANGULAR, NPM_REACT, resolvePackage, UPGRADEABLE_PACKAGES } from "../update/package-resolve";
7+
8+
const CLAUDE_SKILLS_DIR = ".claude/skills";
9+
10+
/**
11+
* Returns the list of 'skills/' directory paths found in installed
12+
* Ignite UI packages that are relevant to the project's detected framework.
13+
*/
14+
function resolveSkillsRoots(): string[] {
15+
const fs = App.container.get<IFileSystem>(FS_TOKEN);
16+
const roots: string[] = [];
17+
18+
let framework: string | null = null;
19+
try {
20+
if (ProjectConfig.hasLocalConfig()) {
21+
framework = ProjectConfig.getConfig().project?.framework?.toLowerCase() ?? null;
22+
}
23+
} catch { /* config not readable – fall through to scan all */ }
24+
25+
const allPkgKeys = Object.keys(UPGRADEABLE_PACKAGES);
26+
let candidates: string[];
27+
if (framework === "angular") {
28+
candidates = [NPM_ANGULAR];
29+
} else if (framework === "react") {
30+
candidates = [NPM_REACT];
31+
} else if (framework === "webcomponents") {
32+
candidates = allPkgKeys.filter(k => k.startsWith("igniteui-webcomponents"));
33+
} else {
34+
candidates = allPkgKeys;
35+
}
36+
37+
for (const pkg of candidates) {
38+
const resolved = resolvePackage(pkg as keyof typeof UPGRADEABLE_PACKAGES);
39+
const skillsRoot = `node_modules/${resolved}/skills`;
40+
if (fs.directoryExists(skillsRoot) && !roots.includes(skillsRoot)) {
41+
roots.push(skillsRoot);
42+
}
43+
}
44+
45+
return roots;
46+
}
47+
48+
/**
49+
* Copies skill files from the installed Ignite UI package(s) into .claude/skills/.
50+
* Works with both real FS (CLI) and virtual Tree FS (schematics) through IFileSystem.
51+
*/
52+
export function copyAISkillsToProject(): "copied" | "no-source" {
53+
const fs = App.container.get<IFileSystem>(FS_TOKEN);
54+
const skillsRoots = resolveSkillsRoots();
55+
56+
if (!skillsRoots.length) {
57+
return "no-source";
58+
}
59+
60+
const multiRoot = skillsRoots.length > 1;
61+
let copied = false;
62+
63+
for (const skillsRoot of skillsRoots) {
64+
const rawPaths = fs.glob(skillsRoot, "**/*");
65+
const pkgDirName = multiRoot ? path.basename(path.dirname(skillsRoot)) : "";
66+
67+
for (const p of rawPaths) {
68+
// Normalize to posix and strip leading '/' so path.posix.relative works
69+
// across both FsFileSystem (relative paths) and NgTreeFileSystem (tree-rooted paths)
70+
const normP = p.replace(/\\/g, "/").replace(/^\//, "");
71+
const normRoot = skillsRoot.replace(/\\/g, "/").replace(/^\//, "");
72+
const rel = path.posix.relative(normRoot, normP);
73+
const dest = multiRoot
74+
? `${CLAUDE_SKILLS_DIR}/${pkgDirName}/${rel}`
75+
: `${CLAUDE_SKILLS_DIR}/${rel}`;
76+
77+
fs.writeFile(dest, fs.readFile(p));
78+
Util.log(`${Util.greenCheck()} Created ${dest}`);
79+
copied = true;
80+
}
81+
}
82+
return copied ? "copied" : "no-source";
83+
}

packages/core/util/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './ai-skills';
12
export * from './GoogleAnalytics';
23
export * from './Util';
34
export * from './ProjectConfig';

packages/ng-schematics/src/cli-config/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as ts from "typescript";
22
import { DependencyNotFoundException } from "@angular-devkit/core";
33
import { chain, FileDoesNotExistException, Rule, SchematicContext, Tree } from "@angular-devkit/schematics";
44
import * as jsonc from "jsonc-parser";
5-
import { addClassToBody, FormatSettings, NPM_ANGULAR, resolvePackage, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core";
5+
import { addClassToBody, copyAISkillsToProject, FormatSettings, NPM_ANGULAR, resolvePackage, TypeScriptAstTransformer, TypeScriptUtils } from "@igniteui/cli-core";
66
import { AngularTypeScriptFileUpdate } from "@igniteui/angular-templates";
77
import { createCliConfig } from "../utils/cli-config";
88
import { setVirtual } from "../utils/NgFileSystem";
@@ -120,6 +120,8 @@ function importStyles(): Rule {
120120

121121
export function addAIConfig(): Rule {
122122
return (tree: Tree) => {
123+
copyAISkillsToProject();
124+
123125
const mcpFilePath = "/.vscode/mcp.json";
124126
const angularCliServer = {
125127
command: "npx",

packages/ng-schematics/src/collection.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"factory": "./cli-config/index"
3535
},
3636
"ai-config": {
37-
"description": "Adds AI/MCP server configuration to .vscode/mcp.json.",
37+
"description": "Configures AI tooling: MCP servers and AI coding skills.",
3838
"factory": "./cli-config/index#addAIConfig"
3939
},
4040
"upgrade-packages": {

packages/ng-schematics/src/component/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export function component(options: ComponentOptions): Rule {
8787
component: projLib.components,
8888
custom: projLib.getCustomTemplates()
8989
};
90+
void properties; // cache templates for use inside chooseActionLoop
9091
let prompt: SchematicsPromptSession;
9192
if (!options.template || !options.name) {
9293
prompt = new SchematicsPromptSession(templateManager);

0 commit comments

Comments
 (0)