Skill: Skill Catalog Generator
SKILL.md
Skill: Skill Catalog Generator
Generate dynamic skill catalogs with network diagrams to visualize Alex's capabilities and track learning progress.
Purpose
This skill enables Alex to:
- Generate skill catalogs — List all installed skills with categories, inheritance, and purposes
- Create network diagrams — Mermaid flowcharts showing skill relationships
- Track learning progress — Show which skills have been activated, user-created skills
- Post-migration reporting — Display what skills are available after upgrade
Activation Triggers
- "show my skills" / "what skills do I have"
- "skill catalog" / "generate catalog"
- "skill network" / "show skill diagram"
- "learning progress" / "what have I learned"
- Post-migration completion
/skillsslash command
Output Format
Catalog Structure
# My Alex Skills
> Generated: {timestamp}
> Total Skills: {count} ({system} system, {user} user-created)
## Summary
| Category | Count | Active |
| -------- | ----- | ------ |
| 🧠 Cognitive | 8 | 6 |
| 🔧 Engineering | 7 | 7 |
| ... | ... | ... |
## Skills by Category
### 🧠 Cognitive & Learning
| Skill | Type | Status | Last Used |
| ----- | ---- | ------ | --------- |
| cognitive-load | system | ✅ active | 2026-01-30 |
| my-project-patterns | user | ✅ active | 2026-01-29 |
| ... | ... | ... | ... |
## Network Diagram
{mermaid diagram}
## User-Created Skills
| Skill | Created | Connections |
| ----- | ------- | ----------- |
| my-project-patterns | 2026-01-15 | 3 |
| team-conventions | 2026-01-20 | 1 |
Generation Algorithm
Step 1: Scan Skills Directory
interface SkillInfo {
name: string;
category: string;
inheritance: 'inheritable' | 'master-only' | 'heir:vscode' | 'heir:m365' | 'user';
purpose: string;
hasSynapses: boolean;
connectionCount: number;
lastModified: Date;
isUserCreated: boolean;
isTemporary: boolean; // From synapses.json "temporary": true
removeAfter?: string; // From synapses.json "removeAfter"
staleProne: boolean; // True if skill depends on rapidly changing tech
}
async function scanSkills(skillsPath: string): Promise<SkillInfo[]> {
const skills: SkillInfo[] = [];
const folders = await fs.readdir(skillsPath);
for (const folder of folders) {
const skillPath = path.join(skillsPath, folder);
const skillMd = path.join(skillPath, 'SKILL.md');
const synapsesJson = path.join(skillPath, 'synapses.json');
if (!await fs.pathExists(skillMd)) continue;
const content = await fs.readFile(skillMd, 'utf8');
const synapses = await fs.pathExists(synapsesJson)
? await fs.readJson(synapsesJson)
: null;
skills.push({
name: folder,
category: extractCategory(content),
inheritance: extractInheritance(content, synapses),
purpose: extractPurpose(content),
hasSynapses: !!synapses,
connectionCount: synapses ? Object.keys(synapses.connections || {}).length : 0,
lastModified: (await fs.stat(skillMd)).mtime,
isUserCreated: !SYSTEM_SKILLS.includes(folder),
isTemporary: synapses?.temporary === true,
removeAfter: synapses?.removeAfter,
});
}
return skills;
}
Step 2: Categorize Skills
const CATEGORIES = {
'cognitive': { emoji: '🧠', skills: ['cognitive-load', 'learning-psychology', 'appropriate-reliance', 'bootstrap-learning', 'meditation', 'meditation-facilitation', 'knowledge-synthesis', 'global-knowledge'] },
'engineering': { emoji: '🔧', skills: ['testing-strategies', 'refactoring-patterns', 'debugging-patterns', 'code-review', 'git-workflow', 'project-scaffolding', 'vscode-environment'] },
'operations': { emoji: '🚨', skills: ['error-recovery-patterns', 'root-cause-analysis', 'incident-response', 'release-preflight'] },
'security': { emoji: '🔐', skills: ['privacy-responsible-ai', 'microsoft-sfi'] },
'documentation': { emoji: '📝', skills: ['writing-publication', 'markdown-mermaid', 'lint-clean-markdown', 'ascii-art-alignment', 'llm-model-selection'] },
'visual': { emoji: '🎨', skills: ['svg-graphics', 'image-handling'] },
'architecture': { emoji: '🏗️', skills: ['architecture-refinement', 'architecture-health', 'self-actualization', 'heir-curation'] },
'vscode': { emoji: '💻', skills: ['vscode-extension-patterns', 'chat-participant-patterns'] },
'm365': { emoji: '☁️', skills: ['m365-agent-debugging', 'teams-app-patterns'] },
'user': { emoji: '👤', skills: [] }, // Dynamically populated
};
Step 3: Generate Mermaid Diagram (High Fidelity)
The diagram generator must produce output matching the baseline catalog's richness.
Connection Schema in synapses.json
interface Connection {
target: string; // Target skill name
type: string; // enables, applies, extends, complements, triggers, curates
strength: number; // 0.0-1.0
bidirectional?: boolean; // true = <--> arrow
weak?: boolean; // true = -.-> dashed arrow
}
Arrow Types
| Condition | Arrow | Meaning |
|---|---|---|
bidirectional: true |
<--> |
Mutual reinforcement |
weak: true OR strength < 0.5 |
-.-> |
Optional/weak link |
| Default | --> |
Direct dependency |
Multi-Target Syntax
When a skill connects to multiple targets, use Mermaid's multi-target syntax:
SCG --> MM & AH & KS
Not individual lines (reduces diagram clutter).
function generateNetworkDiagram(skills: SkillInfo[]): string {
const lines: string[] = [
'```mermaid',
"%%{init: {'theme': 'base', 'themeVariables': { 'lineColor': '#666', 'primaryColor': '#e8f4f8', 'primaryBorderColor': '#0969da'}}}%%",
'flowchart LR',
];
// Group by category
for (const [category, config] of Object.entries(CATEGORIES)) {
const categorySkills = skills.filter(s => config.skills.includes(s.name) || (category === 'user' && s.isUserCreated));
if (categorySkills.length === 0) continue;
lines.push(` subgraph ${capitalize(category)}["${config.emoji} ${capitalize(category)}"]`);
for (const skill of categorySkills) {
const abbrev = toAbbreviation(skill.name);
lines.push(` ${abbrev}[${skill.name}]`);
}
lines.push(' end');
}
// Add connections from synapses - GROUP by source for multi-target syntax
const connectionGroups = new Map<string, { targets: string[], arrow: string }>();
for (const skill of skills) {
if (!skill.hasSynapses) continue;
const synapses = loadSynapses(skill.name);
const connections = normalizeConnections(synapses.connections);
for (const conn of connections) {
const sourceAbbrev = toAbbreviation(skill.name);
const targetAbbrev = toAbbreviation(conn.target);
// Determine arrow type
let arrow = '-->';
if (conn.bidirectional) {
arrow = '<-->';
} else if (conn.weak || (conn.strength && conn.strength < 0.5)) {
arrow = '-.->';
}
// Group connections by source+arrow for multi-target syntax
const key = `${sourceAbbrev}|${arrow}`;
if (!connectionGroups.has(key)) {
connectionGroups.set(key, { targets: [], arrow });
}
connectionGroups.get(key)!.targets.push(targetAbbrev);
}
}
// Output grouped connections using multi-target syntax
for (const [key, group] of connectionGroups) {
const source = key.split('|')[0];
if (group.targets.length === 1) {
lines.push(` ${source} ${group.arrow} ${group.targets[0]}`);
} else {
// Multi-target: A --> B & C & D
lines.push(` ${source} ${group.arrow} ${group.targets.join(' & ')}`);
}
}
// Add styling - Inheritance colors
lines.push('');
lines.push(' %% Styling - Inheritance');
lines.push(' classDef master fill:#fff3cd,stroke:#856404');
lines.push(' classDef vscode fill:#e1f0ff,stroke:#0969da');
lines.push(' classDef m365 fill:#e6f4ea,stroke:#1a7f37');
lines.push(' classDef inheritable fill:#e0f7fa,stroke:#00838f');
lines.push(' classDef user fill:#e6ffe6,stroke:#2da02d');
lines.push('');
lines.push(' %% Styling - Staleness (dashed border)');
lines.push(' classDef stale stroke-dasharray:5 5,stroke-width:2px');
lines.push('');
lines.push(' %% Styling - Temporary (purple dashed)');
lines.push(' classDef temp fill:#f3e8ff,stroke:#7c3aed,stroke-dasharray:5 5');
// Apply classes based on inheritance
const masterSkills = skills.filter(s => s.inheritance === 'master-only').map(s => toAbbreviation(s.name));
const vscodeSkills = skills.filter(s => s.inheritance === 'heir:vscode').map(s => toAbbreviation(s.name));
const m365Skills = skills.filter(s => s.inheritance === 'heir:m365').map(s => toAbbreviation(s.name));
const inheritableSkills = skills.filter(s => s.inheritance === 'inheritable' && !s.isTemporary).map(s => toAbbreviation(s.name));
const userSkills = skills.filter(s => s.isUserCreated).map(s => toAbbreviation(s.name));
const tempSkills = skills.filter(s => s.isTemporary).map(s => toAbbreviation(s.name));
const staleSkills = skills.filter(s => s.staleProne).map(s => toAbbreviation(s.name));
// Apply classes to nodes (classDef alone does nothing without this!)
if (masterSkills.length > 0) lines.push(` class ${masterSkills.join(',')} master`);
if (vscodeSkills.length > 0) lines.push(` class ${vscodeSkills.join(',')} vscode`);
if (m365Skills.length > 0) lines.push(` class ${m365Skills.join(',')} m365`);
if (inheritableSkills.length > 0) lines.push(` class ${inheritableSkills.join(',')} inheritable`);
if (userSkills.length > 0) lines.push(` class ${userSkills.join(',')} user`);
if (tempSkills.length > 0) lines.push(` class ${tempSkills.join(',')} temp`);
if (staleSkills.length > 0) lines.push(` class ${staleSkills.join(',')} stale`);
lines.push('```');
return lines.join('\n');
}
// Normalize connections to array format (supports both object and array)
function normalizeConnections(connections: any): Connection[] {
if (Array.isArray(connections)) {
return connections;
}
// Object format: { "target-name": { weight, relationship } }
return Object.entries(connections).map(([target, data]: [string, any]) => ({
target,
type: data.relationship || data.type || 'enables',
strength: data.weight || data.strength || 0.5,
bidirectional: data.bidirectional || false,
weak: data.weak || false,
}));
}
Diagram Legend
Include this legend in generated catalogs:
### Legend
| Color | Inheritance |
| ----- | ----------- |
| 🟨 Yellow | Master-only |
| 🟦 Blue | VS Code heir |
| 🟩 Green | M365 heir |
| 🟪 Purple (dashed) | Temporary |
| 🧊 Cyan | Inheritable |
| Border | Meaning |
| ------ | ------- |
| ┅ Dashed | Staleness-prone or temporary |
| ── Solid | Standard skill |
| Arrow | Meaning |
| ----- | ------- |
| → Solid | Strong connection (weight > 0.7) |
| ⇢ Dashed | Weak connection (weight ≤ 0.7) |
Step 4: Generate Full Catalog
async function generateSkillCatalog(rootPath: string): Promise<string> {
const skillsPath = path.join(rootPath, '.github', 'skills');
const skills = await scanSkills(skillsPath);
const systemSkills = skills.filter(s => !s.isUserCreated);
const userSkills = skills.filter(s => s.isUserCreated);
const catalog = `# My Alex Skills
> Generated: ${new Date().toISOString().split('T')[0]}
> Total Skills: ${skills.length} (${systemSkills.length} system, ${userSkills.length} user-created)
## Summary
| Category | Count |
| -------- | ----- |
${generateCategorySummary(skills)}
## Skills by Category
${generateSkillTables(skills)}
## Network Diagram
${generateNetworkDiagram(skills)}
${userSkills.length > 0 ? generateUserSkillsSection(userSkills) : ''}
---
*Catalog generated by Alex Skill Catalog Generator*
`;
return catalog;
}
VS Code Integration
Command: Alex: Show Skill Catalog
vscode.commands.registerCommand('alex.showSkillCatalog', async () => {
const rootPath = getWorkspaceRoot();
const catalog = await generateSkillCatalog(rootPath);
// Create virtual document
const uri = vscode.Uri.parse('alex-catalog://skills/catalog.md');
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: true });
// Or save to file
const catalogPath = path.join(rootPath, '.github', 'SKILL-CATALOG.md');
await fs.writeFile(catalogPath, catalog);
vscode.window.showInformationMessage(
`Skill catalog generated with ${skills.length} skills`,
'Open Catalog'
).then(choice => {
if (choice === 'Open Catalog') {
vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.file(catalogPath));
}
});
});
Post-Migration Hook
// In migration completion
async function showPostMigrationCatalog(rootPath: string, migrationReport: MigrationReport) {
const catalog = await generateSkillCatalog(rootPath);
// Add migration-specific header
const header = `# 🎉 Migration Complete!
## What Was Migrated
- **Skills restored:** ${migrationReport.skills.length}
- **Domain knowledge:** ${migrationReport.dk.length} files
- **User profile:** ${migrationReport.profileRestored ? '✅' : '❌'}
---
`;
const fullCatalog = header + catalog;
await showCatalogDocument(fullCatalog);
}
User-Created Skills Tracking
Identifying User Skills
A skill is user-created if:
- Not in the
SYSTEM_SKILLSlist (shipped with extension) - Or has
"userCreated": truein synapses.json - Or was created after initial installation (compare to manifest)
Learning Progress Metrics
interface LearningProgress {
totalSkills: number;
systemSkills: number;
userSkills: number;
skillsWithConnections: number;
totalConnections: number;
mostConnectedSkill: string;
recentlyUsedSkills: string[]; // From episodic records
suggestedNextSkills: string[]; // Based on current connections
}
Output Locations
| Trigger | Output |
|---|---|
| Command | Preview in VS Code |
| Slash command | Chat response |
| Post-migration | Modal + saved file |
/skills |
Inline in chat |
Related Skills
markdown-mermaid— Diagram generationarchitecture-health— Skill validationknowledge-synthesis— Pattern extraction
Inheritance
Inheritable — All heirs can generate catalogs for their installed skills.