Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion researchskills-extract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion researchskills-extract/commands/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<slug>.md`
- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/skills/researchskills/<slug>/SKILL.md`
- Option B: "Yes, install to Codex" — stores skills to `~/.codex/skills/researchskills-<slug>/SKILL.md`
- Option C: "Yes, install to both"
- Option D: "No, skip local install"
Expand Down
20 changes: 10 additions & 10 deletions researchskills-extract/commands/researchskills-extract.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|--------|-------------|
Expand Down Expand Up @@ -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."`
Expand All @@ -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`.
Expand Down Expand Up @@ -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 <domain> \
--subdomain <subdomain> \
Expand All @@ -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 <ALL-research-session-ids-csv> \
--verbose
Expand All @@ -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 <ALL-research-session-ids-csv> \
--verbose
Expand All @@ -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 <ALL-research-session-ids-csv> \
--domain <domain> \
--subdomain <subdomain> \
Expand All @@ -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/<slug>.md`
- Option A: "Yes, install to Claude Code" — stores skills to `~/.claude/skills/researchskills/<slug>/SKILL.md`
- Option B: "Yes, install to Codex" — stores skills to `~/.codex/skills/researchskills-<slug>/SKILL.md`
- Option C: "Yes, install to both"
- Option D: "No, skip local install"
Expand All @@ -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 <claude|codex|both> \
--session-ids <ALL-research-session-ids-csv>
```
Expand Down Expand Up @@ -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 <ALL-research-session-ids-csv> \
--domain <domain> \
--subdomain <subdomain> \
Expand Down
55 changes: 43 additions & 12 deletions researchskills-extract/scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
56 changes: 45 additions & 11 deletions researchskills-extract/scripts/postuninstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
35 changes: 25 additions & 10 deletions researchskills-extract/scripts/store-local.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* configuration so they are available as commands/skills in future sessions.
*
* Targets:
* - Claude Code: ~/.claude/commands/researchskills/<slug>.md
* - Claude Code: ~/.claude/skills/researchskills/<slug>/SKILL.md
* - Codex: ~/.codex/skills/researchskills-<slug>/SKILL.md
*
* Skill slugs are derived from the YAML frontmatter `name` field, not from
Expand Down Expand Up @@ -126,21 +126,36 @@ function storeToTarget(targetName, skills) {
let dir;

if (targetName === 'claude') {
// Claude Code: ~/.claude/commands/researchskills/<slug>.md
// Strip YAML frontmatter — Claude commands are plain markdown prompts
dir = path.join(os.homedir(), '.claude', 'commands', 'researchskills');
// Claude Code: ~/.claude/skills/researchskills/<slug>/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') {
Expand Down