-
Notifications
You must be signed in to change notification settings - Fork 8
Add AI coding skills prompt to cli-config schematic with conditional agent selection #1502
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e1b6a03
53f2a41
d85905e
7d653a5
9d2c441
7e585b9
56be65b
c294c77
2dd0a82
9c8e4b2
cd601c0
53e73f5
fa67f32
b9967bd
c6c401d
189cef8
16dd25b
cd6cc33
f966f2e
3f44921
d2eb93f
9dfe26a
7469211
bc8d895
15bb3c2
2a8c381
ff9e6f5
a44105e
6323f9a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea for this was to be added in core package so the utility functions can be directly reused in schematics directly, unless it's hard to do with the current FS API we have |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { copyAISkillsToProject as _copyFromCore } from "@igniteui/cli-core"; | ||
|
|
||
| export function copyAISkillsToProject(): "copied" | "up-to-date" | "no-source" { | ||
| return _copyFromCore(); | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { GoogleAnalytics, Util } from "@igniteui/cli-core"; | ||
| import { copyAISkillsToProject } from "../ai-skills"; | ||
| import { CommandType } from "./types"; | ||
|
|
||
| const command: CommandType = { | ||
| command: "add-skills", | ||
| describe: "Copies AI coding skills to the current project", | ||
| builder: {}, | ||
| async handler() { | ||
| GoogleAnalytics.post({ | ||
| t: "screenview", | ||
| cd: "Add Skills" | ||
| }); | ||
|
|
||
| GoogleAnalytics.post({ | ||
| t: "event", | ||
| ec: "$ig add-skills" | ||
| }); | ||
|
|
||
| const result = copyAISkillsToProject(); | ||
| if (result === "copied") { | ||
| Util.log(Util.greenCheck() + " AI skills added to the project."); | ||
| } else if (result === "up-to-date") { | ||
| Util.log(Util.greenCheck() + " AI skills are already up to date."); | ||
| } else { | ||
| Util.warn("No AI skill files found. Make sure packages are installed (npm install) " + | ||
| "and your Ignite UI package includes a skills/ directory.", "yellow"); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| export default command; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,6 +19,7 @@ export class FsFileSystem implements IFileSystem { | |
| return fs.readFileSync(filePath).toString(); | ||
| } | ||
| public writeFile(filePath: string, text: string): void { | ||
| fs.mkdirSync(path.dirname(filePath), { recursive: true }); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're adding this to this implementation, should also do the same for the NgTreeFileSystem in packages\ng-schematics\src\utils\NgFileSystem.ts, unless that already behaves that way. And should be reflected in the description that the write creates structure along the way to the file if missing. |
||
| fs.writeFileSync(filePath, text); | ||
| } | ||
| public directoryExists(dirPath: string): boolean { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import * as path from "path"; | ||
| import { App } from "./App"; | ||
| import { IFileSystem, FS_TOKEN } from "../types/FileSystem"; | ||
| import { ProjectConfig } from "./ProjectConfig"; | ||
| import { Util } from "./Util"; | ||
| import { NPM_ANGULAR, NPM_REACT, resolvePackage, UPGRADEABLE_PACKAGES } from "../update/package-resolve"; | ||
|
|
||
| const CLAUDE_SKILLS_DIR = ".claude/skills"; | ||
|
|
||
| /** | ||
| * Returns the list of 'skills/' directory paths found in installed | ||
| * Ignite UI packages that are relevant to the project's detected framework. | ||
| */ | ||
| function resolveSkillsRoots(): string[] { | ||
| const fs = App.container.get<IFileSystem>(FS_TOKEN); | ||
| const roots: string[] = []; | ||
|
|
||
| let framework: string | null = null; | ||
| try { | ||
| if (ProjectConfig.hasLocalConfig()) { | ||
| framework = ProjectConfig.getConfig().project?.framework?.toLowerCase() ?? null; | ||
| } | ||
| } catch { /* config not readable – fall through to scan all */ } | ||
|
|
||
| const allPkgKeys = Object.keys(UPGRADEABLE_PACKAGES); | ||
| let candidates: string[]; | ||
| if (framework === "angular") { | ||
| candidates = [NPM_ANGULAR]; | ||
| } else if (framework === "react") { | ||
| candidates = [NPM_REACT]; | ||
| } else if (framework === "webcomponents") { | ||
| candidates = allPkgKeys.filter(k => k.startsWith("igniteui-webcomponents")); | ||
| } else { | ||
| candidates = allPkgKeys; | ||
| } | ||
|
|
||
| for (const pkg of candidates) { | ||
| const resolved = resolvePackage(pkg as keyof typeof UPGRADEABLE_PACKAGES); | ||
| const skillsRoot = `node_modules/${resolved}/skills`; | ||
| if (fs.directoryExists(skillsRoot) && !roots.includes(skillsRoot)) { | ||
| roots.push(skillsRoot); | ||
| } | ||
| } | ||
|
|
||
| return roots; | ||
| } | ||
|
|
||
| /** | ||
| * Copies skill files from the installed Ignite UI package(s) into .claude/skills/. | ||
| * Works with both real FS (CLI) and virtual Tree FS (schematics) through IFileSystem. | ||
| */ | ||
| export function copyAISkillsToProject(): "copied" | "up-to-date" | "no-source" { | ||
| const fs = App.container.get<IFileSystem>(FS_TOKEN); | ||
| const skillsRoots = resolveSkillsRoots(); | ||
|
|
||
| if (!skillsRoots.length) { | ||
| return "no-source"; | ||
| } | ||
|
|
||
| const multiRoot = skillsRoots.length > 1; | ||
| let copied = false; | ||
|
|
||
| for (const skillsRoot of skillsRoots) { | ||
| const rawPaths = fs.glob(skillsRoot, "**/*"); | ||
| const pkgDirName = multiRoot ? path.basename(path.dirname(skillsRoot)) : ""; | ||
|
|
||
| for (const p of rawPaths) { | ||
| // Normalize to posix and strip leading '/' so path.posix.relative works | ||
| // across both FsFileSystem (relative paths) and NgTreeFileSystem (tree-rooted paths) | ||
| const normP = p.replace(/\\/g, "/").replace(/^\//, ""); | ||
| const normRoot = skillsRoot.replace(/\\/g, "/").replace(/^\//, ""); | ||
| const rel = path.posix.relative(normRoot, normP); | ||
| const dest = multiRoot | ||
| ? `${CLAUDE_SKILLS_DIR}/${pkgDirName}/${rel}` | ||
| : `${CLAUDE_SKILLS_DIR}/${rel}`; | ||
|
|
||
| if (!fs.fileExists(dest)) { | ||
| fs.writeFile(dest, fs.readFile(p)); | ||
| Util.log(`${Util.greenCheck()} Created ${dest}`); | ||
| copied = true; | ||
| } | ||
| } | ||
| } | ||
| return copied ? "copied" : "up-to-date"; | ||
| } |
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure Angular's pre-defined prompts work like step-by-step without some connection, pretty likely this will prompt all 3 in sequence
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, @copilot do you have any information if those prompts will actually depend on each other?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Angular's The Angular schematics That said, the current implementation guards the actual file operations: even if all prompts fire, no files are written unless
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, @copilot can you attempt an alternative implementation with the prompt implementation used in the project internally - see the prompting used by the ng-new schematic and the SchematicsPromptSession and getUserInput setup. Make that in a separate PR;
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm unable to create separate PRs — I can only make changes within this current PR branch. If you'd like me to implement the alternative approach using
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot Okay, sure update the implementation in this PR and also please remove the extra changes in .gitignore and yarn.lock
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implemented in
Also removed the |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/schema", | ||
| "$id": "igniteui-angular-cli-config", | ||
| "title": "Ignite UI for Angular CLI Config Options Schema", | ||
| "type": "object", | ||
| "properties": {}, | ||
| "required": [] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Odd to wait on install after
await PackageManager.flushQueuewhich also installs packages (though specific ones).I think the actual install happens in
start(thoughbuildinside) which is much harder to wait for, so I kind of get why. We'd ideally rework how start behaves, but that goes a bit out of scope. Might want to leave a TODO why this is added here.