fix(copilot): generate .prompt.md file for VS Code Copilot slash commands (#328)

* fix(copilot): generate .prompt.md file for VS Code Copilot slash commands

VS Code Copilot requires reusable prompts to be files named
`<name>.prompt.md` directly in `.github/prompts/`. The previous
config generated `.github/prompts/ui-ux-pro-max/PROMPT.md` (a folder
with PROMPT.md inside), which is not recognized as a slash command.

Changes:
- copilot.json: set skillPath to 'prompts', filename to
  'ui-ux-pro-max.prompt.md', add dataPath for data/scripts location
- copilot.json: use 'mode: agent' frontmatter (VS Code format) instead
  of 'name' field
- template.ts: add dataPath support so data/scripts go to a separate
  directory from the prompt file when configured
- template.ts: rewrite hardcoded script paths to platform-specific
  scriptPath for platforms that differ from the default
- uninstall.ts: handle copilot-specific file layout during uninstall

After this fix, `uipro init --ai copilot` produces:
  .github/prompts/ui-ux-pro-max.prompt.md  (slash command file)
  .github/prompts/ui-ux-pro-max/data/      (search database)
  .github/prompts/ui-ux-pro-max/scripts/   (search engine)

* fix(copilot): apply prompt-file layout fix to canonical source template

Address review feedback on PR #328:
- Update src/ui-ux-pro-max/templates/platforms/copilot.json (source of
  truth) with the same .prompt.md layout fix that was previously applied
  only to the packaged CLI asset copy
- Sync cli/assets/templates/platforms/copilot.json byte-for-byte from
  the source template (also fixes stale '15 technology stacks' count;
  there are 16 stack CSVs)

Smoke check (uipro init --ai copilot --offline):
  .github/prompts/ui-ux-pro-max.prompt.md  (slash command, mode: agent)
  .github/prompts/ui-ux-pro-max/data/      (search database)
  .github/prompts/ui-ux-pro-max/scripts/   (search engine)
uninstall --ai copilot removes both the prompt file and data directory.

---------

Co-authored-by: vkayata <volkan.kayatas@mercedes-benz.com>
This commit is contained in:
vkayatas 2026-07-31 18:02:31 +02:00 committed by GitHub
parent ec1f2a9027
commit b0ebb1797e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 57 additions and 15 deletions

View File

@ -4,13 +4,14 @@
"installType": "full",
"folderStructure": {
"root": ".github",
"skillPath": "prompts/ui-ux-pro-max",
"filename": "PROMPT.md"
"skillPath": "prompts",
"filename": "ui-ux-pro-max.prompt.md",
"dataPath": "prompts/ui-ux-pro-max"
},
"scriptPath": "prompts/ui-ux-pro-max/scripts/search.py",
"frontmatter": {
"name": "ui-ux-pro-max",
"description": "Comprehensive design guide for web, mobile, and desktop applications. Contains 67 styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 22 technology stacks."
"description": "Comprehensive design guide for web, mobile, and desktop applications. Contains 67 styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 22 technology stacks.",
"mode": "agent"
},
"sections": {
"quickReference": false

View File

@ -30,9 +30,18 @@ async function removeSkillDir(baseDir: string, aiType: ConcreteAIType): Promise<
// `.github/prompts/`, kiro under `.kiro/steering/`. Also clean the legacy
// `<folder>/skills/` layout (incl. `.shared/`) so older installs are removed.
const parents = new Set<string>();
// Standalone skill files to remove (platforms with a dataPath keep the
// rendered skill file apart from the data directory — e.g. copilot's
// `.github/prompts/ui-ux-pro-max.prompt.md`).
const skillFiles = new Set<string>();
try {
const { folderStructure } = await loadPlatformConfig(aiType);
parents.add(join(folderStructure.root, dirname(folderStructure.skillPath)));
if (folderStructure.dataPath) {
skillFiles.add(join(folderStructure.root, folderStructure.skillPath, folderStructure.filename));
parents.add(join(folderStructure.root, dirname(folderStructure.dataPath)));
} else {
parents.add(join(folderStructure.root, dirname(folderStructure.skillPath)));
}
} catch {
// No platform config — fall back to the legacy folders below.
}
@ -40,6 +49,18 @@ async function removeSkillDir(baseDir: string, aiType: ConcreteAIType): Promise<
parents.add(join(folder, 'skills'));
}
for (const skillFile of skillFiles) {
const filePath = join(baseDir, skillFile);
try {
await stat(filePath);
await rm(filePath, { force: true });
removed.push(skillFile.replaceAll('\\', '/'));
} catch (err: unknown) {
// Skip non-existent files; re-throw permission or other errors
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err;
}
}
for (const parent of parents) {
for (const name of skillNames) {
const skillDir = join(baseDir, parent, name);

View File

@ -31,6 +31,7 @@ export interface PlatformConfig {
root: string;
skillPath: string;
filename: string;
dataPath?: string;
};
scriptPath: string;
frontmatter: Record<string, string> | null;

View File

@ -21,6 +21,7 @@ export interface PlatformConfig {
root: string;
skillPath: string;
filename: string;
dataPath?: string;
};
scriptPath: string;
frontmatter: Record<string, string> | null;
@ -151,12 +152,22 @@ export async function renderSkillFile(config: PlatformConfig, isGlobal = false):
.replace(/\{\{SKILL_OR_WORKFLOW\}\}/g, config.skillOrWorkflow)
.replace(/\{\{QUICK_REFERENCE\}\}/g, quickRefWithNewline);
// Rewrite the hardcoded default script path to the platform-specific path
const defaultScriptPath = 'skills/ui-ux-pro-max/scripts/search.py';
if (config.scriptPath !== defaultScriptPath) {
content = content.replace(
new RegExp(defaultScriptPath.replace(/\//g, '\\/'), 'g'),
config.scriptPath
);
}
// For global install, rewrite relative script paths to absolute ~/root/ paths
if (isGlobal) {
const globalPrefix = `~/${config.folderStructure.root}/`;
// Match any platform's script path pattern (skills/, prompts/, steering/, etc.)
content = content.replace(
/python3 skills\//g,
`python3 ${globalPrefix}skills/`
new RegExp(`python3 ${config.scriptPath.replace(/\//g, '\\/')}`, 'g'),
`python3 ${globalPrefix}${config.scriptPath}`
);
}
@ -272,17 +283,24 @@ export async function generatePlatformFiles(
await writeFile(skillFilePath, skillContent, 'utf-8');
createdFolders.push(config.folderStructure.root);
// Copy data and scripts into the skill directory (self-contained)
await copyDataAndScripts(skillDir);
// Copy data and scripts into the data directory (may differ from skill file location)
const dataDir = config.folderStructure.dataPath
? join(effectiveDir, config.folderStructure.root, config.folderStructure.dataPath)
: skillDir;
await mkdir(dataDir, { recursive: true });
await copyDataAndScripts(dataDir);
// Install the sibling sub-skills (banner-design, brand, design, ...) next to
// the orchestrator so all 7 skills are delivered. The skills parent is the
// orchestrator's parent dir (skills/ for most platforms, prompts/ for
// copilot, steering/ for kiro) — derived, not hardcoded.
// copilot, steering/ for kiro) — derived, not hardcoded. For platforms with
// a separate dataPath (copilot), the orchestrator's data dir is the anchor.
const skillsParentDir = join(
effectiveDir,
config.folderStructure.root,
dirname(config.folderStructure.skillPath)
config.folderStructure.dataPath
? dirname(config.folderStructure.dataPath)
: dirname(config.folderStructure.skillPath)
);
await copySubSkills(skillsParentDir, force);

View File

@ -4,13 +4,14 @@
"installType": "full",
"folderStructure": {
"root": ".github",
"skillPath": "prompts/ui-ux-pro-max",
"filename": "PROMPT.md"
"skillPath": "prompts",
"filename": "ui-ux-pro-max.prompt.md",
"dataPath": "prompts/ui-ux-pro-max"
},
"scriptPath": "prompts/ui-ux-pro-max/scripts/search.py",
"frontmatter": {
"name": "ui-ux-pro-max",
"description": "Comprehensive design guide for web, mobile, and desktop applications. Contains 67 styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 22 technology stacks."
"description": "Comprehensive design guide for web, mobile, and desktop applications. Contains 67 styles, 161 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 22 technology stacks.",
"mode": "agent"
},
"sections": {
"quickReference": false