Skip to content

Commit 4ee5c3f

Browse files
damyanpetevCopilot
andauthored
chore: bundle agent skills & add migration to copy into project (#16961)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a23fc76 commit 4ee5c3f

5 files changed

Lines changed: 168 additions & 3 deletions

File tree

.github/workflows/npm-publish.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,11 @@ jobs:
4141
if [[ ${VERSION} == *"alpha"* || ${VERSION} == *"beta"* || ${VERSION} == *"rc"* ]]; then echo "NPM_TAG=next"; else echo "NPM_TAG=latest"; fi >> $GITHUB_ENV
4242
echo ${NPM_TAG}
4343
44-
- name: Copy readme and license for igniteui-angular
44+
- name: Copy readme, license & skills for igniteui-angular
4545
run: |
4646
cp ../../README.md README.md
4747
cp ../../LICENSE LICENSE
48+
cp -r ../../skills/. skills
4849
working-directory: dist/igniteui-angular
4950

5051
- name: Copy i18n files

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ All notable changes for each version of this project will be documented in this
5353
- Improved positioning accuracy for container-based overlays with fixed container bounds.
5454

5555
- **AI-Assisted Development - Copilot Skills**
56-
- Three consolidated Copilot Skills are now included in the repository to teach AI coding assistants (GitHub Copilot, Cursor, Windsurf, Claude, JetBrains AI, etc.) how to work with Ignite UI for Angular:
56+
- Three consolidated Copilot Skills are now included in the repository to teach AI coding assistants/agents (e.g., GitHub Copilot, Cursor, Windsurf, Claude, JetBrains AI, etc.) how to work with Ignite UI for Angular:
5757
- **Components** - UI Components (form controls, layout, data display, feedback/overlays, directives — Input Group, Combo, Select, Date/Time Pickers, Calendar, Tabs, Stepper, Accordion, List, Card, Dialog, Snackbar, Button, Ripple, Tooltip, Drag and Drop, Layout Manager, Dock Manager and Charts (Area Chart, Bar Chart, Column Chart, Stock/Financial Chart, Pie Chart))
5858
- **Data Grids** - Data Grids (grid type selection, column config, sorting, filtering, selection, editing, grouping, paging, remote data, state persistence, Tree Grid, Hierarchical Grid, Grid Lite, Pivot Grid)
5959
- **Theming & Styling** - Theming & Styling (includes MCP server setup for live theming tools)
60-
- These skills are automatically discovered by GitHub Copilot via `.github/copilot-instructions.md` and can be manually configured in other IDEs. For more information, see the [README](README.md#ai-assisted-development).
60+
- These skills are automatically discovered when placed in the agent's skills path ( e.g. `.claude/skills`) and this release ships with an optional migration to add those to your project. For more information, see the [README](README.md#ai-assisted-development).
6161

6262
### General
6363

projects/igniteui-angular/migrations/migration-collection.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@
272272
"factory": "./update-21_1_0_css-migration",
273273
"recommended": true,
274274
"optional": true
275+
},
276+
"migration-54": {
277+
"version": "21.1.0",
278+
"description": "Add AI Agent skills for Ignite UI for Angular components and theming to the project.",
279+
"factory": "./update-21_1_0_add-agent-skills",
280+
"recommended": true,
281+
"optional": true
275282
}
276283
}
277284
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import * as path from 'path';
2+
3+
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
4+
import { setupTestTree } from '../common/setup.spec';
5+
import { IG_LICENSED_PACKAGE_NAME, IG_PACKAGE_NAME } from '../common/tsUtils';
6+
7+
describe(`Add Agent skills`, () => {
8+
let appTree: UnitTestTree;
9+
const schematicRunner = new SchematicTestRunner('ig-migrate', path.join(__dirname, '../migration-collection.json'));
10+
11+
beforeEach(() => {
12+
appTree = setupTestTree();
13+
});
14+
15+
const migrationName = 'migration-54';
16+
17+
const packagePath = `/node_modules/${IG_PACKAGE_NAME}`;
18+
const licensedPackagePath = `/node_modules/${IG_LICENSED_PACKAGE_NAME}`;
19+
20+
const addPackageSkills = (tree: UnitTestTree, basePath: string, packageName = IG_PACKAGE_NAME) => {
21+
tree.create(`${basePath}/package.json`, JSON.stringify({ name: packageName, version: '21.1.0' }));
22+
tree.create(`${basePath}/skills/igniteui-angular-components/SKILL.md`, '# Components Skill');
23+
tree.create(`${basePath}/skills/igniteui-angular-grids/SKILL.md`, '# Grids Skill');
24+
};
25+
26+
it('should copy skill files to .claude/skills by default when no existing agent directories have content', async () => {
27+
addPackageSkills(appTree, packagePath);
28+
29+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
30+
31+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeTrue();
32+
expect(tree.exists('/.claude/skills/igniteui-angular-grids/SKILL.md')).toBeTrue();
33+
});
34+
35+
it('should copy skill files to .agents/skills when that directory already has content', async () => {
36+
addPackageSkills(appTree, packagePath);
37+
appTree.create('/.agents/skills/existing-skill/SKILL.md', '# Existing Skill');
38+
39+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
40+
41+
expect(tree.exists('/.agents/skills/igniteui-angular-components/SKILL.md')).toBeTrue();
42+
expect(tree.exists('/.agents/skills/igniteui-angular-grids/SKILL.md')).toBeTrue();
43+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
44+
});
45+
46+
it('should copy skill files to .cursor/skills when that directory already has content', async () => {
47+
addPackageSkills(appTree, packagePath);
48+
appTree.create('/.cursor/skills/existing-skill/SKILL.md', '# Existing Skill');
49+
50+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
51+
52+
expect(tree.exists('/.cursor/skills/igniteui-angular-components/SKILL.md')).toBeTrue();
53+
expect(tree.exists('/.cursor/skills/igniteui-angular-grids/SKILL.md')).toBeTrue();
54+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
55+
});
56+
57+
it('should copy skill files to .github/skills when that directory already has content', async () => {
58+
addPackageSkills(appTree, packagePath);
59+
appTree.create('/.github/skills/existing-skill/SKILL.md', '# Existing Skill');
60+
61+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
62+
63+
expect(tree.exists('/.github/skills/igniteui-angular-components/SKILL.md')).toBeTrue();
64+
expect(tree.exists('/.github/skills/igniteui-angular-grids/SKILL.md')).toBeTrue();
65+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
66+
});
67+
68+
it('should use the licensed package skills when only the licensed package is installed', async () => {
69+
addPackageSkills(appTree, licensedPackagePath, IG_LICENSED_PACKAGE_NAME);
70+
71+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
72+
73+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeTrue();
74+
});
75+
76+
it('should not create any skill files when neither package is installed', async () => {
77+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
78+
79+
expect(tree.exists('/.claude/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
80+
expect(tree.exists('/.agents/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
81+
expect(tree.exists('/.cursor/skills/igniteui-angular-components/SKILL.md')).toBeFalse();
82+
});
83+
84+
it('should overwrite existing skill files when re-running the migration', async () => {
85+
addPackageSkills(appTree, packagePath);
86+
appTree.create('/.claude/skills/igniteui-angular-components/SKILL.md', '# Old Content');
87+
88+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
89+
90+
expect(tree.readContent('/.claude/skills/igniteui-angular-components/SKILL.md')).toBe('# Components Skill');
91+
});
92+
93+
it('should preserve content of all skill files during copy', async () => {
94+
const componentsContent = '# Components Skill\nSome detailed content here.';
95+
const gridsContent = '# Grids Skill\nGrid-specific instructions.';
96+
appTree.create(`${packagePath}/package.json`, JSON.stringify({ name: IG_PACKAGE_NAME, version: '21.1.0' }));
97+
appTree.create(`${packagePath}/skills/igniteui-angular-components/SKILL.md`, componentsContent);
98+
appTree.create(`${packagePath}/skills/igniteui-angular-grids/SKILL.md`, gridsContent);
99+
100+
const tree = await schematicRunner.runSchematic(migrationName, {}, appTree);
101+
102+
expect(tree.readContent('/.claude/skills/igniteui-angular-components/SKILL.md')).toBe(componentsContent);
103+
expect(tree.readContent('/.claude/skills/igniteui-angular-grids/SKILL.md')).toBe(gridsContent);
104+
});
105+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { normalize, type Path, relative } from '@angular-devkit/core';
2+
import {
3+
apply,
4+
MergeStrategy,
5+
mergeWith,
6+
move,
7+
type Rule,
8+
type SchematicContext,
9+
type Source,
10+
Tree
11+
} from '@angular-devkit/schematics';
12+
import { IG_LICENSED_PACKAGE_NAME, IG_PACKAGE_NAME } from '../common/tsUtils';
13+
14+
export default (): Rule => async (host: Tree, context: SchematicContext) => {
15+
let skillsPath = normalize(`.claude/skills`);
16+
const agentsPath = normalize(`.agents/skills`);
17+
const cursorSkills = normalize(`.cursor/skills`);
18+
const githubSkills = normalize(`.github/skills`);
19+
20+
if (host.getDir(agentsPath).subdirs.length)
21+
skillsPath = agentsPath;
22+
if (host.getDir(cursorSkills).subdirs.length)
23+
skillsPath = cursorSkills;
24+
if (host.getDir(githubSkills).subdirs.length)
25+
skillsPath = githubSkills;
26+
27+
let from: Path | undefined;
28+
const packagePath = `/node_modules/${IG_PACKAGE_NAME}`;
29+
const licensedPackagePath = `/node_modules/${IG_LICENSED_PACKAGE_NAME}`;
30+
31+
// exists works on files only, so check package.json
32+
if (host.exists(`${packagePath}/package.json`))
33+
from = normalize(`${packagePath}/skills`);
34+
35+
if (host.exists(`${licensedPackagePath}/package.json`))
36+
from = normalize(`${licensedPackagePath}/skills`);
37+
38+
if (from) {
39+
const skillsSource: Source = () => {
40+
const tree = Tree.empty();
41+
host.getDir(from).visit((filePath) => {
42+
tree.create(relative(from, filePath), host.read(filePath));
43+
});
44+
return tree;
45+
};
46+
47+
context.logger.info(`Added Ignite UI for Angular agent skill files to ${skillsPath}.`);
48+
context.logger.info(`If your agent doesn't load from that path by default, please refer to its documentation and move the skill files as needed.`);
49+
50+
return mergeWith(apply(skillsSource, [move(skillsPath)]), MergeStrategy.Overwrite);
51+
}
52+
};

0 commit comments

Comments
 (0)