diff --git a/researchskills-extract/README.md b/researchskills-extract/README.md index c35bb48..23b3578 100644 --- a/researchskills-extract/README.md +++ b/researchskills-extract/README.md @@ -19,7 +19,7 @@ npm install -g @scienceintelligence/researchskills-extract ``` This installs the command automatically to both platforms: -- **Claude Code** → `~/.claude/commands/researchskills-extract.md` +- **Claude Code** → `~/.claude/skills/researchskills-extract/SKILL.md` - **Codex** → `~/.codex/skills/researchskills-extract/SKILL.md` ## Usage diff --git a/researchskills-extract/commands/SKILL.md b/researchskills-extract/commands/SKILL.md index 39f8d16..e7b48b4 100644 --- a/researchskills-extract/commands/SKILL.md +++ b/researchskills-extract/commands/SKILL.md @@ -213,7 +213,7 @@ node ~/.codex/skills/researchskills-extract/scripts/finalize.js \ Use `ask` (Codex) or `AskUserQuestion` (Claude Code) to present the options. If no interactive tool is available, print the options and end your turn — the user's next message is their selection. - Question: "Install extracted skills into your local AI coding tool?" -- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/commands/researchskills/.md` +- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/skills/researchskills//SKILL.md` - Option B: "Yes, install to Codex" — stores skills to `~/.codex/skills/researchskills-/SKILL.md` - Option C: "Yes, install to both" - Option D: "No, skip local install" diff --git a/researchskills-extract/commands/researchskills-extract.md b/researchskills-extract/commands/researchskills-extract.md index e064df5..409868c 100644 --- a/researchskills-extract/commands/researchskills-extract.md +++ b/researchskills-extract/commands/researchskills-extract.md @@ -28,7 +28,7 @@ finalize.js ─┘ You (main agent) ← call scripts, read summaries, report ``` -Helper scripts (installed at `~/.claude/utils/`): +Helper scripts (installed at `~/.claude/skills/researchskills-extract/scripts/`): | Script | What it does | |--------|-------------| @@ -56,7 +56,7 @@ Detect mode at start. Announce: `"Running in TEST MODE"` or `"Running in product ```bash mkdir -p ~/.researchskills/cache/meta ~/.researchskills/cache/skills -node ~/.claude/utils/scan-sessions.js +node ~/.claude/skills/researchskills-extract/scripts/scan-sessions.js ``` Reads `~/.researchskills/cache/work-list.json` output. Report: `"Found N sessions across M projects."` @@ -68,7 +68,7 @@ Reads `~/.researchskills/cache/work-list.json` output. Report: `"Found N session **YOU MUST call this script. Do NOT classify projects yourself.** ```bash -node ~/.claude/utils/classify-projects.js ~/.researchskills/cache/work-list.json --cc --verbose +node ~/.claude/skills/researchskills-extract/scripts/classify-projects.js ~/.researchskills/cache/work-list.json --cc --verbose ``` For test mode, add `--test`. @@ -127,7 +127,7 @@ The extraction script MUST be called in a loop with `--single-batch`. Each call ```bash # REPEAT this exact Bash call in a loop. Each call = 1 batch. -node ~/.claude/utils/extract-skills.js ~/.researchskills/cache/work-list.json \ +node ~/.claude/skills/researchskills-extract/scripts/extract-skills.js ~/.researchskills/cache/work-list.json \ --cc \ --domain \ --subdomain \ @@ -154,7 +154,7 @@ If you need to process multiple projects with different domains, call the script Run Opus to review all extracted skills: reject engineering content, fix PII leaks, merge duplicates. ```bash -node ~/.claude/utils/clean-skills.js \ +node ~/.claude/skills/researchskills-extract/scripts/clean-skills.js \ --cc \ --session-ids \ --verbose @@ -171,7 +171,7 @@ Report: `"Clean: kept N, rejected M, merged K."` Run Opus to assess the value of each surviving skill on 3 dimensions. ```bash -node ~/.claude/utils/score-skills.js \ +node ~/.claude/skills/researchskills-extract/scripts/score-skills.js \ --cc \ --session-ids \ --verbose @@ -190,7 +190,7 @@ Use the AI-generated `project_name` from classification.json (Stage 2). Do NOT u **Do NOT pass `--upload` here.** Collect skills locally first. Upload requires explicit user consent in Stage 7. ```bash -node ~/.claude/utils/finalize.js \ +node ~/.claude/skills/researchskills-extract/scripts/finalize.js \ --session-ids \ --domain \ --subdomain \ @@ -207,7 +207,7 @@ node ~/.claude/utils/finalize.js \ Use AskUserQuestion to present the options: - Question: "Install extracted skills into your local AI coding tool?" -- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/commands/researchskills/.md` +- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/skills/researchskills//SKILL.md` - Option B: "Yes, install to Codex" — stores skills to `~/.codex/skills/researchskills-/SKILL.md` - Option C: "Yes, install to both" - Option D: "No, skip local install" @@ -217,7 +217,7 @@ Use AskUserQuestion to present the options: If the user picks A, B, or C, run: ```bash -node ~/.claude/utils/store-local.js \ +node ~/.claude/skills/researchskills-extract/scripts/store-local.js \ --target \ --session-ids ``` @@ -271,7 +271,7 @@ If the user consents, re-run finalize with `--upload`. When the user has consented via the prompt above, pass `--consent` to include `consent: true` in the upload payload. ```bash -node ~/.claude/utils/finalize.js \ +node ~/.claude/skills/researchskills-extract/scripts/finalize.js \ --session-ids \ --domain \ --subdomain \ diff --git a/researchskills-extract/scripts/postinstall.js b/researchskills-extract/scripts/postinstall.js index a91af97..166a809 100644 --- a/researchskills-extract/scripts/postinstall.js +++ b/researchskills-extract/scripts/postinstall.js @@ -26,33 +26,64 @@ const HELPER_SCRIPTS = [ ]; // --- Claude Code --- -const CC_COMMANDS_DIR = path.join(os.homedir(), ".claude", "commands"); -const CC_COMMAND_TARGET = path.join(CC_COMMANDS_DIR, "researchskills-extract.md"); -const CC_UTILS_DIR = path.join(os.homedir(), ".claude", "utils"); +const CC_SKILL_DIR = path.join(os.homedir(), ".claude", "skills", "researchskills-extract"); +const CC_SKILL_TARGET = path.join(CC_SKILL_DIR, "SKILL.md"); +const CC_SCRIPTS_DIR = path.join(CC_SKILL_DIR, "scripts"); try { - fs.mkdirSync(CC_COMMANDS_DIR, { recursive: true }); - fs.mkdirSync(CC_UTILS_DIR, { recursive: true }); - fs.copyFileSync(SOURCE_CC_COMMAND, CC_COMMAND_TARGET); - const CC_CONVERT_TARGET = path.join(CC_COMMANDS_DIR, "researchskills-convert.md"); - fs.copyFileSync(SOURCE_CC_CONVERT, CC_CONVERT_TARGET); - console.log("✓ Claude Code: /researchskills-extract installed to ~/.claude/commands/"); - console.log("✓ Claude Code: /researchskills-convert installed to ~/.claude/commands/"); + fs.mkdirSync(CC_SKILL_DIR, { recursive: true }); + fs.mkdirSync(CC_SCRIPTS_DIR, { recursive: true }); + fs.copyFileSync(SOURCE_CC_COMMAND, CC_SKILL_TARGET); + const CC_CONVERT_DIR = path.join(os.homedir(), ".claude", "skills", "researchskills-convert"); + fs.mkdirSync(CC_CONVERT_DIR, { recursive: true }); + fs.copyFileSync(SOURCE_CC_CONVERT, path.join(CC_CONVERT_DIR, "SKILL.md")); + console.log("✓ Claude Code: /researchskills-extract installed to ~/.claude/skills/researchskills-extract/"); + console.log("✓ Claude Code: /researchskills-convert installed to ~/.claude/skills/researchskills-convert/"); for (const script of HELPER_SCRIPTS) { const src = path.join(__dirname, script); - const dst = path.join(CC_UTILS_DIR, script); + const dst = path.join(CC_SCRIPTS_DIR, script); if (fs.existsSync(src)) { fs.copyFileSync(src, dst); } else { console.warn(`⚠ Claude Code: ${script} not found in package, skipping`); } } - console.log(`✓ Claude Code: ${HELPER_SCRIPTS.length} scripts installed to ~/.claude/utils/`); + console.log(`✓ Claude Code: ${HELPER_SCRIPTS.length} scripts installed to ~/.claude/skills/researchskills-extract/scripts/`); } catch (err) { console.error("⚠ Claude Code: could not install —", err.message); } +// --- Clean up legacy Claude Code paths --- +const LEGACY_CC_COMMANDS_DIR = path.join(os.homedir(), ".claude", "commands"); +const LEGACY_CC_UTILS_DIR = path.join(os.homedir(), ".claude", "utils"); +try { + const legacyCcExtract = path.join(LEGACY_CC_COMMANDS_DIR, "researchskills-extract.md"); + if (fs.existsSync(legacyCcExtract)) { + fs.unlinkSync(legacyCcExtract); + console.log("✓ Removed legacy ~/.claude/commands/researchskills-extract.md"); + } + const legacyCcConvert = path.join(LEGACY_CC_COMMANDS_DIR, "researchskills-convert.md"); + if (fs.existsSync(legacyCcConvert)) { + fs.unlinkSync(legacyCcConvert); + console.log("✓ Removed legacy ~/.claude/commands/researchskills-convert.md"); + } + for (const script of HELPER_SCRIPTS) { + const p = path.join(LEGACY_CC_UTILS_DIR, script); + if (fs.existsSync(p)) fs.unlinkSync(p); + } + // Remove utils dir if empty + try { + if (fs.existsSync(LEGACY_CC_UTILS_DIR)) { + const remaining = fs.readdirSync(LEGACY_CC_UTILS_DIR); + if (remaining.length === 0) fs.rmdirSync(LEGACY_CC_UTILS_DIR); + } + } catch (_) { /* best effort */ } + console.log("✓ Cleaned up legacy ~/.claude/commands/ and ~/.claude/utils/ paths"); +} catch (err) { + console.warn("⚠ Could not clean up legacy Claude Code paths —", err.message); +} + // --- Codex --- const CODEX_SKILL_DIR = path.join(os.homedir(), ".codex", "skills", "researchskills-extract"); const CODEX_SKILL_TARGET = path.join(CODEX_SKILL_DIR, "SKILL.md"); diff --git a/researchskills-extract/scripts/postuninstall.js b/researchskills-extract/scripts/postuninstall.js index dd3133e..55934e3 100644 --- a/researchskills-extract/scripts/postuninstall.js +++ b/researchskills-extract/scripts/postuninstall.js @@ -20,28 +20,62 @@ const HELPER_SCRIPTS = [ ]; // --- Claude Code --- -const CC_COMMAND_TARGET = path.join(os.homedir(), ".claude", "commands", "researchskills-extract.md"); -const CC_CONVERT_TARGET = path.join(os.homedir(), ".claude", "commands", "researchskills-convert.md"); -const CC_UTILS_DIR = path.join(os.homedir(), ".claude", "utils"); +const CC_SKILL_DIR = path.join(os.homedir(), ".claude", "skills", "researchskills-extract"); +const CC_SKILL_TARGET = path.join(CC_SKILL_DIR, "SKILL.md"); +const CC_SCRIPTS_DIR = path.join(CC_SKILL_DIR, "scripts"); +const CC_CONVERT_DIR = path.join(os.homedir(), ".claude", "skills", "researchskills-convert"); try { - if (fs.existsSync(CC_COMMAND_TARGET)) { - fs.unlinkSync(CC_COMMAND_TARGET); - console.log("✓ Claude Code: /researchskills-extract removed"); - } - if (fs.existsSync(CC_CONVERT_TARGET)) { - fs.unlinkSync(CC_CONVERT_TARGET); - console.log("✓ Claude Code: /researchskills-convert removed"); + if (fs.existsSync(CC_SKILL_TARGET)) { + fs.unlinkSync(CC_SKILL_TARGET); + console.log("✓ Claude Code: /researchskills-extract SKILL.md removed"); } for (const script of HELPER_SCRIPTS) { - const p = path.join(CC_UTILS_DIR, script); + const p = path.join(CC_SCRIPTS_DIR, script); if (fs.existsSync(p)) fs.unlinkSync(p); } + // Remove dirs if empty + try { + if (fs.existsSync(CC_SCRIPTS_DIR)) { + const remaining = fs.readdirSync(CC_SCRIPTS_DIR); + if (remaining.length === 0) fs.rmdirSync(CC_SCRIPTS_DIR); + } + if (fs.existsSync(CC_SKILL_DIR)) { + const skillRemaining = fs.readdirSync(CC_SKILL_DIR); + if (skillRemaining.length === 0) fs.rmdirSync(CC_SKILL_DIR); + } + } catch (_) { /* best effort */ } + if (fs.existsSync(CC_CONVERT_DIR)) { + fs.rmSync(CC_CONVERT_DIR, { recursive: true, force: true }); + console.log("✓ Claude Code: /researchskills-convert removed"); + } console.log("✓ Claude Code: helper scripts removed"); } catch (err) { // ignore } +// --- Clean up legacy Claude Code paths (in case they still exist) --- +try { + const LEGACY_CC_COMMANDS_DIR = path.join(os.homedir(), ".claude", "commands"); + const LEGACY_CC_UTILS_DIR = path.join(os.homedir(), ".claude", "utils"); + const legacyCcExtract = path.join(LEGACY_CC_COMMANDS_DIR, "researchskills-extract.md"); + const legacyCcConvert = path.join(LEGACY_CC_COMMANDS_DIR, "researchskills-convert.md"); + if (fs.existsSync(legacyCcExtract)) fs.unlinkSync(legacyCcExtract); + if (fs.existsSync(legacyCcConvert)) fs.unlinkSync(legacyCcConvert); + for (const script of HELPER_SCRIPTS) { + const p = path.join(LEGACY_CC_UTILS_DIR, script); + if (fs.existsSync(p)) fs.unlinkSync(p); + } + try { + if (fs.existsSync(LEGACY_CC_UTILS_DIR)) { + const remaining = fs.readdirSync(LEGACY_CC_UTILS_DIR); + if (remaining.length === 0) fs.rmdirSync(LEGACY_CC_UTILS_DIR); + } + } catch (_) { /* best effort */ } +} catch (err) { + // ignore — legacy paths may not exist +} + // --- Codex --- const CODEX_SKILL_DIR = path.join(os.homedir(), ".codex", "skills", "researchskills-extract"); const CODEX_SKILL_TARGET = path.join(CODEX_SKILL_DIR, "SKILL.md"); diff --git a/researchskills-extract/scripts/store-local.js b/researchskills-extract/scripts/store-local.js index 4bd9fc8..7e18c17 100644 --- a/researchskills-extract/scripts/store-local.js +++ b/researchskills-extract/scripts/store-local.js @@ -6,7 +6,7 @@ * configuration so they are available as commands/skills in future sessions. * * Targets: - * - Claude Code: ~/.claude/commands/researchskills/.md + * - Claude Code: ~/.claude/skills/researchskills//SKILL.md * - Codex: ~/.codex/skills/researchskills-/SKILL.md * * Skill slugs are derived from the YAML frontmatter `name` field, not from @@ -126,21 +126,36 @@ function storeToTarget(targetName, skills) { let dir; if (targetName === 'claude') { - // Claude Code: ~/.claude/commands/researchskills/.md - // Strip YAML frontmatter — Claude commands are plain markdown prompts - dir = path.join(os.homedir(), '.claude', 'commands', 'researchskills'); + // Claude Code: ~/.claude/skills/researchskills//SKILL.md + // Keep full content with YAML frontmatter (same as Codex path) + dir = path.join(os.homedir(), '.claude', 'skills', 'researchskills'); fs.mkdirSync(dir, { recursive: true }); for (const [slug, { content, name }] of skills) { - const body = extractBody(content).trim(); - // Prepend a markdown title from the skill name - const commandContent = `# ${name}\n\n${body}\n`; - const dst = path.join(dir, `${slug}.md`); - if (fs.existsSync(dst) && fs.readFileSync(dst, 'utf-8') === commandContent) { + // Add description field if missing (for skill indexing/triggering) + let finalContent = content; + if (!extractField(content, 'description')) { + const memoryType = extractField(content, 'memory_type') || 'research'; + const domain = extractField(content, 'domain') || ''; + const subdomain = extractField(content, 'subdomain') || ''; + const body = extractBody(content).trim(); + const firstLine = body.split(/\n/)[0] || ''; + const snippet = firstLine.substring(0, 120).replace(/[#*_>]/g, '').trim(); + const parts = [`${memoryType} skill`, domain, subdomain].filter(Boolean); + const rawDesc = snippet + ? `${parts.join(' / ')}: ${snippet}` + : `ResearchSkills ${parts.join(' / ')}: ${name}`; + const desc = rawDesc.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + finalContent = content.replace(/^(---\s*\n)/, `$1description: "${desc}"\n`); + } + const skillDir = path.join(dir, slug); + fs.mkdirSync(skillDir, { recursive: true }); + const dst = path.join(skillDir, 'SKILL.md'); + if (fs.existsSync(dst) && fs.readFileSync(dst, 'utf-8') === finalContent) { skipped++; continue; } - fs.writeFileSync(dst, commandContent); + fs.writeFileSync(dst, finalContent); installed++; } } else if (targetName === 'codex') {