From af88cbb42af0f6e9f2828f3bd24cd626ad6bc481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quy=E1=BB=81n?= Date: Mon, 1 Jun 2026 23:07:40 +0700 Subject: [PATCH] fix: flatten nested skills to - so Claude Code discovers them Claude Code scans skills only one level deep and uses the folder name as the slash-command name; it does not recurse into nested group folders. The installer symlinked/copied whole group folders (~/.claude/skills/fix -> assets/skills/fix), so all 21 namespaced skills were invisible (/fix:root-cause returned "No commands match"). - listSkillsNested: flatten / to - (hyphen, since ':' is an invalid filename character on Windows); skip non-dir entries (.DS_Store) - install/native.ts: install flat single-level skill entries for Claude - install/skill-editor.ts: same flattening for Codex/Antigravity skill folders - uninstall.ts: include flattened names in managed-names so they get removed - docs: switch to /- and correct the skill-discovery explanation --- CLAUDE.md | 100 +++++++++++++-------------- README.md | 76 ++++++++++---------- src/commands/install/native.ts | 12 ++-- src/commands/install/skill-editor.ts | 13 ++-- src/commands/uninstall.ts | 11 ++- src/utils/symlink.ts | 16 +++-- 6 files changed, 122 insertions(+), 106 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 11e4358..19888db 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,34 +57,34 @@ moicle/ │ │ ├── bootstrap.md │ │ ├── brainstorm.md │ │ └── marketing.md -│ └── skills/ # Nested namespace: /group:action +│ └── skills/ # nested in repo, flattened to /group-action on install │ ├── feature/ -│ │ ├── new/ # /feature:new -│ │ ├── refactor/ # /feature:refactor -│ │ ├── api/ # /feature:api -│ │ └── deprecate/ # /feature:deprecate +│ │ ├── new/ # /feature-new +│ │ ├── refactor/ # /feature-refactor +│ │ ├── api/ # /feature-api +│ │ └── deprecate/ # /feature-deprecate │ ├── fix/ -│ │ ├── hotfix/ # /fix:hotfix -│ │ ├── root-cause/ # /fix:root-cause -│ │ ├── incident/ # /fix:incident -│ │ └── pr-comment/ # /fix:pr-comment +│ │ ├── hotfix/ # /fix-hotfix +│ │ ├── root-cause/ # /fix-root-cause +│ │ ├── incident/ # /fix-incident +│ │ └── pr-comment/ # /fix-pr-comment │ ├── review/ -│ │ ├── branch/ # /review:branch -│ │ ├── pr/ # /review:pr -│ │ ├── architect/ # /review:architect -│ │ └── tdd/ # /review:tdd +│ │ ├── branch/ # /review-branch +│ │ ├── pr/ # /review-pr +│ │ ├── architect/ # /review-architect +│ │ └── tdd/ # /review-tdd │ ├── research/ -│ │ ├── web/ # /research:web -│ │ ├── spike/ # /research:spike -│ │ └── onboarding/ # /research:onboarding +│ │ ├── web/ # /research-web +│ │ ├── spike/ # /research-spike +│ │ └── onboarding/ # /research-onboarding │ ├── docs/ -│ │ ├── write/ # /docs:write -│ │ └── sync/ # /docs:sync +│ │ ├── write/ # /docs-write +│ │ └── sync/ # /docs-sync │ └── marketing/ -│ ├── content/ # /marketing:content -│ ├── seo-blog/ # /marketing:seo-blog -│ ├── logo/ # /marketing:logo -│ └── video/ # /marketing:video +│ ├── content/ # /marketing-content +│ ├── seo-blog/ # /marketing-seo-blog +│ ├── logo/ # /marketing-logo +│ └── video/ # /marketing-video ├── package.json └── README.md ``` @@ -144,54 +144,54 @@ Comprehensive marketing plan wizard - combines logo design, video content, and c ## Skills (21) -Skills are nested under 5 namespaces. Folder path becomes the trigger: `skills///SKILL.md` → `/:`. Old trigger phrases stay in `description` so Claude still auto-invokes the right skill from natural language. +Skills are organized into 6 groups in this repo as `skills///SKILL.md`. Claude Code only scans skills **one level deep** and uses the **folder name** as the slash-command name — it does not recurse into nested group folders. So the installer **flattens** each nested skill to a single-level `-` entry: `skills/fix/root-cause/` → `~/.claude/skills/fix-root-cause/` → `/fix-root-cause`. A hyphen is used (not a colon) because `:` is an invalid filename character on Windows. Old trigger phrases stay in `description` so Claude still auto-invokes the right skill from natural language. See `README.md` for the decision matrix when multiple skills overlap. -### `/feature:*` — Build & Change +### `/feature-*` — Build & Change | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/feature:new` | "implement feature", "add feature", "build feature", "create feature" | -| `/feature:refactor` | "refactor", "clean up", "improve code", "restructure", "migrate to ddd" | -| `/feature:api` | "integrate api", "add endpoint", "new api", "connect api" | -| `/feature:deprecate` | "deprecate", "remove feature", "sunset", "phase out" | +| `/feature-new` | "implement feature", "add feature", "build feature", "create feature" | +| `/feature-refactor` | "refactor", "clean up", "improve code", "restructure", "migrate to ddd" | +| `/feature-api` | "integrate api", "add endpoint", "new api", "connect api" | +| `/feature-deprecate` | "deprecate", "remove feature", "sunset", "phase out" | -### `/fix:*` — Bugs & Incidents +### `/fix-*` — Bugs & Incidents | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/fix:hotfix` | "fix bug", "hotfix", "urgent fix", "production issue" | -| `/fix:root-cause` | "deep debug", "trace bug", "find root cause", "hard bug" | -| `/fix:incident` | "incident", "outage", "production down", "service down" | -| `/fix:pr-comment` | "fix pr comment", "fix review comment", "address pr feedback" | +| `/fix-hotfix` | "fix bug", "hotfix", "urgent fix", "production issue" | +| `/fix-root-cause` | "deep debug", "trace bug", "find root cause", "hard bug" | +| `/fix-incident` | "incident", "outage", "production down", "service down" | +| `/fix-pr-comment` | "fix pr comment", "fix review comment", "address pr feedback" | -### `/review:*` — Review & Quality +### `/review-*` — Review & Quality | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/review:branch` | "review changes", "review branch", "check branch", "review before pr" | -| `/review:pr` | "review pr", "check pr", "review code", "pr review" | -| `/review:architect` | "architect-review", "architecture review", "review ddd" | -| `/review:tdd` | "tdd", "test first", "test driven", "red green refactor" | +| `/review-branch` | "review changes", "review branch", "check branch", "review before pr" | +| `/review-pr` | "review pr", "check pr", "review code", "pr review" | +| `/review-architect` | "architect-review", "architecture review", "review ddd" | +| `/review-tdd` | "tdd", "test first", "test driven", "red green refactor" | -### `/research:*` — Explore & Learn +### `/research-*` — Explore & Learn | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/research:web` | "research", "tìm giải pháp", "find best practice", "so sánh giải pháp" | -| `/research:spike` | "spike", "prototype", "poc", "explore" | -| `/research:onboarding` | "explain codebase", "onboard", "new to project", "understand project" | +| `/research-web` | "research", "tìm giải pháp", "find best practice", "so sánh giải pháp" | +| `/research-spike` | "spike", "prototype", "poc", "explore" | +| `/research-onboarding` | "explain codebase", "onboard", "new to project", "understand project" | -### `/docs:*` — Project Documentation +### `/docs-*` — Project Documentation | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/docs:write` | "document", "generate docs", "write docs" | -| `/docs:sync` | "sync docs", "sync documentation", "doc sync" | +| `/docs-write` | "document", "generate docs", "write docs" | +| `/docs-sync` | "sync docs", "sync documentation", "doc sync" | -### `/marketing:*` — Brand & Content (wrapped by `/marketing` command) +### `/marketing-*` — Brand & Content (wrapped by `/marketing` command) | Skill | Trigger phrases (auto-invoke) | |-------|------------------------------| -| `/marketing:content` | "write content", "content strategy", "content plan", "newsletter" | -| `/marketing:seo-blog` | "write seo blog", "seo blog", "evergreen post", "compare post", "blog for AI" | -| `/marketing:logo` | "design logo", "create logo", "brand identity" | -| `/marketing:video` | "create video", "video content", "video script" | +| `/marketing-content` | "write content", "content strategy", "content plan", "newsletter" | +| `/marketing-seo-blog` | "write seo blog", "seo blog", "evergreen post", "compare post", "blog for AI" | +| `/marketing-logo` | "design logo", "create logo", "brand identity" | +| `/marketing-video` | "create video", "video content", "video script" | ## Development diff --git a/README.md b/README.md index 70b1b05..3619316 100644 --- a/README.md +++ b/README.md @@ -118,58 +118,58 @@ moicle install --target antigravity --global ### Skills (21) -Skills are grouped into 6 namespaces. Type `/:` in Claude Code to see all skills in a group. +Skills are grouped by a `-` prefix. Type `/-` then `Tab` in Claude Code to see all skills in a group. -**`/feature:*` — Build & Change** +**`/feature-*` — Build & Change** | Skill | When to use | |-------|-------------| -| `/feature:new` | Build a new feature end-to-end following DDD | -| `/feature:refactor` | Restructure existing module to DDD or improve internals | -| `/feature:api` | Add a new endpoint or integrate an external API | -| `/feature:deprecate` | Safely sunset a feature, API, or module | +| `/feature-new` | Build a new feature end-to-end following DDD | +| `/feature-refactor` | Restructure existing module to DDD or improve internals | +| `/feature-api` | Add a new endpoint or integrate an external API | +| `/feature-deprecate` | Safely sunset a feature, API, or module | -**`/fix:*` — Bugs & Incidents** +**`/fix-*` — Bugs & Incidents** | Skill | When to use | |-------|-------------| -| `/fix:hotfix` | Fix a bug fast with a rollback plan | -| `/fix:root-cause` | Hard-to-trace bug that has been "fixed" multiple times | -| `/fix:incident` | Production outage / on-call workflow | -| `/fix:pr-comment` | Address review comments on an existing PR | +| `/fix-hotfix` | Fix a bug fast with a rollback plan | +| `/fix-root-cause` | Hard-to-trace bug that has been "fixed" multiple times | +| `/fix-incident` | Production outage / on-call workflow | +| `/fix-pr-comment` | Address review comments on an existing PR | -**`/review:*` — Review & Quality** +**`/review-*` — Review & Quality** | Skill | When to use | |-------|-------------| -| `/review:branch` | Self-review your branch BEFORE pushing / opening PR | -| `/review:pr` | Review someone else's open PR | -| `/review:architect` | DDD compliance check (called by `/feature:new` / `/feature:refactor`) | -| `/review:tdd` | Drive implementation with test-first discipline | +| `/review-branch` | Self-review your branch BEFORE pushing / opening PR | +| `/review-pr` | Review someone else's open PR | +| `/review-architect` | DDD compliance check (called by `/feature-new` / `/feature-refactor`) | +| `/review-tdd` | Drive implementation with test-first discipline | -**`/research:*` — Explore & Learn** +**`/research-*` — Explore & Learn** | Skill | When to use | |-------|-------------| -| `/research:web` | Search the web for solutions / best practices | -| `/research:spike` | Time-boxed prototype to learn / decide | -| `/research:onboarding` | Get up to speed on a new codebase | +| `/research-web` | Search the web for solutions / best practices | +| `/research-spike` | Time-boxed prototype to learn / decide | +| `/research-onboarding` | Get up to speed on a new codebase | -**`/docs:*` — Project Documentation** +**`/docs-*` — Project Documentation** | Skill | When to use | |-------|-------------| -| `/docs:write` | Author docs manually (README / API / ARCH / CONTRIB) | -| `/docs:sync` | Auto-generate structured docs from codebase with review loop | +| `/docs-write` | Author docs manually (README / API / ARCH / CONTRIB) | +| `/docs-sync` | Auto-generate structured docs from codebase with review loop | -**`/marketing:*` — Brand & Content** (wrapped by the `/marketing` command) +**`/marketing-*` — Brand & Content** (wrapped by the `/marketing` command) | Skill | When to use | |-------|-------------| -| `/marketing:content` | Multi-post content strategy (pillars, calendar, channels) | -| `/marketing:seo-blog` | Write ONE evergreen blog post optimized for Search + AI tools | -| `/marketing:logo` | Logo + brand identity specification | -| `/marketing:video` | Video script, storyboard, production plan | +| `/marketing-content` | Multi-post content strategy (pillars, calendar, channels) | +| `/marketing-seo-blog` | Write ONE evergreen blog post optimized for Search + AI tools | +| `/marketing-logo` | Logo + brand identity specification | +| `/marketing-video` | Video script, storyboard, production plan | ### Skill decision matrix @@ -177,19 +177,19 @@ When more than one skill could fit, use this matrix: | Situation | Use | Not | |-----------|-----|-----| -| Bug just happened in prod, need fix in <1h | `/fix:hotfix` | `/fix:root-cause` (too slow) | -| Bug keeps coming back after "fixes" | `/fix:root-cause` | `/fix:hotfix` (will repeat) | -| About to push / open PR | `/review:branch` | `/review:pr` (that's for others') | -| Reviewing teammate's PR | `/review:pr` | `/review:branch` (that's for own branch) | -| Want to verify DDD compliance only | `/review:architect` | `/review:pr` (broader scope) | -| Don't know the right solution yet | `/research:web` | `/research:spike` (skip if you can decide from docs) | -| Need to validate an idea by building | `/research:spike` | `/feature:new` (commit only after spike) | -| Writing README / API docs by hand | `/docs:write` | `/docs:sync` (overkill for single file) | -| Generating full docs site from codebase | `/docs:sync` | `/docs:write` (manual is slower) | +| Bug just happened in prod, need fix in <1h | `/fix-hotfix` | `/fix-root-cause` (too slow) | +| Bug keeps coming back after "fixes" | `/fix-root-cause` | `/fix-hotfix` (will repeat) | +| About to push / open PR | `/review-branch` | `/review-pr` (that's for others') | +| Reviewing teammate's PR | `/review-pr` | `/review-branch` (that's for own branch) | +| Want to verify DDD compliance only | `/review-architect` | `/review-pr` (broader scope) | +| Don't know the right solution yet | `/research-web` | `/research-spike` (skip if you can decide from docs) | +| Need to validate an idea by building | `/research-spike` | `/feature-new` (commit only after spike) | +| Writing README / API docs by hand | `/docs-write` | `/docs-sync` (overkill for single file) | +| Generating full docs site from codebase | `/docs-sync` | `/docs-write` (manual is slower) | ### Backward compatibility -Old trigger phrases still work — they're kept in the skill `description` so Claude auto-invokes the right skill when the user says e.g. "deep debug", "hotfix", "review changes". The namespace `/group:action` is the new explicit invocation form. +Old trigger phrases still work — they're kept in the skill `description` so Claude auto-invokes the right skill when the user says e.g. "deep debug", "hotfix", "review changes". The flattened name `/group-action` is the explicit invocation form. ## Architecture-First Approach diff --git a/src/commands/install/native.ts b/src/commands/install/native.ts index 6f4dfc2..1f7199d 100644 --- a/src/commands/install/native.ts +++ b/src/commands/install/native.ts @@ -14,7 +14,7 @@ import { getArchitectureDir, getClaudeDir, getFiles, - getDirs, + listSkillsNested, } from '../../utils/symlink.js'; import { printInstalled } from './print.js'; @@ -55,10 +55,14 @@ const installCommands = (targetDir: string, useSymlink: boolean): FileResult[] = const installSkills = (targetDir: string, useSymlink: boolean): FileResult[] => { ensureDir(targetDir); const skillsDir = path.join(ASSETS_DIR, 'skills'); + // Claude Code scans skills one level deep and uses the folder name as the + // slash-command name. Nested groups (skills///SKILL.md) are + // flattened to a single-level "-" entry so each becomes + // /-. const results = fs.existsSync(skillsDir) - ? getDirs(skillsDir).map((dir) => { - const target = path.join(targetDir, path.basename(dir)); - return useSymlink ? createSymlink(dir, target) : copyDir(dir, target); + ? listSkillsNested(skillsDir).map((skill) => { + const target = path.join(targetDir, skill.name); + return useSymlink ? createSymlink(skill.path, target) : copyDir(skill.path, target); }) : []; printInstalled('Skills', targetDir, results); diff --git a/src/commands/install/skill-editor.ts b/src/commands/install/skill-editor.ts index 9c04a7b..758d368 100644 --- a/src/commands/install/skill-editor.ts +++ b/src/commands/install/skill-editor.ts @@ -2,7 +2,7 @@ import chalk from 'chalk'; import path from 'path'; import fs from 'fs'; import type { FileResult, Scope } from '../../types.js'; -import { ASSETS_DIR, ensureDir, getEditorConfig, getEditorDir, getFiles, getDirs } from '../../utils/symlink.js'; +import { ASSETS_DIR, ensureDir, getEditorConfig, getEditorDir, getFiles, listSkillsNested } from '../../utils/symlink.js'; import { printSummary } from './print.js'; import { type SkillEditorTarget, @@ -41,9 +41,9 @@ const ensureSkillDir = (baseDir: string, name: string): string => { const installSkillFolder = ( sourceDir: string, targetSkillsDir: string, - target: SkillEditorTarget + target: SkillEditorTarget, + skillName: string = path.basename(sourceDir) ): FileResult => { - const skillName = path.basename(sourceDir); const targetDir = ensureSkillDir(targetSkillsDir, skillName); let status: FileResult['status'] = 'created'; @@ -111,11 +111,12 @@ const installEditorSkills = (targetDir: string, target: SkillEditorTarget): File const targetSkillsDir = path.join(targetDir, 'skills'); ensureDir(targetSkillsDir); - // 1. Skill folders → copied verbatim (paths rewritten). + // 1. Skill folders → copied verbatim (paths rewritten). Nested groups are + // flattened to a single-level "-" skill folder. const skillsDir = path.join(ASSETS_DIR, 'skills'); if (fs.existsSync(skillsDir)) { - for (const dir of getDirs(skillsDir)) { - results.push(installSkillFolder(dir, targetSkillsDir, target)); + for (const skill of listSkillsNested(skillsDir)) { + results.push(installSkillFolder(skill.path, targetSkillsDir, target, skill.name)); } } diff --git a/src/commands/uninstall.ts b/src/commands/uninstall.ts index 7b73e63..2e52cac 100644 --- a/src/commands/uninstall.ts +++ b/src/commands/uninstall.ts @@ -9,6 +9,7 @@ import { EDITOR_CONFIGS, removeItem, listItems, + listSkillsNested, getAgentsDir, getCommandsDir, getSkillsDir, @@ -43,7 +44,9 @@ const getKitFiles = (): Set => { addFilesFromDir(developersDir); addFilesFromDir(utilitiesDir); addFilesFromDir(commandsDir); - addFilesFromDir(skillsDir); + addFilesFromDir(skillsDir); // group folder names, cleans up legacy nested installs + // Flattened skill names (-) created by the current installer. + listSkillsNested(skillsDir).forEach((s) => files.add(s.name)); return files; }; @@ -101,7 +104,8 @@ const getCodexManagedNames = (): { architecture: string[]; skills: string[] } => const skillsDir = path.join(ASSETS_DIR, 'skills'); if (fs.existsSync(skillsDir)) { - fs.readdirSync(skillsDir).forEach((name) => skills.push(name)); + fs.readdirSync(skillsDir).forEach((name) => skills.push(name)); // legacy group folders + listSkillsNested(skillsDir).forEach((s) => skills.push(s.name)); // flattened skills } const commandsDir = path.join(ASSETS_DIR, 'commands'); @@ -158,7 +162,8 @@ const getAntigravityManagedNames = (): { architecture: string[]; skills: string[ const skillsDir = path.join(ASSETS_DIR, 'skills'); if (fs.existsSync(skillsDir)) { - fs.readdirSync(skillsDir).forEach((name) => skills.push(name)); + fs.readdirSync(skillsDir).forEach((name) => skills.push(name)); // legacy group folders + listSkillsNested(skillsDir).forEach((s) => skills.push(s.name)); // flattened skills } const commandsDir = path.join(ASSETS_DIR, 'commands'); diff --git a/src/utils/symlink.ts b/src/utils/symlink.ts index 0a2df84..09a27ce 100644 --- a/src/utils/symlink.ts +++ b/src/utils/symlink.ts @@ -270,9 +270,13 @@ export const listItems = (dir: string): ListItem[] => { }; /** - * List skills in nested namespace form. For a directory laid out as + * List skills in flattened form. For a directory laid out as * ///SKILL.md - * returns one item per / with name = ":". + * returns one item per / with name = "-". + * Claude Code only scans skills one level deep (~/.claude/skills//SKILL.md) + * and the folder name IS the slash-command name, so nested groups must be + * flattened to a single-level name. A hyphen is used (not a colon) because + * `:` is an invalid filename character on Windows. * Top-level files or single-level folders are returned as-is for back-compat. */ export const listSkillsNested = (dir: string): ListItem[] => { @@ -316,7 +320,7 @@ export const listSkillsNested = (dir: string): ListItem[] => { fs.existsSync(path.join(actionPath, 'SKILL.md')) ) { items.push({ - name: `${entry}:${action}`, + name: `${entry}-${action}`, path: path.join(entryPath, action), isSymlink, target: target ? `${target}/${action}` : null, @@ -325,9 +329,11 @@ export const listSkillsNested = (dir: string): ListItem[] => { } continue; } - } - items.push({ name: entry, path: entryPath, isSymlink, target }); + // Single-level (flat) skill folder — keep as-is. + items.push({ name: entry, path: entryPath, isSymlink, target }); + } + // Non-directory entries (e.g. .DS_Store) are not skills — skip them. } return items;