Make ig ai-config agent-agnostic instead of hard-coding .claude/skills#1664
Make ig ai-config agent-agnostic instead of hard-coding .claude/skills#1664
ig ai-config agent-agnostic instead of hard-coding .claude/skills#1664Conversation
…ions - Add AIAgentTarget type and AI_AGENT_SKILLS_DIRS mapping for claude, copilot, cursor, codex, windsurf, gemini, junie, and generic agents - Add getSkillsDir() utility to resolve agent name to skills directory - Accept optional skillsDir parameter in copyAISkillsToProject() - Add --agent (-a) and --skills-dir (-d) CLI options to ig ai-config - Default to .claude/skills for backward compatibility when no option given - Add 15 new unit tests for agent-aware destinations and getSkillsDir Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/6ffa80e2-ee1c-4982-8606-c428fb045137 Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/6ffa80e2-ee1c-4982-8606-c428fb045137 Co-authored-by: kdinev <1472513+kdinev@users.noreply.github.com>
ig ai-config agent-agnostic instead of hard-coding .claude/skillsig ai-config agent-agnostic instead of hard-coding .claude/skills
|
@copilot Update angular schematics to accepts an optional output directory, when copying the skills. The ai-config schematic should work same as ai-config cli command |
…lls-dir options - Add ai-config-schema.json with agent and skillsDir properties - Update collection.json to reference the schema - Update aiConfig() to accept and pass through skillsDir - Update addAIConfig() to resolve skillsDir from agent/skillsDir options - Add 6 new schematic tests verifying agent-aware skill copy Agent-Logs-Url: https://github.com/IgniteUI/igniteui-cli/sessions/235a5b72-55c7-4dda-a33d-47abfb77073e Co-authored-by: Marina-L-Stoyanova <6087266+Marina-L-Stoyanova@users.noreply.github.com>
Done in d4478a9. The
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
There was a problem hiding this comment.
Pull request overview
Updates ig ai-config to support multiple AI agents by resolving agent-specific (or custom) skills destination directories instead of always writing to .claude/skills.
Changes:
- Core: add agent target type + agent→directory mapping,
getSkillsDir(), and allowcopyAISkillsToProject()to accept an optional output directory (defaulting to.claude/skills). - CLI: add
--agent/--skills-diroptions and thread the resolved directory through skills configuration. - Angular schematics: add
ai-configschema/options foragent/skillsDir, pass resolved directory through, and add schematic specs.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
packages/core/util/ai-skills.ts |
Introduces agent targets + mapping + resolver; makes skills copy destination configurable. |
packages/cli/lib/commands/ai-config.ts |
Adds CLI flags to select agent/custom directory and passes destination into core copy routine. |
packages/ng-schematics/src/cli-config/index.ts |
Adds schematic options and resolves skillsDir from agent/skillsDir. |
packages/ng-schematics/src/cli-config/ai-config-schema.json |
Defines schema for agent enum and skillsDir override. |
packages/ng-schematics/src/collection.json |
Wires the new schema into the ai-config schematic entry. |
packages/ng-schematics/src/cli-config/index_spec.ts |
Adds tests validating option resolution and passthrough behavior. |
spec/unit/ai-skills-spec.ts |
Adds core unit coverage for agent-aware destinations, resolver, and mapping shape. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const DEFAULT_SKILLS_DIR = ".claude/skills"; | ||
| const SKILLS_DIR_TEMPLATE = "__dot__claude/skills"; | ||
|
|
||
| export type AIAgentTarget = "claude" | "copilot" | "cursor" | "codex" | "windsurf" | "gemini" | "junie" | "generic"; | ||
|
|
||
| export const AI_AGENT_SKILLS_DIRS: Record<AIAgentTarget, string> = { | ||
| claude: ".claude/skills", | ||
| copilot: ".github/skills", | ||
| cursor: ".cursor/skills", | ||
| codex: ".codex/skills", | ||
| windsurf: ".windsurf/skills", | ||
| gemini: ".gemini/skills", | ||
| junie: ".junie/skills", | ||
| generic: ".agents/skills" | ||
| }; | ||
|
|
||
| /** | ||
| * Returns the project-level skills directory for the given AI agent target. | ||
| * Falls back to `.claude/skills` when no target is specified. | ||
| */ | ||
| export function getSkillsDir(target?: AIAgentTarget): string { | ||
| return AI_AGENT_SKILLS_DIRS[target ?? "claude"]; | ||
| } |
There was a problem hiding this comment.
DEFAULT_SKILLS_DIR duplicates the value of AI_AGENT_SKILLS_DIRS.claude, and the fallback logic is split between copyAISkillsToProject and getSkillsDir. This makes it easy for the defaults to drift if one is updated later. Consider deriving the default from a single source (e.g., set AI_AGENT_SKILLS_DIRS.claude = DEFAULT_SKILLS_DIR and/or have copyAISkillsToProject default to getSkillsDir()).
| @@ -102,8 +127,8 @@ export function copyAISkillsToProject(): AISkillsCopyResult { | |||
| 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}`; | |||
| ? `${outputDir}/${pkgDirName}/${rel}` | |||
| : `${outputDir}/${rel}`; | |||
|
|
|||
There was a problem hiding this comment.
outputDir is concatenated into destination paths without normalization. If callers pass a trailing slash (e.g. .cursor/skills/ or custom/path/) or Windows-style separators, this can produce paths with // or mixed separators, which may break fileExists checks (especially for virtual FS implementations) and lead to duplicate writes. Consider normalizing outputDir (convert \\ → /, trim trailing /) and building dest via a join helper to ensure consistent paths.
…llsDir handling Co-authored-by: Copilot <copilot@github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let skillsDir = argv.skillsDir as string | undefined; | ||
|
|
||
| if (!skillsDir) { | ||
| let agent = argv.agent as AIAgentTarget | undefined; | ||
|
|
||
| if (!agent) { | ||
| agent = await InquirerWrapper.select({ | ||
| message: "Which AI agent are you using?", | ||
| choices: AI_AGENT_CHOICES | ||
| }) as AIAgentTarget; | ||
| } | ||
|
|
||
| GoogleAnalytics.post({ | ||
| t: "event", | ||
| ec: "$ig ai-config", | ||
| ea: `agent: ${agent}` | ||
| }); | ||
|
|
||
| skillsDir = getSkillsDir(agent); | ||
| } |
There was a problem hiding this comment.
When --skills-dir is provided, the handler skips posting the $ig ai-config analytics event entirely (only the screenview is sent). If telemetry for this command is expected to be consistent regardless of flags, move the event post outside the if (!skillsDir) block and avoid sending the raw custom path (e.g., record a boolean like customSkillsDir: true and/or the selected agent when present).
| const SKILLS_DIR_TEMPLATE = "__dot__claude/skills"; | ||
|
|
||
| export type AIAgentTarget = "claude" | "copilot" | "cursor" | "codex" | "windsurf" | "gemini" | "junie" | "generic"; | ||
|
|
||
| export const AI_AGENT_SKILLS_DIRS: Record<AIAgentTarget, string> = { |
There was a problem hiding this comment.
SKILLS_DIR_TEMPLATE is still hard-coded to __dot__claude/skills, but the new name no longer reflects that Claude-specific meaning. This is likely to confuse future maintainers (it reads like a generic template path). Consider renaming it back to something Claude-specific (e.g., CLAUDE_SKILLS_DIR_TEMPLATE) or to a name that clarifies it's the source template location used for fallback discovery.
| describe("handler", () => { | ||
| it("posts analytics and calls configure", async () => { | ||
| it("prompts for agent when neither --agent nor --skills-dir is provided", async () => { | ||
| App.container.set(FS_TOKEN, createMockFs()); | ||
| spyOn(InquirerWrapper, "select").and.returnValue(Promise.resolve("claude")); | ||
|
|
||
| await aiConfig.default.handler({ _: ["ai-config"], $0: "ig" }); | ||
|
|
||
| expect(Util.log).toHaveBeenCalledWith(jasmine.stringContaining("MCP servers configured")); | ||
| expect(InquirerWrapper.select).toHaveBeenCalledWith(jasmine.objectContaining({ | ||
| message: "Which AI agent are you using?" | ||
| })); | ||
| expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "screenview", cd: "MCP" })); | ||
| expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ec: "$ig ai-config" })); | ||
| expect(GoogleAnalytics.post).toHaveBeenCalledWith(jasmine.objectContaining({ t: "event", ea: "agent: claude" })); | ||
| }); |
There was a problem hiding this comment.
This test asserts that ig ai-config (no flags) prompts for an agent. That behavior contradicts the PR description/usage examples stating no-arg should default to .claude/skills for backward compatibility, and it makes the command non-scriptable in non-interactive contexts. If the intended behavior is to default to Claude without prompting, update the handler and adjust this test to verify the non-interactive default instead of expecting a prompt.
| if (!skillsDir) { | ||
| let agent = argv.agent as AIAgentTarget | undefined; | ||
|
|
||
| if (!agent) { | ||
| agent = await InquirerWrapper.select({ | ||
| message: "Which AI agent are you using?", | ||
| choices: AI_AGENT_CHOICES | ||
| }) as AIAgentTarget; | ||
| } |
There was a problem hiding this comment.
The CLI now prompts via Inquirer when neither --agent nor --skills-dir is provided. This changes the documented/expected no-arg behavior (PR description says ig ai-config should default to .claude/skills) and can hang in non-interactive environments (CI/scripts). Consider defaulting agent to "claude" when no flags are provided (preserving prior behavior) and only prompting when an explicit interactive mode is requested or when running in a TTY and the user opted into prompting.
… Google Analytics event tracking Co-authored-by: Copilot <copilot@github.com>
| .option("agent", { | ||
| alias: "a", | ||
| describe: "AI agent to configure skills for (determines the target skills directory)", | ||
| choices: AI_AGENT_CHOICES, | ||
| type: "string" | ||
| }) | ||
| .option("skills-dir", { | ||
| alias: "d", | ||
| describe: "Custom skills directory path (overrides --agent)", | ||
| type: "string" | ||
| }), |
There was a problem hiding this comment.
Can we roll that option into one thing? Kinda odd to be able to pass both - like --agent claude --dir .github/skills
… updating skills directory handling Co-authored-by: Copilot <copilot@github.com>
…and remove skillsDir option Co-authored-by: Copilot <copilot@github.com>
…o copilot/update-ai-config-agent-agnostic
Co-authored-by: Copilot <copilot@github.com>
ig ai-confighard-coded.claude/skillsas the only skill destination. The CLI positions this as general AI tooling, but the implementation only supported Claude Code.Core (
packages/core/util/ai-skills.ts)AIAgentTargettype:claude | copilot | cursor | codex | windsurf | gemini | junie | genericAI_AGENT_SKILLS_DIRSmapping each agent to its conventional skills directorygetSkillsDir(target?)resolvercopyAISkillsToProject(skillsDir?)now accepts an optional output directory; defaults to.claude/skillsfor backward compatCLI (
packages/cli/lib/commands/ai-config.ts)--agent(-a) — pick a known agent target--skills-dir(-d) — arbitrary path, overrides--agentconfigureSkills()andconfigure()thread through the resolved directoryAngular Schematics (
packages/ng-schematics/src/cli-config)ai-configschematic now acceptsagentandskillsDiroptions, matching the CLI command behaviorai-config-schema.jsonwithagent(enum of supported targets) andskillsDir(custom path) propertiescollection.jsonto reference the schemaaddAIConfig()resolvesskillsDirfromagent/skillsDiroptions (skillsDiroverridesagent)aiConfig()passes the resolved directory through tocopyAISkillsToProject()Agent → directory mapping
claude.claude/skillscopilot.github/skillscursor.cursor/skillscodex.codex/skillswindsurf.windsurf/skillsgemini.gemini/skillsjunie.junie/skillsgeneric.agents/skillsUsage
Tests
15 new core specs covering agent-aware destinations,
getSkillsDir()for each target, custom paths, and theAI_AGENT_SKILLS_DIRSshape. 6 new schematic specs verifying agent/skillsDir option resolution and passthrough. Existing tests pass unchanged — no-arg calls still write to.claude/skills.