mirror of
https://github.com/obra/superpowers.git
synced 2026-06-09 17:02:07 +00:00
Compare commits
141 Commits
personal-s
...
claude/rev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0806ba5af | ||
|
|
6ecd72c5bf | ||
|
|
f600f969f5 | ||
|
|
4ef2f9185d | ||
|
|
8a626e75f3 | ||
|
|
014b11cf57 | ||
|
|
3f73365155 | ||
|
|
9f33fc95bf | ||
|
|
9220bb62af | ||
|
|
f5a4002daf | ||
|
|
9dcf5eaabe | ||
|
|
f3d6c331a1 | ||
|
|
b0ba2cf15a | ||
|
|
cbbd8d2edf | ||
|
|
9cd6c52acc | ||
|
|
107859a748 | ||
|
|
5f5b789e3e | ||
|
|
7db10cf540 | ||
|
|
67ce04077b | ||
|
|
d749c620b5 | ||
|
|
aa1fe045c6 | ||
|
|
368674419a | ||
|
|
c940d84f3d | ||
|
|
8e7f90a954 | ||
|
|
d92de28150 | ||
|
|
fa53c8f925 | ||
|
|
d3e89e8719 | ||
|
|
b746f7587b | ||
|
|
4eab16380b | ||
|
|
7ffff61965 | ||
|
|
fbd419e394 | ||
|
|
a131267d7c | ||
|
|
26c152b37e | ||
|
|
6ae8ef4733 | ||
|
|
1aa29ad52b | ||
|
|
6847cf4cfc | ||
|
|
425b40359c | ||
|
|
9e5ba91be6 | ||
|
|
4abd4df171 | ||
|
|
5dd31b90ee | ||
|
|
0fbdfa3c4a | ||
|
|
a23eead918 | ||
|
|
4594596e38 | ||
|
|
536fd24603 | ||
|
|
85effaaedb | ||
|
|
6cec629cf3 | ||
|
|
5dd8871a1b | ||
|
|
84283dfc05 | ||
|
|
d90334e030 | ||
|
|
9a9618489d | ||
|
|
02c87670de | ||
|
|
b187e75a1e | ||
|
|
8674dc0868 | ||
|
|
42d44ceaf9 | ||
|
|
d46dddd32c | ||
|
|
b1fa6a1a46 | ||
|
|
8e38ab86dc | ||
|
|
31fd764285 | ||
|
|
e3208f1d93 | ||
|
|
1d21ee842d | ||
|
|
aa8c6b4fd0 | ||
|
|
22f57e7cb0 | ||
|
|
da9f4f1edd | ||
|
|
5831c4dfea | ||
|
|
26487902f8 | ||
|
|
19e2997334 | ||
|
|
c88b0d674f | ||
|
|
17bbc2b130 | ||
|
|
f6ee98a41a | ||
|
|
e3d881b7b6 | ||
|
|
184a4c464e | ||
|
|
7fc125e5e9 | ||
|
|
79436abffa | ||
|
|
9597f088c4 | ||
|
|
7ce751294e | ||
|
|
1ef5758621 | ||
|
|
accb1231fc | ||
|
|
22ec50318e | ||
|
|
0bc5a5989d | ||
|
|
e59cf658c5 | ||
|
|
4d8db812ae | ||
|
|
141953a4be | ||
|
|
9e82a51f34 | ||
|
|
a681cfb024 | ||
|
|
48410c7f19 | ||
|
|
2076e49d18 | ||
|
|
d6ca4d6213 | ||
|
|
10afd4eacc | ||
|
|
451db491b8 | ||
|
|
deb45f5cad | ||
|
|
32659fe0f3 | ||
|
|
9c9547cc04 | ||
|
|
84123b8450 | ||
|
|
9db0a61515 | ||
|
|
e548c5d628 | ||
|
|
0fdea80a7d | ||
|
|
d6ac9a8b5d | ||
|
|
809956f3bf | ||
|
|
02429d7e26 | ||
|
|
51e14692c9 | ||
|
|
b331a79267 | ||
|
|
2d89035187 | ||
|
|
a3bbf5f009 | ||
|
|
927512f1ab | ||
|
|
998a3d545f | ||
|
|
b6f6ce12a6 | ||
|
|
bde691cedf | ||
|
|
9eefffc541 | ||
|
|
488139d6d1 | ||
|
|
0f30fd2989 | ||
|
|
5c19a391d9 | ||
|
|
a39561bd41 | ||
|
|
f6327a0051 | ||
|
|
b063888520 | ||
|
|
d5e2fe7876 | ||
|
|
d1f42e5462 | ||
|
|
60cb0cd0ca | ||
|
|
661e6292c6 | ||
|
|
562428563d | ||
|
|
e39929b541 | ||
|
|
a1a1c3119d | ||
|
|
5b0b086829 | ||
|
|
400ac0f436 | ||
|
|
eea083623e | ||
|
|
87f04224ef | ||
|
|
015a07f7d7 | ||
|
|
33622d710e | ||
|
|
2227e3151e | ||
|
|
cd83c0aac8 | ||
|
|
7f3a1e7428 | ||
|
|
7421ddfc2f | ||
|
|
35cec21e96 | ||
|
|
f801cd616d | ||
|
|
32eb6ed221 | ||
|
|
e8c960641e | ||
|
|
3c26a7f875 | ||
|
|
ec15b59c89 | ||
|
|
e901534e9e | ||
|
|
d3dd95027e | ||
|
|
958527f07e | ||
|
|
dedae3a786 |
20
.claude-plugin/marketplace.json
Normal file
20
.claude-plugin/marketplace.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "superpowers-dev",
|
||||
"description": "Development marketplace for Superpowers core skills library",
|
||||
"owner": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "3.4.0",
|
||||
"source": "./",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "superpowers",
|
||||
"description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques",
|
||||
"version": "1.0.0",
|
||||
"version": "3.4.1",
|
||||
"author": {
|
||||
"name": "Jesse Vincent",
|
||||
"email": "jesse@fsck.com"
|
||||
@@ -9,6 +9,5 @@
|
||||
"homepage": "https://github.com/obra/superpowers",
|
||||
"repository": "https://github.com/obra/superpowers",
|
||||
"license": "MIT",
|
||||
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"],
|
||||
"category": "productivity"
|
||||
"keywords": ["skills", "tdd", "debugging", "collaboration", "best-practices", "workflows"]
|
||||
}
|
||||
|
||||
141
.claude/settings.local.json
Normal file
141
.claude/settings.local.json
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/getting-started/**)",
|
||||
"Read(//Users/jesse/Downloads/**)",
|
||||
"Bash(~/.claude/plugins/cache/superpowers/skills/getting-started/list-skills)",
|
||||
"Bash(~/.claude/plugins/cache/superpowers/skills/getting-started/skills-search \"prompt\")",
|
||||
"Bash(~/.claude/plugins/cache/superpowers/skills/getting-started/skills-search \"communication\")",
|
||||
"Bash(~/.claude/plugins/cache/superpowers/skills/getting-started/skills-search \"interaction\")",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/meta/testing-skills-with-subagents/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/collaboration/dispatching-parallel-agents/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/collaboration/requesting-code-review/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/collaboration/writing-plans/**)",
|
||||
"mcp__journal__search_journal",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/meta/creating-skills/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/collaboration/brainstorming/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/superpowers/skills/**)",
|
||||
"Read(//Users/jesse/.claude/plugins/cache/**)",
|
||||
"mcp__journal__read_journal_entry",
|
||||
"Bash(/Users/jesse/git/superpowers/superpowers/skills/getting-started/list-skills)",
|
||||
"Bash(/Users/jesse/git/superpowers/superpowers/skills/getting-started/skills-search refactor)",
|
||||
"Read(//Users/jesse/Documents/GitHub/superpowers/**)",
|
||||
"Bash(${CLAUDE_PLUGIN_ROOT}/skills/getting-started/list-skills:*)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers/skills/getting-started/list-skills)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers/skills/getting-started/skills-search editing)",
|
||||
"Bash(list-skills brainstorm)",
|
||||
"Read(//Users/jesse/.claude/commands/**)",
|
||||
"Bash(git checkout:*)",
|
||||
"Bash(/Users/jesse/.claude/plugins/cache/superpowers/skills/getting-started/list-skills)",
|
||||
"Bash(ln:*)",
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)",
|
||||
"Bash(git push:*)",
|
||||
"Read(//Users/jesse/.claude/plugins/**)",
|
||||
"Read(//Users/jesse/.claude/**)",
|
||||
"Bash(cat:*)",
|
||||
"Read(//Users/jesse/.superpowers/**)",
|
||||
"Bash(find:*)",
|
||||
"Read(//Users/jesse/.clank/**)",
|
||||
"Bash(./search-conversations:*)",
|
||||
"Bash(./skills/collaboration/remembering-conversations/tool/search-conversations:*)",
|
||||
"Bash(npm install)",
|
||||
"Bash(sqlite3:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers/skills/collaboration/remembering-conversations/tool/migrate-to-config.sh:*)",
|
||||
"Read(//Users/jesse/.config/superpowers/**)",
|
||||
"Bash(./index-conversations --help)",
|
||||
"Bash(./index-conversations:*)",
|
||||
"Bash(bc)",
|
||||
"Bash(bc:*)",
|
||||
"Bash(./scripts/find-skills)",
|
||||
"Bash(./scripts/run:*)",
|
||||
"Bash(./scripts/find-skills test)",
|
||||
"Bash(find-skills:*)",
|
||||
"Bash(/Users/jesse/.claude/plugins/cache/superpowers/scripts/find-skills refactor)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(git worktree add:*)",
|
||||
"Bash([ -f package.json ])",
|
||||
"Bash(git worktree:*)",
|
||||
"Bash(gh repo create:*)",
|
||||
"Bash(git clone:*)",
|
||||
"Bash(gh repo view:*)",
|
||||
"Bash(test:*)",
|
||||
"Bash(git ls-tree:*)",
|
||||
"Bash(git rm:*)",
|
||||
"Bash(git mv:*)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers-skills/skills/using-skills/find-skills)",
|
||||
"Bash(tree:*)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers-skills/skills/using-skills/skill-run --help)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(git log:*)",
|
||||
"Bash(git show:*)",
|
||||
"Bash(git diff-tree:*)",
|
||||
"Bash(bash:*)",
|
||||
"Bash(xargs ls:*)",
|
||||
"Bash(git rev-parse:*)",
|
||||
"Bash(git reset:*)",
|
||||
"Bash(./skills/using-skills/find-skills)",
|
||||
"Bash(git rebase:*)",
|
||||
"Bash(GIT_SEQUENCE_EDITOR=\"sed -i '' 's/^pick 683707a/edit 683707a/'\" git rebase:*)",
|
||||
"Bash(gh pr create:*)",
|
||||
"Bash(for:*)",
|
||||
"Bash(do [ -f \"$skill\" ])",
|
||||
"Bash(! grep -q \"^when_to_use:\" \"$skill\")",
|
||||
"Bash(done)",
|
||||
"Bash(gh issue view:*)",
|
||||
"Bash(gh pr view:*)",
|
||||
"Bash(gh pr diff:*)",
|
||||
"Bash(/Users/jesse/Documents/GitHub/superpowers/superpowers-skills/skills/using-skills/find-skills test)",
|
||||
"Bash(xargs -I {} bash -c 'dir=$(echo {} | sed \"\"\"\"s|/SKILL.md||\"\"\"\" | xargs basename); name=$(grep \"\"\"\"^name:\"\"\"\" {} | sed \"\"\"\"s/^name: //\"\"\"\"); echo \"\"\"\"$dir -> $name\"\"\"\"')",
|
||||
"mcp__obsidian-mcp-tools__fetch",
|
||||
"Skill(superpowers:using-git-worktrees)",
|
||||
"Skill(superpowers:subagent-driven-development)",
|
||||
"Bash(./test-raw.sh:*)",
|
||||
"Bash(./chrome-ws raw \"ws://localhost:9222/devtools/page/test\" '{\"\"id\"\":1,\"\"method\"\":\"\"Browser.getVersion\"\"}')",
|
||||
"Bash(./test-tabs.sh:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(./chrome-ws tabs:*)",
|
||||
"Bash(./chrome-ws close:*)",
|
||||
"Bash(./chrome-ws raw:*)",
|
||||
"Bash(./chrome-ws new:*)",
|
||||
"Bash(./test-navigate.sh:*)",
|
||||
"Bash(./test-interact.sh:*)",
|
||||
"Bash(./test-extract.sh)",
|
||||
"Bash(./test-wait.sh:*)",
|
||||
"Bash(./test-e2e.sh:*)",
|
||||
"Bash(./chrome-ws extract:*)",
|
||||
"Bash(./chrome-ws screenshot:*)",
|
||||
"Bash(./chrome-ws start:*)",
|
||||
"Bash(./chrome-ws navigate:*)",
|
||||
"Bash(git init:*)",
|
||||
"Bash(git tag:*)",
|
||||
"Skill(example-skills:mcp-builder)",
|
||||
"Bash(npm run build)",
|
||||
"Bash(npm run clean)",
|
||||
"Bash(timeout 3s node dist/index.js)",
|
||||
"Bash(git -C /Users/jesse/Documents/GitHub/superpowers/superpowers-chrome ls-files .claude-plugin/marketplace.json)",
|
||||
"mcp__private-journal__read_journal_entry",
|
||||
"Bash(git pull:*)",
|
||||
"Skill(elements-of-style:writing-clearly-and-concisely)",
|
||||
"Bash(gh release list:*)",
|
||||
"Bash(gh release create:*)",
|
||||
"Read(//Users/jesse/git/superpowers/superpowers-marketplace/.claude-plugin/**)",
|
||||
"mcp__plugin_episodic-memory_episodic-memory__search",
|
||||
"Skill(superpowers:writing-skills)",
|
||||
"mcp__private-journal__process_thoughts",
|
||||
"Skill(superpowers:brainstorming)",
|
||||
"Skill(superpowers:using-superpowers)",
|
||||
"Skill(episodic-memory:remembering-conversations)",
|
||||
"Skill(superpowers-developing-for-claude-code:developing-claude-code-plugins)",
|
||||
"Skill(working-with-claude-code)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": [],
|
||||
"additionalDirectories": [
|
||||
"/Users/jesse/Documents/GitHub/superpowers/superpowers-skills/",
|
||||
"/Users/jesse/Documents/GitHub/superpowers/superpowers-marketplace",
|
||||
"/Users/jesse/Documents/GitHub/superpowers/using-chrome-directly/"
|
||||
]
|
||||
}
|
||||
}
|
||||
35
.codex/INSTALL.md
Normal file
35
.codex/INSTALL.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Installing Superpowers for Codex
|
||||
|
||||
Quick setup to enable superpowers skills in Codex.
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Clone superpowers repository**:
|
||||
```bash
|
||||
mkdir -p ~/.codex/superpowers
|
||||
cd ~/.codex/superpowers
|
||||
git clone https://github.com/obra/superpowers.git .
|
||||
```
|
||||
|
||||
2. **Create personal skills directory**:
|
||||
```bash
|
||||
mkdir -p ~/.codex/skills
|
||||
```
|
||||
|
||||
3. **Update ~/.codex/AGENTS.md** to include this superpowers section:
|
||||
```markdown
|
||||
## Superpowers System
|
||||
|
||||
<EXTREMELY_IMPORTANT>
|
||||
You have superpowers. Superpowers teach you new skills and capabilities. RIGHT NOW run: `~/.codex/superpowers/.codex/superpowers-codex bootstrap` and follow the instructions it returns.
|
||||
</EXTREMELY_IMPORTANT>
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Test the installation:
|
||||
```bash
|
||||
~/.codex/superpowers/.codex/superpowers-codex bootstrap
|
||||
```
|
||||
|
||||
You should see skill listings and bootstrap instructions. The system is now ready for use.
|
||||
33
.codex/superpowers-bootstrap.md
Normal file
33
.codex/superpowers-bootstrap.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Superpowers Bootstrap for Codex
|
||||
|
||||
<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
**Tool for running skills:**
|
||||
- `~/.codex/superpowers/.codex/superpowers-codex use-skill <skill-name>`
|
||||
|
||||
**Tool Mapping for Codex:**
|
||||
When skills reference tools you don't have, substitute your equivalent tools:
|
||||
- `TodoWrite` → `update_plan` (your planning/task tracking tool)
|
||||
- `Task` tool with subagents → Tell the user that subagents aren't available in Codex yet and you'll do the work the subagent would do
|
||||
- `Skill` tool → `~/.codex/superpowers/.codex/superpowers-codex use-skill` command (already available)
|
||||
- `Read`, `Write`, `Edit`, `Bash` → Use your native tools with similar functions
|
||||
|
||||
**Skills naming:**
|
||||
- Superpowers skills: `superpowers:skill-name` (from ~/.codex/superpowers/skills/)
|
||||
- Personal skills: `skill-name` (from ~/.codex/skills/)
|
||||
- Personal skills override superpowers skills when names match
|
||||
|
||||
**Critical Rules:**
|
||||
- Before ANY task, review the skills list (shown below)
|
||||
- If a relevant skill exists, you MUST use `~/.codex/superpowers/.codex/superpowers-codex use-skill` to load it
|
||||
- Announce: "I've read the [Skill Name] skill and I'm using it to [purpose]"
|
||||
- Skills with checklists require `update_plan` todos for each item
|
||||
- NEVER skip mandatory workflows (brainstorming before coding, TDD, systematic debugging)
|
||||
|
||||
**Skills location:**
|
||||
- Superpowers skills: ~/.codex/superpowers/skills/
|
||||
- Personal skills: ~/.codex/skills/ (override superpowers when names match)
|
||||
|
||||
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
||||
</EXTREMELY_IMPORTANT>
|
||||
267
.codex/superpowers-codex
Executable file
267
.codex/superpowers-codex
Executable file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const skillsCore = require('../lib/skills-core');
|
||||
|
||||
// Paths
|
||||
const homeDir = os.homedir();
|
||||
const superpowersSkillsDir = path.join(homeDir, '.codex', 'superpowers', 'skills');
|
||||
const personalSkillsDir = path.join(homeDir, '.codex', 'skills');
|
||||
const bootstrapFile = path.join(homeDir, '.codex', 'superpowers', '.codex', 'superpowers-bootstrap.md');
|
||||
const superpowersRepoDir = path.join(homeDir, '.codex', 'superpowers');
|
||||
|
||||
// Utility functions
|
||||
function printSkill(skillPath, sourceType) {
|
||||
const skillFile = path.join(skillPath, 'SKILL.md');
|
||||
const relPath = sourceType === 'personal'
|
||||
? path.relative(personalSkillsDir, skillPath)
|
||||
: path.relative(superpowersSkillsDir, skillPath);
|
||||
|
||||
// Print skill name with namespace
|
||||
if (sourceType === 'personal') {
|
||||
console.log(relPath.replace(/\\/g, '/')); // Personal skills are not namespaced
|
||||
} else {
|
||||
console.log(`superpowers:${relPath.replace(/\\/g, '/')}`); // Superpowers skills get superpowers namespace
|
||||
}
|
||||
|
||||
// Extract and print metadata
|
||||
const { name, description } = skillsCore.extractFrontmatter(skillFile);
|
||||
|
||||
if (description) console.log(` ${description}`);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Commands
|
||||
function runFindSkills() {
|
||||
console.log('Available skills:');
|
||||
console.log('==================');
|
||||
console.log('');
|
||||
|
||||
const foundSkills = new Set();
|
||||
|
||||
// Find personal skills first (these take precedence)
|
||||
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 2);
|
||||
for (const skill of personalSkills) {
|
||||
const relPath = path.relative(personalSkillsDir, skill.path);
|
||||
foundSkills.add(relPath);
|
||||
printSkill(skill.path, 'personal');
|
||||
}
|
||||
|
||||
// Find superpowers skills (only if not already found in personal)
|
||||
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 1);
|
||||
for (const skill of superpowersSkills) {
|
||||
const relPath = path.relative(superpowersSkillsDir, skill.path);
|
||||
if (!foundSkills.has(relPath)) {
|
||||
printSkill(skill.path, 'superpowers');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Usage:');
|
||||
console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
|
||||
console.log('');
|
||||
console.log('Skill naming:');
|
||||
console.log(' Superpowers skills: superpowers:skill-name (from ~/.codex/superpowers/skills/)');
|
||||
console.log(' Personal skills: skill-name (from ~/.codex/skills/)');
|
||||
console.log(' Personal skills override superpowers skills when names match.');
|
||||
console.log('');
|
||||
console.log('Note: All skills are disclosed at session start via bootstrap.');
|
||||
}
|
||||
|
||||
function runBootstrap() {
|
||||
console.log('# Superpowers Bootstrap for Codex');
|
||||
console.log('# ================================');
|
||||
console.log('');
|
||||
|
||||
// Check for updates (with timeout protection)
|
||||
if (skillsCore.checkForUpdates(superpowersRepoDir)) {
|
||||
console.log('## Update Available');
|
||||
console.log('');
|
||||
console.log('⚠️ Your superpowers installation is behind the latest version.');
|
||||
console.log('To update, run: `cd ~/.codex/superpowers && git pull`');
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Show the bootstrap instructions
|
||||
if (fs.existsSync(bootstrapFile)) {
|
||||
console.log('## Bootstrap Instructions:');
|
||||
console.log('');
|
||||
try {
|
||||
const content = fs.readFileSync(bootstrapFile, 'utf8');
|
||||
console.log(content);
|
||||
} catch (error) {
|
||||
console.log(`Error reading bootstrap file: ${error.message}`);
|
||||
}
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
}
|
||||
|
||||
// Run find-skills to show available skills
|
||||
console.log('## Available Skills:');
|
||||
console.log('');
|
||||
runFindSkills();
|
||||
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
|
||||
// Load the using-superpowers skill automatically
|
||||
console.log('## Auto-loading superpowers:using-superpowers skill:');
|
||||
console.log('');
|
||||
runUseSkill('superpowers:using-superpowers');
|
||||
|
||||
console.log('');
|
||||
console.log('---');
|
||||
console.log('');
|
||||
console.log('# Bootstrap Complete!');
|
||||
console.log('# You now have access to all superpowers skills.');
|
||||
console.log('# Use "superpowers-codex use-skill <skill>" to load and apply skills.');
|
||||
console.log('# Remember: If a skill applies to your task, you MUST use it!');
|
||||
}
|
||||
|
||||
function runUseSkill(skillName) {
|
||||
if (!skillName) {
|
||||
console.log('Usage: superpowers-codex use-skill <skill-name>');
|
||||
console.log('Examples:');
|
||||
console.log(' superpowers-codex use-skill superpowers:brainstorming # Load superpowers skill');
|
||||
console.log(' superpowers-codex use-skill brainstorming # Load personal skill (or superpowers if not found)');
|
||||
console.log(' superpowers-codex use-skill my-custom-skill # Load personal skill');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle namespaced skill names
|
||||
let actualSkillPath;
|
||||
let forceSuperpowers = false;
|
||||
|
||||
if (skillName.startsWith('superpowers:')) {
|
||||
// Remove the superpowers: namespace prefix
|
||||
actualSkillPath = skillName.substring('superpowers:'.length);
|
||||
forceSuperpowers = true;
|
||||
} else {
|
||||
actualSkillPath = skillName;
|
||||
}
|
||||
|
||||
// Remove "skills/" prefix if present
|
||||
if (actualSkillPath.startsWith('skills/')) {
|
||||
actualSkillPath = actualSkillPath.substring('skills/'.length);
|
||||
}
|
||||
|
||||
// Function to find skill file
|
||||
function findSkillFile(searchPath) {
|
||||
// Check for exact match with SKILL.md
|
||||
const skillMdPath = path.join(searchPath, 'SKILL.md');
|
||||
if (fs.existsSync(skillMdPath)) {
|
||||
return skillMdPath;
|
||||
}
|
||||
|
||||
// Check for direct SKILL.md file
|
||||
if (searchPath.endsWith('SKILL.md') && fs.existsSync(searchPath)) {
|
||||
return searchPath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let skillFile = null;
|
||||
|
||||
// If superpowers: namespace was used, only check superpowers skills
|
||||
if (forceSuperpowers) {
|
||||
if (fs.existsSync(superpowersSkillsDir)) {
|
||||
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(superpowersPath);
|
||||
}
|
||||
} else {
|
||||
// First check personal skills directory (takes precedence)
|
||||
if (fs.existsSync(personalSkillsDir)) {
|
||||
const personalPath = path.join(personalSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(personalPath);
|
||||
if (skillFile) {
|
||||
console.log(`# Loading personal skill: ${actualSkillPath}`);
|
||||
console.log(`# Source: ${skillFile}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in personal, check superpowers skills
|
||||
if (!skillFile && fs.existsSync(superpowersSkillsDir)) {
|
||||
const superpowersPath = path.join(superpowersSkillsDir, actualSkillPath);
|
||||
skillFile = findSkillFile(superpowersPath);
|
||||
if (skillFile) {
|
||||
console.log(`# Loading superpowers skill: superpowers:${actualSkillPath}`);
|
||||
console.log(`# Source: ${skillFile}`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If still not found, error
|
||||
if (!skillFile) {
|
||||
console.log(`Error: Skill not found: ${actualSkillPath}`);
|
||||
console.log('');
|
||||
console.log('Available skills:');
|
||||
runFindSkills();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract frontmatter and content using shared core functions
|
||||
let content, frontmatter;
|
||||
try {
|
||||
const fullContent = fs.readFileSync(skillFile, 'utf8');
|
||||
const { name, description } = skillsCore.extractFrontmatter(skillFile);
|
||||
content = skillsCore.stripFrontmatter(fullContent);
|
||||
frontmatter = { name, description };
|
||||
} catch (error) {
|
||||
console.log(`Error reading skill file: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Display skill header with clean info
|
||||
const displayName = forceSuperpowers ? `superpowers:${actualSkillPath}` :
|
||||
(skillFile.includes(personalSkillsDir) ? actualSkillPath : `superpowers:${actualSkillPath}`);
|
||||
|
||||
const skillDirectory = path.dirname(skillFile);
|
||||
|
||||
console.log(`# ${frontmatter.name || displayName}`);
|
||||
if (frontmatter.description) {
|
||||
console.log(`# ${frontmatter.description}`);
|
||||
}
|
||||
console.log(`# Supporting tools and docs are in ${skillDirectory}`);
|
||||
console.log('# ============================================');
|
||||
console.log('');
|
||||
|
||||
// Display the skill content (without frontmatter)
|
||||
console.log(content);
|
||||
|
||||
}
|
||||
|
||||
// Main CLI
|
||||
const command = process.argv[2];
|
||||
const arg = process.argv[3];
|
||||
|
||||
switch (command) {
|
||||
case 'bootstrap':
|
||||
runBootstrap();
|
||||
break;
|
||||
case 'use-skill':
|
||||
runUseSkill(arg);
|
||||
break;
|
||||
case 'find-skills':
|
||||
runFindSkills();
|
||||
break;
|
||||
default:
|
||||
console.log('Superpowers for Codex');
|
||||
console.log('Usage:');
|
||||
console.log(' superpowers-codex bootstrap # Run complete bootstrap with all skills');
|
||||
console.log(' superpowers-codex use-skill <skill-name> # Load a specific skill');
|
||||
console.log(' superpowers-codex find-skills # List all available skills');
|
||||
console.log('');
|
||||
console.log('Examples:');
|
||||
console.log(' superpowers-codex bootstrap');
|
||||
console.log(' superpowers-codex use-skill superpowers:brainstorming');
|
||||
console.log(' superpowers-codex use-skill my-custom-skill');
|
||||
break;
|
||||
}
|
||||
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [obra]
|
||||
135
.opencode/INSTALL.md
Normal file
135
.opencode/INSTALL.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# Installing Superpowers for OpenCode
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
|
||||
## Installation Steps
|
||||
|
||||
### 1. Install Superpowers
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
```
|
||||
|
||||
### 2. Register the Plugin
|
||||
|
||||
Create a symlink so OpenCode discovers the plugin:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
```
|
||||
|
||||
### 3. Restart OpenCode
|
||||
|
||||
Restart OpenCode. The plugin will automatically inject superpowers context via the chat.message hook.
|
||||
|
||||
You should see superpowers is active when you ask "do you have superpowers?"
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Use the `find_skills` tool to list all available skills:
|
||||
|
||||
```
|
||||
use find_skills tool
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
Use the `use_skill` tool to load a specific skill:
|
||||
|
||||
```
|
||||
use use_skill tool with skill_name: "superpowers:brainstorming"
|
||||
```
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.config/opencode/skills/`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/skills/my-skill
|
||||
```
|
||||
|
||||
Create `~/.config/opencode/skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
Personal skills override superpowers skills with the same name.
|
||||
|
||||
### Project Skills
|
||||
|
||||
Create project-specific skills in your OpenCode project:
|
||||
|
||||
```bash
|
||||
# In your OpenCode project
|
||||
mkdir -p .opencode/skills/my-project-skill
|
||||
```
|
||||
|
||||
Create `.opencode/skills/my-project-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-project-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Project Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
**Skill Priority:** Project skills override personal skills, which override superpowers skills.
|
||||
|
||||
**Skill Naming:**
|
||||
- `project:skill-name` - Force project skill lookup
|
||||
- `skill-name` - Searches project → personal → superpowers
|
||||
- `superpowers:skill-name` - Force superpowers skill lookup
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.config/opencode/superpowers
|
||||
git pull
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin not loading
|
||||
|
||||
1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
2. Check OpenCode logs for errors
|
||||
3. Verify Node.js is installed: `node --version`
|
||||
|
||||
### Skills not found
|
||||
|
||||
1. Verify skills directory exists: `ls ~/.config/opencode/superpowers/skills`
|
||||
2. Use `find_skills` tool to see what's discovered
|
||||
3. Check file structure: each skill should have a `SKILL.md` file
|
||||
|
||||
### Tool mapping issues
|
||||
|
||||
When a skill references a Claude Code tool you don't have:
|
||||
- `TodoWrite` → use `update_plan`
|
||||
- `Task` with subagents → use `@mention` syntax to invoke OpenCode subagents
|
||||
- `Skill` → use `use_skill` tool
|
||||
- File operations → use your native tools
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Documentation: https://github.com/obra/superpowers
|
||||
205
.opencode/plugin/superpowers.js
Normal file
205
.opencode/plugin/superpowers.js
Normal file
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Superpowers plugin for OpenCode.ai
|
||||
*
|
||||
* Provides custom tools for loading and discovering skills,
|
||||
* with prompt generation for agent configuration.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { tool } from '@opencode-ai/plugin/tool';
|
||||
import * as skillsCore from '../../lib/skills-core.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const SuperpowersPlugin = async ({ client, directory }) => {
|
||||
const homeDir = os.homedir();
|
||||
const projectSkillsDir = path.join(directory, '.opencode/skills');
|
||||
// Derive superpowers skills dir from plugin location (works for both symlinked and local installs)
|
||||
const superpowersSkillsDir = path.resolve(__dirname, '../../skills');
|
||||
const personalSkillsDir = path.join(homeDir, '.config/opencode/skills');
|
||||
|
||||
return {
|
||||
tool: {
|
||||
use_skill: tool({
|
||||
description: 'Load and read a specific skill to guide your work. Skills contain proven workflows, mandatory processes, and expert techniques.',
|
||||
args: {
|
||||
skill_name: tool.schema.string().describe('Name of the skill to load (e.g., "superpowers:brainstorming", "my-custom-skill", or "project:my-skill")')
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
const { skill_name } = args;
|
||||
|
||||
// Resolve with priority: project > personal > superpowers
|
||||
// Check for project: prefix first
|
||||
const forceProject = skill_name.startsWith('project:');
|
||||
const actualSkillName = forceProject ? skill_name.replace(/^project:/, '') : skill_name;
|
||||
|
||||
let resolved = null;
|
||||
|
||||
// Try project skills first (if project: prefix or no prefix)
|
||||
if (forceProject || !skill_name.startsWith('superpowers:')) {
|
||||
const projectPath = path.join(projectSkillsDir, actualSkillName);
|
||||
const projectSkillFile = path.join(projectPath, 'SKILL.md');
|
||||
if (fs.existsSync(projectSkillFile)) {
|
||||
resolved = {
|
||||
skillFile: projectSkillFile,
|
||||
sourceType: 'project',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to personal/superpowers resolution
|
||||
if (!resolved && !forceProject) {
|
||||
resolved = skillsCore.resolveSkillPath(skill_name, superpowersSkillsDir, personalSkillsDir);
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
return `Error: Skill "${skill_name}" not found.\n\nRun find_skills to see available skills.`;
|
||||
}
|
||||
|
||||
const fullContent = fs.readFileSync(resolved.skillFile, 'utf8');
|
||||
const { name, description } = skillsCore.extractFrontmatter(resolved.skillFile);
|
||||
const content = skillsCore.stripFrontmatter(fullContent);
|
||||
const skillDirectory = path.dirname(resolved.skillFile);
|
||||
|
||||
const skillHeader = `# ${name || skill_name}
|
||||
# ${description || ''}
|
||||
# Supporting tools and docs are in ${skillDirectory}
|
||||
# ============================================`;
|
||||
|
||||
// Insert as user message with noReply for persistence across compaction
|
||||
try {
|
||||
await client.session.prompt({
|
||||
path: { id: context.sessionID },
|
||||
body: {
|
||||
noReply: true,
|
||||
parts: [
|
||||
{ type: "text", text: `Loading skill: ${name || skill_name}` },
|
||||
{ type: "text", text: `${skillHeader}\n\n${content}` }
|
||||
]
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Fallback: return content directly if message insertion fails
|
||||
return `${skillHeader}\n\n${content}`;
|
||||
}
|
||||
|
||||
return `Launching skill: ${name || skill_name}`;
|
||||
}
|
||||
}),
|
||||
find_skills: tool({
|
||||
description: 'List all available skills in the project, personal, and superpowers skill libraries.',
|
||||
args: {},
|
||||
execute: async (args, context) => {
|
||||
const projectSkills = skillsCore.findSkillsInDir(projectSkillsDir, 'project', 3);
|
||||
const personalSkills = skillsCore.findSkillsInDir(personalSkillsDir, 'personal', 3);
|
||||
const superpowersSkills = skillsCore.findSkillsInDir(superpowersSkillsDir, 'superpowers', 3);
|
||||
|
||||
// Priority: project > personal > superpowers
|
||||
const allSkills = [...projectSkills, ...personalSkills, ...superpowersSkills];
|
||||
|
||||
if (allSkills.length === 0) {
|
||||
return 'No skills found. Install superpowers skills to ~/.config/opencode/superpowers/skills/ or add project skills to .opencode/skills/';
|
||||
}
|
||||
|
||||
let output = 'Available skills:\n\n';
|
||||
|
||||
for (const skill of allSkills) {
|
||||
let namespace;
|
||||
switch (skill.sourceType) {
|
||||
case 'project':
|
||||
namespace = 'project:';
|
||||
break;
|
||||
case 'personal':
|
||||
namespace = '';
|
||||
break;
|
||||
default:
|
||||
namespace = 'superpowers:';
|
||||
}
|
||||
const skillName = skill.name || path.basename(skill.path);
|
||||
|
||||
output += `${namespace}${skillName}\n`;
|
||||
if (skill.description) {
|
||||
output += ` ${skill.description}\n`;
|
||||
}
|
||||
output += ` Directory: ${skill.path}\n\n`;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
})
|
||||
},
|
||||
"chat.message": async (input, output) => {
|
||||
// Only inject on first message of session (or every message if needed)
|
||||
if (!output.message.system || output.message.system.length === 0) {
|
||||
const usingSuperpowersPath = skillsCore.resolveSkillPath('using-superpowers', superpowersSkillsDir, personalSkillsDir);
|
||||
|
||||
if (usingSuperpowersPath) {
|
||||
const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8');
|
||||
const usingSuperpowersContent = skillsCore.stripFrontmatter(fullContent);
|
||||
|
||||
const toolMapping = `**Tool Mapping for OpenCode:**
|
||||
When skills reference tools you don't have, substitute OpenCode equivalents:
|
||||
- \`TodoWrite\` → \`update_plan\`
|
||||
- \`Task\` tool with subagents → Use OpenCode's subagent system (@mention)
|
||||
- \`Skill\` tool → \`use_skill\` custom tool
|
||||
- \`Read\`, \`Write\`, \`Edit\`, \`Bash\` → Your native tools
|
||||
|
||||
**Skills naming (priority order):**
|
||||
- Project skills: \`project:skill-name\` (in .opencode/skills/)
|
||||
- Personal skills: \`skill-name\` (in ~/.config/opencode/skills/)
|
||||
- Superpowers skills: \`superpowers:skill-name\`
|
||||
- Project skills override personal, which override superpowers when names match`;
|
||||
|
||||
output.message.system = `<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
${usingSuperpowersContent}
|
||||
|
||||
${toolMapping}
|
||||
</EXTREMELY_IMPORTANT>`;
|
||||
}
|
||||
}
|
||||
},
|
||||
event: async ({ event }) => {
|
||||
// Re-inject bootstrap after context compaction to maintain superpowers
|
||||
if (event.type === 'session.compacted') {
|
||||
const usingSuperpowersPath = skillsCore.resolveSkillPath('using-superpowers', superpowersSkillsDir, personalSkillsDir);
|
||||
|
||||
if (usingSuperpowersPath) {
|
||||
const fullContent = fs.readFileSync(usingSuperpowersPath.skillFile, 'utf8');
|
||||
const content = skillsCore.stripFrontmatter(fullContent);
|
||||
|
||||
const toolMapping = `**Tool Mapping:** TodoWrite->update_plan, Task->@mention, Skill->use_skill
|
||||
|
||||
**Skills naming (priority order):** project: > personal > superpowers:`;
|
||||
|
||||
try {
|
||||
await client.session.prompt({
|
||||
path: { id: event.properties.sessionID },
|
||||
body: {
|
||||
noReply: true,
|
||||
parts: [{
|
||||
type: "text",
|
||||
text: `<EXTREMELY_IMPORTANT>
|
||||
You have superpowers.
|
||||
|
||||
${content}
|
||||
|
||||
${toolMapping}
|
||||
</EXTREMELY_IMPORTANT>`
|
||||
}]
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// Silent failure - bootstrap will be missing but session continues
|
||||
console.error('Failed to re-inject superpowers after compaction:', err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
234
README.md
234
README.md
@@ -1,154 +1,119 @@
|
||||
# Superpowers
|
||||
|
||||
Give Claude Code superpowers with a comprehensive skills library of proven techniques, patterns, and tools.
|
||||
AI agents skip steps under time pressure. They bypass best practices when confident. They lack consistency across tasks. The result: bugs you didn't catch, designs you didn't validate, tests you didn't write.
|
||||
|
||||
## What You Get
|
||||
**Superpowers fixes this.** Skills are mandatory instruction documents agents must follow. When a relevant skill exists, the agent checks for it, uses it, or fails the task.
|
||||
|
||||
- **Testing Skills** - TDD, async testing, anti-patterns
|
||||
- **Debugging Skills** - Systematic debugging, root cause tracing, verification
|
||||
- **Collaboration Skills** - Brainstorming, planning, code review, parallel agents
|
||||
- **Meta Skills** - Creating, testing, and contributing skills
|
||||
## How It Works
|
||||
|
||||
Plus:
|
||||
- **Slash Commands** - `/brainstorm`, `/write-plan`, `/execute-plan`
|
||||
- **Skills Search** - Grep-powered discovery of relevant skills
|
||||
- **Gap Tracking** - Failed searches logged for skill creation
|
||||
At session start, the agent learns which skills exist. Before any task, the agent checks: "Does a skill match this work?" If yes, the agent loads and follows that skill.
|
||||
|
||||
## Learn More
|
||||
Skills are markdown files with proven workflows. The `test-driven-development` skill forces RED-GREEN-REFACTOR. No test-first? Delete the code and start over. The skill prevents rationalization.
|
||||
|
||||
Read the introduction: [Superpowers for Claude Code](https://blog.fsck.com/2025/10/09/superpowers/)
|
||||
## The Workflow
|
||||
|
||||
**When you ask to build a feature:**
|
||||
|
||||
1. **brainstorming** - Activates before writing code. Refines rough ideas through questions, explores alternatives, presents design in sections for validation. Saves design document.
|
||||
|
||||
2. **using-git-worktrees** - Activates after design approval. Creates isolated workspace on new branch, runs project setup, verifies clean test baseline.
|
||||
|
||||
3. **writing-plans** - Activates with approved design. Breaks work into bite-sized tasks (2-5 minutes each). Every task has exact file paths, complete code, verification steps.
|
||||
|
||||
4. **subagent-driven-development** or **executing-plans** - Activates with plan. Dispatches fresh subagent per task (same session, fast iteration) or executes in batches (parallel session, human checkpoints).
|
||||
|
||||
5. **test-driven-development** - Activates during implementation. Enforces RED-GREEN-REFACTOR: write failing test, watch it fail, write minimal code, watch it pass, commit. Deletes code written before tests.
|
||||
|
||||
6. **requesting-code-review** - Activates between tasks. Reviews against plan, reports issues by severity. Critical issues block progress.
|
||||
|
||||
7. **finishing-a-development-branch** - Activates when tasks complete. Verifies tests, presents options (merge/PR/keep/discard), cleans up worktree.
|
||||
|
||||
**The agent checks for relevant skills before any task.** Mandatory workflows, not suggestions.
|
||||
|
||||
## Installation
|
||||
|
||||
### Via Plugin Marketplace (Recommended)
|
||||
**Note:** Installation differs by platform. Claude Code has a built-in plugin system. Codex and OpenCode require manual setup.
|
||||
|
||||
### Claude Code (via Plugin Marketplace)
|
||||
|
||||
In Claude Code, register the marketplace first:
|
||||
|
||||
```bash
|
||||
# In Claude Code
|
||||
/plugin marketplace add obra/superpowers-marketplace
|
||||
```
|
||||
|
||||
Then install the plugin from this marketplace:
|
||||
|
||||
```bash
|
||||
/plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
That's it! On first session, the plugin automatically:
|
||||
- Sets up `~/.config/superpowers/` as your personal skills repository
|
||||
- Initializes git repo for version control
|
||||
- Makes core skills searchable alongside your personal skills
|
||||
- Adds `/brainstorm`, `/write-plan`, and `/execute-plan` commands
|
||||
- Offers to create public GitHub repo for sharing your skills
|
||||
|
||||
### Verify Installation
|
||||
|
||||
Check that commands appear:
|
||||
|
||||
```bash
|
||||
# Check that commands appear
|
||||
/help
|
||||
```
|
||||
|
||||
```
|
||||
# Should see:
|
||||
# /brainstorm - Interactive design refinement
|
||||
# /write-plan - Create implementation plan
|
||||
# /execute-plan - Execute plan in batches
|
||||
# /superpowers:brainstorm - Interactive design refinement
|
||||
# /superpowers:write-plan - Create implementation plan
|
||||
# /superpowers:execute-plan - Execute plan in batches
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
### Codex
|
||||
|
||||
### Your Personal Skills
|
||||
Tell Codex:
|
||||
|
||||
Write your own skills to `~/.config/superpowers/skills/`:
|
||||
- All personal skills automatically discovered by search tools
|
||||
- Personal skills shadow core skills when names match
|
||||
- Version controlled with git
|
||||
- Optional: Share on GitHub and contribute back to core
|
||||
|
||||
See `skills/meta/writing-skills` for how to create new skills.
|
||||
See `skills/meta/sharing-skills` for how to contribute to core.
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Find both personal and core skills before starting any task:
|
||||
|
||||
```bash
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/find-skills # All skills with descriptions
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/find-skills test # Filter by pattern
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/find-skills 'TDD|debug' # Regex pattern
|
||||
```
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.codex/INSTALL.md
|
||||
```
|
||||
|
||||
### Using Slash Commands
|
||||
**Detailed docs:** [docs/README.codex.md](docs/README.codex.md)
|
||||
|
||||
### OpenCode
|
||||
|
||||
Tell OpenCode:
|
||||
|
||||
**Brainstorm a design:**
|
||||
```
|
||||
/brainstorm
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.opencode/INSTALL.md
|
||||
```
|
||||
|
||||
**Create an implementation plan:**
|
||||
```
|
||||
/write-plan
|
||||
```
|
||||
|
||||
**Execute the plan:**
|
||||
```
|
||||
/execute-plan
|
||||
```
|
||||
**Detailed docs:** [docs/README.opencode.md](docs/README.opencode.md)
|
||||
|
||||
## What's Inside
|
||||
|
||||
### Skills Library
|
||||
|
||||
**Testing** (`skills/testing/`)
|
||||
- test-driven-development - RED-GREEN-REFACTOR cycle
|
||||
- condition-based-waiting - Async test patterns
|
||||
- testing-anti-patterns - Common pitfalls to avoid
|
||||
- **test-driven-development** - RED-GREEN-REFACTOR cycle
|
||||
- **condition-based-waiting** - Async test patterns
|
||||
- **testing-anti-patterns** - Common pitfalls to avoid
|
||||
|
||||
**Debugging** (`skills/debugging/`)
|
||||
- systematic-debugging - 4-phase root cause process
|
||||
- root-cause-tracing - Find the real problem
|
||||
- verification-before-completion - Ensure it's actually fixed
|
||||
- defense-in-depth - Multiple validation layers
|
||||
- **systematic-debugging** - 4-phase root cause process
|
||||
- **root-cause-tracing** - Find the real problem
|
||||
- **verification-before-completion** - Ensure it's actually fixed
|
||||
- **defense-in-depth** - Multiple validation layers
|
||||
|
||||
**Collaboration** (`skills/collaboration/`)
|
||||
- brainstorming - Socratic design refinement
|
||||
- writing-plans - Detailed implementation plans
|
||||
- executing-plans - Batch execution with checkpoints
|
||||
- dispatching-parallel-agents - Concurrent subagent workflows
|
||||
- remembering-conversations - Search past work
|
||||
- using-git-worktrees - Parallel development branches
|
||||
- requesting-code-review - Pre-review checklist
|
||||
- receiving-code-review - Responding to feedback
|
||||
- **brainstorming** - Socratic design refinement
|
||||
- **writing-plans** - Detailed implementation plans
|
||||
- **executing-plans** - Batch execution with checkpoints
|
||||
- **dispatching-parallel-agents** - Concurrent subagent workflows
|
||||
- **requesting-code-review** - Pre-review checklist
|
||||
- **receiving-code-review** - Responding to feedback
|
||||
- **using-git-worktrees** - Parallel development branches
|
||||
- **finishing-a-development-branch** - Merge/PR decision workflow
|
||||
- **subagent-driven-development** - Fast iteration with quality gates
|
||||
|
||||
**Meta** (`skills/meta/`)
|
||||
- setting-up-personal-superpowers - Personal skills repository setup
|
||||
- writing-skills - TDD for documentation, create new skills
|
||||
- sharing-skills - Contribute skills back to core
|
||||
- testing-skills-with-subagents - Validate skill quality
|
||||
- gardening-skills-wiki - Maintain and improve skills
|
||||
|
||||
### Commands
|
||||
|
||||
- **brainstorm.md** - Interactive design refinement using Socratic method
|
||||
- **write-plan.md** - Create detailed implementation plans
|
||||
- **execute-plan.md** - Execute plans in batches with review checkpoints
|
||||
|
||||
### Tools
|
||||
|
||||
**In `scripts/` directory:**
|
||||
- **find-skills** - Unified skill discovery with descriptions (replaces list-skills + skills-search)
|
||||
- **skill-run** - Generic runner for any skill script (searches personal then core)
|
||||
|
||||
**Skill-specific tools:**
|
||||
- **search-conversations** - Semantic search of past Claude sessions (in remembering-conversations skill)
|
||||
|
||||
**Using scripts:**
|
||||
```bash
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/find-skills # Show all skills
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/find-skills pattern # Search skills
|
||||
${CLAUDE_PLUGIN_ROOT}/scripts/skill-run <path> [args] # Run any skill script
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **SessionStart Hook** - Auto-setup personal skills repo, inject core skills context
|
||||
2. **Two-Tier Skills** - Personal skills (`~/.config/superpowers/skills/`) + Core skills (plugin)
|
||||
3. **Skills Discovery** - `find-skills` searches both locations with descriptions
|
||||
4. **Shadowing** - Personal skills override core skills when paths match
|
||||
5. **Mandatory Workflow** - Skills become required when they exist for your task
|
||||
6. **Gap Tracking** - Failed searches logged to `~/.config/superpowers/search-log.jsonl`
|
||||
- **writing-skills** - Create new skills following best practices
|
||||
- **sharing-skills** - Contribute skills back via branch and PR
|
||||
- **testing-skills-with-subagents** - Validate skill quality
|
||||
- **using-superpowers** - Introduction to the skills system
|
||||
|
||||
## Philosophy
|
||||
|
||||
@@ -156,51 +121,28 @@ ${CLAUDE_PLUGIN_ROOT}/scripts/skill-run <path> [args] # Run any skill script
|
||||
- **Systematic over ad-hoc** - Process over guessing
|
||||
- **Complexity reduction** - Simplicity as primary goal
|
||||
- **Evidence over claims** - Verify before declaring success
|
||||
- **Domain over implementation** - Work at problem level, not solution level
|
||||
|
||||
## Personal Superpowers Directory
|
||||
|
||||
The plugin auto-creates your personal superpowers directory on first session.
|
||||
|
||||
**Default location:** `~/.config/superpowers/`
|
||||
|
||||
**Customize via environment variables:**
|
||||
```bash
|
||||
# Option 1: Set exact location
|
||||
export PERSONAL_SUPERPOWERS_DIR="$HOME/my-superpowers"
|
||||
|
||||
# Option 2: Use XDG standard
|
||||
export XDG_CONFIG_HOME="$HOME/.local/config" # Uses $HOME/.local/config/superpowers
|
||||
```
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
~/.config/superpowers/ # (or your custom location)
|
||||
├── .git/ # Git repository
|
||||
├── .gitignore # Ignores logs and indexes
|
||||
├── README.md # About your personal superpowers
|
||||
├── skills/ # Your personal skills
|
||||
│ └── your-skill/
|
||||
│ └── SKILL.md
|
||||
├── search-log.jsonl # Failed skill searches (not tracked)
|
||||
└── conversation-index/ # Indexed conversations (not tracked)
|
||||
```
|
||||
|
||||
**Why git?** Track your skills evolution, share with others, contribute back to core.
|
||||
Read more: [Superpowers for Claude Code](https://blog.fsck.com/2025/10/09/superpowers/)
|
||||
|
||||
## Contributing
|
||||
|
||||
**Write personal skills:**
|
||||
1. Create in `~/.config/superpowers/skills/your-skill/`
|
||||
2. Follow TDD process in `skills/meta/writing-skills`
|
||||
3. Commit to your personal repo
|
||||
Skills live directly in this repository. To contribute:
|
||||
|
||||
**Share with everyone:**
|
||||
1. Follow workflow in `skills/meta/sharing-skills`
|
||||
2. Fork → Branch → Copy → PR to core
|
||||
3. Keep personal version or delete after merge
|
||||
1. Fork the repository
|
||||
2. Create a branch for your skill
|
||||
3. Follow the `writing-skills` skill for creating new skills
|
||||
4. Use the `testing-skills-with-subagents` skill to validate quality
|
||||
5. Submit a PR
|
||||
|
||||
**Missing a skill?** Edit `skills/REQUESTS.md` or open an issue
|
||||
See `skills/meta/writing-skills/SKILL.md` for the complete guide.
|
||||
|
||||
## Updating
|
||||
|
||||
Skills update automatically when you update the plugin:
|
||||
|
||||
```bash
|
||||
/plugin update superpowers
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
||||
474
RELEASE-NOTES.md
Normal file
474
RELEASE-NOTES.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# Superpowers Release Notes
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- **OpenCode Support**: Native JavaScript plugin for OpenCode.ai
|
||||
- Custom tools: `use_skill` and `find_skills`
|
||||
- Message insertion pattern for skill persistence across context compaction
|
||||
- Automatic context injection via chat.message hook
|
||||
- Auto re-injection on session.compacted events
|
||||
- Three-tier skill priority: project > personal > superpowers
|
||||
- Project-local skills support (`.opencode/skills/`)
|
||||
- Shared core module (`lib/skills-core.js`) for code reuse with Codex
|
||||
- Installation guide in `.opencode/INSTALL.md`
|
||||
|
||||
### Changed
|
||||
|
||||
- **Refactored Codex Implementation**: Now uses shared `lib/skills-core.js` ES module
|
||||
- Eliminates code duplication between Codex and OpenCode
|
||||
- Single source of truth for skill discovery and parsing
|
||||
- Codex successfully loads ES modules via Node.js interop
|
||||
|
||||
---
|
||||
|
||||
## v3.4.1 (2025-10-31)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Optimized superpowers bootstrap to eliminate redundant skill execution. The `using-superpowers` skill content is now provided directly in session context, with clear guidance to use the Skill tool only for other skills. This reduces overhead and prevents the confusing loop where agents would execute `using-superpowers` manually despite already having the content from session start.
|
||||
|
||||
## v3.4.0 (2025-10-30)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Simplified `brainstorming` skill to return to original conversational vision. Removed heavyweight 6-phase process with formal checklists in favor of natural dialogue: ask questions one at a time, then present design in 200-300 word sections with validation. Keeps documentation and implementation handoff features.
|
||||
|
||||
## v3.3.1 (2025-10-28)
|
||||
|
||||
### Improvements
|
||||
|
||||
- Updated `brainstorming` skill to require autonomous recon before questioning, encourage recommendation-driven decisions, and prevent agents from delegating prioritization back to humans.
|
||||
- Applied writing clarity improvements to `brainstorming` skill following Strunk's "Elements of Style" principles (omitted needless words, converted negative to positive form, improved parallel construction).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Clarified `writing-skills` guidance so it points to the correct agent-specific personal skill directories (`~/.claude/skills` for Claude Code, `~/.codex/skills` for Codex).
|
||||
|
||||
## v3.3.0 (2025-10-28)
|
||||
|
||||
### New Features
|
||||
|
||||
**Experimental Codex Support**
|
||||
- Added unified `superpowers-codex` script with bootstrap/use-skill/find-skills commands
|
||||
- Cross-platform Node.js implementation (works on Windows, macOS, Linux)
|
||||
- Namespaced skills: `superpowers:skill-name` for superpowers skills, `skill-name` for personal
|
||||
- Personal skills override superpowers skills when names match
|
||||
- Clean skill display: shows name/description without raw frontmatter
|
||||
- Helpful context: shows supporting files directory for each skill
|
||||
- Tool mapping for Codex: TodoWrite→update_plan, subagents→manual fallback, etc.
|
||||
- Bootstrap integration with minimal AGENTS.md for automatic startup
|
||||
- Complete installation guide and bootstrap instructions specific to Codex
|
||||
|
||||
**Key differences from Claude Code integration:**
|
||||
- Single unified script instead of separate tools
|
||||
- Tool substitution system for Codex-specific equivalents
|
||||
- Simplified subagent handling (manual work instead of delegation)
|
||||
- Updated terminology: "Superpowers skills" instead of "Core skills"
|
||||
|
||||
### Files Added
|
||||
- `codex/INSTALL.md` - Installation guide for Codex users
|
||||
- `codex/superpowers-bootstrap.md` - Bootstrap instructions with Codex adaptations
|
||||
- `scripts/superpowers-codex` - Unified Node.js executable with all functionality
|
||||
|
||||
**Note:** Codex support is experimental. The integration provides core superpowers functionality but may require refinement based on user feedback.
|
||||
|
||||
## v3.2.3 (2025-10-23)
|
||||
|
||||
### Improvements
|
||||
|
||||
**Updated using-superpowers skill to use Skill tool instead of Read tool**
|
||||
- Changed skill invocation instructions from Read tool to Skill tool
|
||||
- Updated description: "using Read tool" → "using Skill tool"
|
||||
- Updated step 3: "Use the Read tool" → "Use the Skill tool to read and run"
|
||||
- Updated rationalization list: "Read the current version" → "Run the current version"
|
||||
|
||||
The Skill tool is the proper mechanism for invoking skills in Claude Code. This update corrects the bootstrap instructions to guide agents toward the correct tool.
|
||||
|
||||
### Files Changed
|
||||
- Updated: `skills/using-superpowers/SKILL.md` - Changed tool references from Read to Skill
|
||||
|
||||
## v3.2.2 (2025-10-21)
|
||||
|
||||
### Improvements
|
||||
|
||||
**Strengthened using-superpowers skill against agent rationalization**
|
||||
- Added EXTREMELY-IMPORTANT block with absolute language about mandatory skill checking
|
||||
- "If even 1% chance a skill applies, you MUST read it"
|
||||
- "You do not have a choice. You cannot rationalize your way out."
|
||||
- Added MANDATORY FIRST RESPONSE PROTOCOL checklist
|
||||
- 5-step process agents must complete before any response
|
||||
- Explicit "responding without this = failure" consequence
|
||||
- Added Common Rationalizations section with 8 specific evasion patterns
|
||||
- "This is just a simple question" → WRONG
|
||||
- "I can check files quickly" → WRONG
|
||||
- "Let me gather information first" → WRONG
|
||||
- Plus 5 more common patterns observed in agent behavior
|
||||
|
||||
These changes address observed agent behavior where they rationalize around skill usage despite clear instructions. The forceful language and pre-emptive counter-arguments aim to make non-compliance harder.
|
||||
|
||||
### Files Changed
|
||||
- Updated: `skills/using-superpowers/SKILL.md` - Added three layers of enforcement to prevent skill-skipping rationalization
|
||||
|
||||
## v3.2.1 (2025-10-20)
|
||||
|
||||
### New Features
|
||||
|
||||
**Code reviewer agent now included in plugin**
|
||||
- Added `superpowers:code-reviewer` agent to plugin's `agents/` directory
|
||||
- Agent provides systematic code review against plans and coding standards
|
||||
- Previously required users to have personal agent configuration
|
||||
- All skill references updated to use namespaced `superpowers:code-reviewer`
|
||||
- Fixes #55
|
||||
|
||||
### Files Changed
|
||||
- New: `agents/code-reviewer.md` - Agent definition with review checklist and output format
|
||||
- Updated: `skills/requesting-code-review/SKILL.md` - References to `superpowers:code-reviewer`
|
||||
- Updated: `skills/subagent-driven-development/SKILL.md` - References to `superpowers:code-reviewer`
|
||||
|
||||
## v3.2.0 (2025-10-18)
|
||||
|
||||
### New Features
|
||||
|
||||
**Design documentation in brainstorming workflow**
|
||||
- Added Phase 4: Design Documentation to brainstorming skill
|
||||
- Design documents now written to `docs/plans/YYYY-MM-DD-<topic>-design.md` before implementation
|
||||
- Restores functionality from original brainstorming command that was lost during skill conversion
|
||||
- Documents written before worktree setup and implementation planning
|
||||
- Tested with subagent to verify compliance under time pressure
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**Skill reference namespace standardization**
|
||||
- All internal skill references now use `superpowers:` namespace prefix
|
||||
- Updated format: `superpowers:test-driven-development` (previously just `test-driven-development`)
|
||||
- Affects all REQUIRED SUB-SKILL, RECOMMENDED SUB-SKILL, and REQUIRED BACKGROUND references
|
||||
- Aligns with how skills are invoked using the Skill tool
|
||||
- Files updated: brainstorming, executing-plans, subagent-driven-development, systematic-debugging, testing-skills-with-subagents, writing-plans, writing-skills
|
||||
|
||||
### Improvements
|
||||
|
||||
**Design vs implementation plan naming**
|
||||
- Design documents use `-design.md` suffix to prevent filename collisions
|
||||
- Implementation plans continue using existing `YYYY-MM-DD-<feature-name>.md` format
|
||||
- Both stored in `docs/plans/` directory with clear naming distinction
|
||||
|
||||
## v3.1.1 (2025-10-17)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Fixed command syntax in README** (#44) - Updated all command references to use correct namespaced syntax (`/superpowers:brainstorm` instead of `/brainstorm`). Plugin-provided commands are automatically namespaced by Claude Code to avoid conflicts between plugins.
|
||||
|
||||
## v3.1.0 (2025-10-17)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**Skill names standardized to lowercase**
|
||||
- All skill frontmatter `name:` fields now use lowercase kebab-case matching directory names
|
||||
- Examples: `brainstorming`, `test-driven-development`, `using-git-worktrees`
|
||||
- All skill announcements and cross-references updated to lowercase format
|
||||
- This ensures consistent naming across directory names, frontmatter, and documentation
|
||||
|
||||
### New Features
|
||||
|
||||
**Enhanced brainstorming skill**
|
||||
- Added Quick Reference table showing phases, activities, and tool usage
|
||||
- Added copyable workflow checklist for tracking progress
|
||||
- Added decision flowchart for when to revisit earlier phases
|
||||
- Added comprehensive AskUserQuestion tool guidance with concrete examples
|
||||
- Added "Question Patterns" section explaining when to use structured vs open-ended questions
|
||||
- Restructured Key Principles as scannable table
|
||||
|
||||
**Anthropic best practices integration**
|
||||
- Added `skills/writing-skills/anthropic-best-practices.md` - Official Anthropic skill authoring guide
|
||||
- Referenced in writing-skills SKILL.md for comprehensive guidance
|
||||
- Provides patterns for progressive disclosure, workflows, and evaluation
|
||||
|
||||
### Improvements
|
||||
|
||||
**Skill cross-reference clarity**
|
||||
- All skill references now use explicit requirement markers:
|
||||
- `**REQUIRED BACKGROUND:**` - Prerequisites you must understand
|
||||
- `**REQUIRED SUB-SKILL:**` - Skills that must be used in workflow
|
||||
- `**Complementary skills:**` - Optional but helpful related skills
|
||||
- Removed old path format (`skills/collaboration/X` → just `X`)
|
||||
- Updated Integration sections with categorized relationships (Required vs Complementary)
|
||||
- Updated cross-reference documentation with best practices
|
||||
|
||||
**Alignment with Anthropic best practices**
|
||||
- Fixed description grammar and voice (fully third-person)
|
||||
- Added Quick Reference tables for scanning
|
||||
- Added workflow checklists Claude can copy and track
|
||||
- Appropriate use of flowcharts for non-obvious decision points
|
||||
- Improved scannable table formats
|
||||
- All skills well under 500-line recommendation
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Re-added missing command redirects** - Restored `commands/brainstorm.md` and `commands/write-plan.md` that were accidentally removed in v3.0 migration
|
||||
- Fixed `defense-in-depth` name mismatch (was `Defense-in-Depth-Validation`)
|
||||
- Fixed `receiving-code-review` name mismatch (was `Code-Review-Reception`)
|
||||
- Fixed `commands/brainstorm.md` reference to correct skill name
|
||||
- Removed references to non-existent related skills
|
||||
|
||||
### Documentation
|
||||
|
||||
**writing-skills improvements**
|
||||
- Updated cross-referencing guidance with explicit requirement markers
|
||||
- Added reference to Anthropic's official best practices
|
||||
- Improved examples showing proper skill reference format
|
||||
|
||||
## v3.0.1 (2025-10-16)
|
||||
|
||||
### Changes
|
||||
|
||||
We now use Anthropic's first-party skills system!
|
||||
|
||||
## v2.0.2 (2025-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Fixed false warning when local skills repo is ahead of upstream** - The initialization script was incorrectly warning "New skills available from upstream" when the local repository had commits ahead of upstream. The logic now correctly distinguishes between three git states: local behind (should update), local ahead (no warning), and diverged (should warn).
|
||||
|
||||
## v2.0.1 (2025-10-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Fixed session-start hook execution in plugin context** (#8, PR #9) - The hook was failing silently with "Plugin hook error" preventing skills context from loading. Fixed by:
|
||||
- Using `${BASH_SOURCE[0]:-$0}` fallback when BASH_SOURCE is unbound in Claude Code's execution context
|
||||
- Adding `|| true` to handle empty grep results gracefully when filtering status flags
|
||||
|
||||
---
|
||||
|
||||
# Superpowers v2.0.0 Release Notes
|
||||
|
||||
## Overview
|
||||
|
||||
Superpowers v2.0 makes skills more accessible, maintainable, and community-driven through a major architectural shift.
|
||||
|
||||
The headline change is **skills repository separation**: all skills, scripts, and documentation have moved from the plugin into a dedicated repository ([obra/superpowers-skills](https://github.com/obra/superpowers-skills)). This transforms superpowers from a monolithic plugin into a lightweight shim that manages a local clone of the skills repository. Skills auto-update on session start. Users fork and contribute improvements via standard git workflows. The skills library versions independently from the plugin.
|
||||
|
||||
Beyond infrastructure, this release adds nine new skills focused on problem-solving, research, and architecture. We rewrote the core **using-skills** documentation with imperative tone and clearer structure, making it easier for Claude to understand when and how to use skills. **find-skills** now outputs paths you can paste directly into the Read tool, eliminating friction in the skills discovery workflow.
|
||||
|
||||
Users experience seamless operation: the plugin handles cloning, forking, and updating automatically. Contributors find the new architecture makes improving and sharing skills trivial. This release lays the foundation for skills to evolve rapidly as a community resource.
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Skills Repository Separation
|
||||
|
||||
**The biggest change:** Skills no longer live in the plugin. They've been moved to a separate repository at [obra/superpowers-skills](https://github.com/obra/superpowers-skills).
|
||||
|
||||
**What this means for you:**
|
||||
|
||||
- **First install:** Plugin automatically clones skills to `~/.config/superpowers/skills/`
|
||||
- **Forking:** During setup, you'll be offered the option to fork the skills repo (if `gh` is installed)
|
||||
- **Updates:** Skills auto-update on session start (fast-forward when possible)
|
||||
- **Contributing:** Work on branches, commit locally, submit PRs to upstream
|
||||
- **No more shadowing:** Old two-tier system (personal/core) replaced with single-repo branch workflow
|
||||
|
||||
**Migration:**
|
||||
|
||||
If you have an existing installation:
|
||||
1. Your old `~/.config/superpowers/.git` will be backed up to `~/.config/superpowers/.git.bak`
|
||||
2. Old skills will be backed up to `~/.config/superpowers/skills.bak`
|
||||
3. Fresh clone of obra/superpowers-skills will be created at `~/.config/superpowers/skills/`
|
||||
|
||||
### Removed Features
|
||||
|
||||
- **Personal superpowers overlay system** - Replaced with git branch workflow
|
||||
- **setup-personal-superpowers hook** - Replaced by initialize-skills.sh
|
||||
|
||||
## New Features
|
||||
|
||||
### Skills Repository Infrastructure
|
||||
|
||||
**Automatic Clone & Setup** (`lib/initialize-skills.sh`)
|
||||
- Clones obra/superpowers-skills on first run
|
||||
- Offers fork creation if GitHub CLI is installed
|
||||
- Sets up upstream/origin remotes correctly
|
||||
- Handles migration from old installation
|
||||
|
||||
**Auto-Update**
|
||||
- Fetches from tracking remote on every session start
|
||||
- Auto-merges with fast-forward when possible
|
||||
- Notifies when manual sync needed (branch diverged)
|
||||
- Uses pulling-updates-from-skills-repository skill for manual sync
|
||||
|
||||
### New Skills
|
||||
|
||||
**Problem-Solving Skills** (`skills/problem-solving/`)
|
||||
- **collision-zone-thinking** - Force unrelated concepts together for emergent insights
|
||||
- **inversion-exercise** - Flip assumptions to reveal hidden constraints
|
||||
- **meta-pattern-recognition** - Spot universal principles across domains
|
||||
- **scale-game** - Test at extremes to expose fundamental truths
|
||||
- **simplification-cascades** - Find insights that eliminate multiple components
|
||||
- **when-stuck** - Dispatch to right problem-solving technique
|
||||
|
||||
**Research Skills** (`skills/research/`)
|
||||
- **tracing-knowledge-lineages** - Understand how ideas evolved over time
|
||||
|
||||
**Architecture Skills** (`skills/architecture/`)
|
||||
- **preserving-productive-tensions** - Keep multiple valid approaches instead of forcing premature resolution
|
||||
|
||||
### Skills Improvements
|
||||
|
||||
**using-skills (formerly getting-started)**
|
||||
- Renamed from getting-started to using-skills
|
||||
- Complete rewrite with imperative tone (v4.0.0)
|
||||
- Front-loaded critical rules
|
||||
- Added "Why" explanations for all workflows
|
||||
- Always includes /SKILL.md suffix in references
|
||||
- Clearer distinction between rigid rules and flexible patterns
|
||||
|
||||
**writing-skills**
|
||||
- Cross-referencing guidance moved from using-skills
|
||||
- Added token efficiency section (word count targets)
|
||||
- Improved CSO (Claude Search Optimization) guidance
|
||||
|
||||
**sharing-skills**
|
||||
- Updated for new branch-and-PR workflow (v2.0.0)
|
||||
- Removed personal/core split references
|
||||
|
||||
**pulling-updates-from-skills-repository** (new)
|
||||
- Complete workflow for syncing with upstream
|
||||
- Replaces old "updating-skills" skill
|
||||
|
||||
### Tools Improvements
|
||||
|
||||
**find-skills**
|
||||
- Now outputs full paths with /SKILL.md suffix
|
||||
- Makes paths directly usable with Read tool
|
||||
- Updated help text
|
||||
|
||||
**skill-run**
|
||||
- Moved from scripts/ to skills/using-skills/
|
||||
- Improved documentation
|
||||
|
||||
### Plugin Infrastructure
|
||||
|
||||
**Session Start Hook**
|
||||
- Now loads from skills repository location
|
||||
- Shows full skills list at session start
|
||||
- Prints skills location info
|
||||
- Shows update status (updated successfully / behind upstream)
|
||||
- Moved "skills behind" warning to end of output
|
||||
|
||||
**Environment Variables**
|
||||
- `SUPERPOWERS_SKILLS_ROOT` set to `~/.config/superpowers/skills`
|
||||
- Used consistently throughout all paths
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- Fixed duplicate upstream remote addition when forking
|
||||
- Fixed find-skills double "skills/" prefix in output
|
||||
- Removed obsolete setup-personal-superpowers call from session-start
|
||||
- Fixed path references throughout hooks and commands
|
||||
|
||||
## Documentation
|
||||
|
||||
### README
|
||||
- Updated for new skills repository architecture
|
||||
- Prominent link to superpowers-skills repo
|
||||
- Updated auto-update description
|
||||
- Fixed skill names and references
|
||||
- Updated Meta skills list
|
||||
|
||||
### Testing Documentation
|
||||
- Added comprehensive testing checklist (`docs/TESTING-CHECKLIST.md`)
|
||||
- Created local marketplace config for testing
|
||||
- Documented manual testing scenarios
|
||||
|
||||
## Technical Details
|
||||
|
||||
### File Changes
|
||||
|
||||
**Added:**
|
||||
- `lib/initialize-skills.sh` - Skills repo initialization and auto-update
|
||||
- `docs/TESTING-CHECKLIST.md` - Manual testing scenarios
|
||||
- `.claude-plugin/marketplace.json` - Local testing config
|
||||
|
||||
**Removed:**
|
||||
- `skills/` directory (82 files) - Now in obra/superpowers-skills
|
||||
- `scripts/` directory - Now in obra/superpowers-skills/skills/using-skills/
|
||||
- `hooks/setup-personal-superpowers.sh` - Obsolete
|
||||
|
||||
**Modified:**
|
||||
- `hooks/session-start.sh` - Use skills from ~/.config/superpowers/skills
|
||||
- `commands/brainstorm.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT
|
||||
- `commands/write-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT
|
||||
- `commands/execute-plan.md` - Updated paths to SUPERPOWERS_SKILLS_ROOT
|
||||
- `README.md` - Complete rewrite for new architecture
|
||||
|
||||
### Commit History
|
||||
|
||||
This release includes:
|
||||
- 20+ commits for skills repository separation
|
||||
- PR #1: Amplifier-inspired problem-solving and research skills
|
||||
- PR #2: Personal superpowers overlay system (later replaced)
|
||||
- Multiple skill refinements and documentation improvements
|
||||
|
||||
## Upgrade Instructions
|
||||
|
||||
### Fresh Install
|
||||
|
||||
```bash
|
||||
# In Claude Code
|
||||
/plugin marketplace add obra/superpowers-marketplace
|
||||
/plugin install superpowers@superpowers-marketplace
|
||||
```
|
||||
|
||||
The plugin handles everything automatically.
|
||||
|
||||
### Upgrading from v1.x
|
||||
|
||||
1. **Backup your personal skills** (if you have any):
|
||||
```bash
|
||||
cp -r ~/.config/superpowers/skills ~/superpowers-skills-backup
|
||||
```
|
||||
|
||||
2. **Update the plugin:**
|
||||
```bash
|
||||
/plugin update superpowers
|
||||
```
|
||||
|
||||
3. **On next session start:**
|
||||
- Old installation will be backed up automatically
|
||||
- Fresh skills repo will be cloned
|
||||
- If you have GitHub CLI, you'll be offered the option to fork
|
||||
|
||||
4. **Migrate personal skills** (if you had any):
|
||||
- Create a branch in your local skills repo
|
||||
- Copy your personal skills from backup
|
||||
- Commit and push to your fork
|
||||
- Consider contributing back via PR
|
||||
|
||||
## What's Next
|
||||
|
||||
### For Users
|
||||
|
||||
- Explore the new problem-solving skills
|
||||
- Try the branch-based workflow for skill improvements
|
||||
- Contribute skills back to the community
|
||||
|
||||
### For Contributors
|
||||
|
||||
- Skills repository is now at https://github.com/obra/superpowers-skills
|
||||
- Fork → Branch → PR workflow
|
||||
- See skills/meta/writing-skills/SKILL.md for TDD approach to documentation
|
||||
|
||||
## Known Issues
|
||||
|
||||
None at this time.
|
||||
|
||||
## Credits
|
||||
|
||||
- Problem-solving skills inspired by Amplifier patterns
|
||||
- Community contributions and feedback
|
||||
- Extensive testing and iteration on skill effectiveness
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog:** https://github.com/obra/superpowers/compare/dd013f6...main
|
||||
**Skills Repository:** https://github.com/obra/superpowers-skills
|
||||
**Issues:** https://github.com/obra/superpowers/issues
|
||||
48
agents/code-reviewer.md
Normal file
48
agents/code-reviewer.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: code-reviewer
|
||||
description: |
|
||||
Use this agent when a major project step has been completed and needs to be reviewed against the original plan and coding standards. Examples: <example>Context: The user is creating a code-review agent that should be called after a logical chunk of code is written. user: "I've finished implementing the user authentication system as outlined in step 3 of our plan" assistant: "Great work! Now let me use the code-reviewer agent to review the implementation against our plan and coding standards" <commentary>Since a major project step has been completed, use the code-reviewer agent to validate the work against the plan and identify any issues.</commentary></example> <example>Context: User has completed a significant feature implementation. user: "The API endpoints for the task management system are now complete - that covers step 2 from our architecture document" assistant: "Excellent! Let me have the code-reviewer agent examine this implementation to ensure it aligns with our plan and follows best practices" <commentary>A numbered step from the planning document has been completed, so the code-reviewer agent should review the work.</commentary></example>
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a Senior Code Reviewer with expertise in software architecture, design patterns, and best practices. Your role is to review completed project steps against original plans and ensure code quality standards are met.
|
||||
|
||||
When reviewing completed work, you will:
|
||||
|
||||
1. **Plan Alignment Analysis**:
|
||||
- Compare the implementation against the original planning document or step description
|
||||
- Identify any deviations from the planned approach, architecture, or requirements
|
||||
- Assess whether deviations are justified improvements or problematic departures
|
||||
- Verify that all planned functionality has been implemented
|
||||
|
||||
2. **Code Quality Assessment**:
|
||||
- Review code for adherence to established patterns and conventions
|
||||
- Check for proper error handling, type safety, and defensive programming
|
||||
- Evaluate code organization, naming conventions, and maintainability
|
||||
- Assess test coverage and quality of test implementations
|
||||
- Look for potential security vulnerabilities or performance issues
|
||||
|
||||
3. **Architecture and Design Review**:
|
||||
- Ensure the implementation follows SOLID principles and established architectural patterns
|
||||
- Check for proper separation of concerns and loose coupling
|
||||
- Verify that the code integrates well with existing systems
|
||||
- Assess scalability and extensibility considerations
|
||||
|
||||
4. **Documentation and Standards**:
|
||||
- Verify that code includes appropriate comments and documentation
|
||||
- Check that file headers, function documentation, and inline comments are present and accurate
|
||||
- Ensure adherence to project-specific coding standards and conventions
|
||||
|
||||
5. **Issue Identification and Recommendations**:
|
||||
- Clearly categorize issues as: Critical (must fix), Important (should fix), or Suggestions (nice to have)
|
||||
- For each issue, provide specific examples and actionable recommendations
|
||||
- When you identify plan deviations, explain whether they're problematic or beneficial
|
||||
- Suggest specific improvements with code examples when helpful
|
||||
|
||||
6. **Communication Protocol**:
|
||||
- If you find significant deviations from the plan, ask the coding agent to review and confirm the changes
|
||||
- If you identify issues with the original plan itself, recommend plan updates
|
||||
- For implementation problems, provide clear guidance on fixes needed
|
||||
- Always acknowledge what was done well before highlighting issues
|
||||
|
||||
Your output should be structured, actionable, and focused on helping maintain high code quality while ensuring project goals are met. Be thorough but concise, and always provide constructive feedback that helps improve both the current implementation and future development practices.
|
||||
@@ -1,36 +0,0 @@
|
||||
# Claude Commands
|
||||
|
||||
Slash commands for Claude that reference skills.
|
||||
|
||||
## Available Commands
|
||||
|
||||
- `/brainstorm` - Interactive idea refinement using Socratic method (→ `@skills/collaboration/brainstorming/SKILL.md`)
|
||||
- `/write-plan` - Create detailed implementation plan (→ `@skills/collaboration/writing-plans/SKILL.md`)
|
||||
- `/execute-plan` - Execute plan in batches with review (→ `@skills/collaboration/executing-plans/SKILL.md`)
|
||||
|
||||
## Format
|
||||
|
||||
Each command is a simple markdown file containing a single `@` reference to a skill:
|
||||
|
||||
```markdown
|
||||
@skills/collaboration/brainstorming/SKILL.md
|
||||
```
|
||||
|
||||
When you run the command (e.g., `/brainstorm`), Claude loads and follows that skill.
|
||||
|
||||
## Creating Custom Commands
|
||||
|
||||
To add your own commands:
|
||||
|
||||
1. Create `your-command.md` in this directory
|
||||
2. Add a single line referencing a skill:
|
||||
```markdown
|
||||
@skills/your-category/your-skill/SKILL.md
|
||||
```
|
||||
3. The command `/your-command` is now available
|
||||
|
||||
## Installation
|
||||
|
||||
These commands are automatically symlinked to `~/.claude/commands/` by the clank installer.
|
||||
|
||||
See `@skills/meta/installing-skills/SKILL.md` for installation details.
|
||||
@@ -2,4 +2,4 @@
|
||||
description: Interactive design refinement using Socratic method
|
||||
---
|
||||
|
||||
Read and follow: ${CLAUDE_PLUGIN_ROOT}/skills/collaboration/brainstorming/SKILL.md
|
||||
Use and follow the brainstorming skill exactly as written
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
description: Execute plan in batches with review checkpoints
|
||||
---
|
||||
|
||||
Read and follow: ${CLAUDE_PLUGIN_ROOT}/skills/collaboration/executing-plans/SKILL.md
|
||||
Use the executing-plans skill exactly as written
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
description: Create detailed implementation plan with bite-sized tasks
|
||||
---
|
||||
|
||||
Read and follow: ${CLAUDE_PLUGIN_ROOT}/skills/collaboration/writing-plans/SKILL.md
|
||||
Use the writing-plans skill exactly as written
|
||||
|
||||
153
docs/README.codex.md
Normal file
153
docs/README.codex.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# Superpowers for Codex
|
||||
|
||||
Complete guide for using Superpowers with OpenAI Codex.
|
||||
|
||||
## Quick Install
|
||||
|
||||
Tell Codex:
|
||||
|
||||
```
|
||||
Fetch and follow instructions from https://raw.githubusercontent.com/obra/superpowers/refs/heads/main/.codex/INSTALL.md
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- OpenAI Codex access
|
||||
- Shell access to install files
|
||||
|
||||
### Installation Steps
|
||||
|
||||
#### 1. Clone Superpowers
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.codex/superpowers
|
||||
```
|
||||
|
||||
#### 2. Install Bootstrap
|
||||
|
||||
The bootstrap file is included in the repository at `.codex/superpowers-bootstrap.md`. Codex will automatically use it from the cloned location.
|
||||
|
||||
#### 3. Verify Installation
|
||||
|
||||
Tell Codex:
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex find-skills to show available skills
|
||||
```
|
||||
|
||||
You should see a list of available skills with descriptions.
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex find-skills
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex use-skill superpowers:brainstorming
|
||||
```
|
||||
|
||||
### Bootstrap All Skills
|
||||
|
||||
```
|
||||
Run ~/.codex/superpowers/.codex/superpowers-codex bootstrap
|
||||
```
|
||||
|
||||
This loads the complete bootstrap with all skill information.
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.codex/skills/`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.codex/skills/my-skill
|
||||
```
|
||||
|
||||
Create `~/.codex/skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
Personal skills override superpowers skills with the same name.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Codex CLI Tool
|
||||
|
||||
**Location:** `~/.codex/superpowers/.codex/superpowers-codex`
|
||||
|
||||
A Node.js CLI script that provides three commands:
|
||||
- `bootstrap` - Load complete bootstrap with all skills
|
||||
- `use-skill <name>` - Load a specific skill
|
||||
- `find-skills` - List all available skills
|
||||
|
||||
### Shared Core Module
|
||||
|
||||
**Location:** `~/.codex/superpowers/lib/skills-core.js`
|
||||
|
||||
The Codex implementation uses the shared `skills-core` module (ES module format) for skill discovery and parsing. This is the same module used by the OpenCode plugin, ensuring consistent behavior across platforms.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are adapted for Codex with these mappings:
|
||||
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → Tell user subagents aren't available, do work directly
|
||||
- `Skill` tool → `~/.codex/superpowers/.codex/superpowers-codex use-skill`
|
||||
- File operations → Native Codex tools
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.codex/superpowers
|
||||
git pull
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Skills not found
|
||||
|
||||
1. Verify installation: `ls ~/.codex/superpowers/skills`
|
||||
2. Check CLI works: `~/.codex/superpowers/.codex/superpowers-codex find-skills`
|
||||
3. Verify skills have SKILL.md files
|
||||
|
||||
### CLI script not executable
|
||||
|
||||
```bash
|
||||
chmod +x ~/.codex/superpowers/.codex/superpowers-codex
|
||||
```
|
||||
|
||||
### Node.js errors
|
||||
|
||||
The CLI script requires Node.js. Verify:
|
||||
|
||||
```bash
|
||||
node --version
|
||||
```
|
||||
|
||||
Should show v14 or higher (v18+ recommended for ES module support).
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Main documentation: https://github.com/obra/superpowers
|
||||
- Blog post: https://blog.fsck.com/2025/10/27/skills-for-openai-codex/
|
||||
|
||||
## Note
|
||||
|
||||
Codex support is experimental and may require refinement based on user feedback. If you encounter issues, please report them on GitHub.
|
||||
234
docs/README.opencode.md
Normal file
234
docs/README.opencode.md
Normal file
@@ -0,0 +1,234 @@
|
||||
# Superpowers for OpenCode
|
||||
|
||||
Complete guide for using Superpowers with [OpenCode.ai](https://opencode.ai).
|
||||
|
||||
## Quick Install
|
||||
|
||||
Tell OpenCode:
|
||||
|
||||
```
|
||||
Clone https://github.com/obra/superpowers to ~/.config/opencode/superpowers, then create directory ~/.config/opencode/plugin, then symlink ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js to ~/.config/opencode/plugin/superpowers.js, then restart opencode.
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [OpenCode.ai](https://opencode.ai) installed
|
||||
- Node.js installed
|
||||
- Git installed
|
||||
|
||||
### Installation Steps
|
||||
|
||||
#### 1. Install Superpowers
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/superpowers
|
||||
git clone https://github.com/obra/superpowers.git ~/.config/opencode/superpowers
|
||||
```
|
||||
|
||||
#### 2. Register the Plugin
|
||||
|
||||
OpenCode discovers plugins from `~/.config/opencode/plugin/`. Create a symlink:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js ~/.config/opencode/plugin/superpowers.js
|
||||
```
|
||||
|
||||
Alternatively, for project-local installation:
|
||||
|
||||
```bash
|
||||
# In your OpenCode project
|
||||
mkdir -p .opencode/plugin
|
||||
ln -sf ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js .opencode/plugin/superpowers.js
|
||||
```
|
||||
|
||||
#### 3. Restart OpenCode
|
||||
|
||||
Restart OpenCode to load the plugin. Superpowers will automatically activate.
|
||||
|
||||
## Usage
|
||||
|
||||
### Finding Skills
|
||||
|
||||
Use the `find_skills` tool to list all available skills:
|
||||
|
||||
```
|
||||
use find_skills tool
|
||||
```
|
||||
|
||||
### Loading a Skill
|
||||
|
||||
Use the `use_skill` tool to load a specific skill:
|
||||
|
||||
```
|
||||
use use_skill tool with skill_name: "superpowers:brainstorming"
|
||||
```
|
||||
|
||||
Skills are automatically inserted into the conversation and persist across context compaction.
|
||||
|
||||
### Personal Skills
|
||||
|
||||
Create your own skills in `~/.config/opencode/skills/`:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/opencode/skills/my-skill
|
||||
```
|
||||
|
||||
Create `~/.config/opencode/skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
### Project Skills
|
||||
|
||||
Create project-specific skills in your OpenCode project:
|
||||
|
||||
```bash
|
||||
# In your OpenCode project
|
||||
mkdir -p .opencode/skills/my-project-skill
|
||||
```
|
||||
|
||||
Create `.opencode/skills/my-project-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-project-skill
|
||||
description: Use when [condition] - [what it does]
|
||||
---
|
||||
|
||||
# My Project Skill
|
||||
|
||||
[Your skill content here]
|
||||
```
|
||||
|
||||
## Skill Priority
|
||||
|
||||
Skills are resolved with this priority order:
|
||||
|
||||
1. **Project skills** (`.opencode/skills/`) - Highest priority
|
||||
2. **Personal skills** (`~/.config/opencode/skills/`)
|
||||
3. **Superpowers skills** (`~/.config/opencode/superpowers/skills/`)
|
||||
|
||||
You can force resolution to a specific level:
|
||||
- `project:skill-name` - Force project skill
|
||||
- `skill-name` - Search project → personal → superpowers
|
||||
- `superpowers:skill-name` - Force superpowers skill
|
||||
|
||||
## Features
|
||||
|
||||
### Automatic Context Injection
|
||||
|
||||
The plugin automatically injects superpowers context via the chat.message hook on every session. No manual configuration needed.
|
||||
|
||||
### Message Insertion Pattern
|
||||
|
||||
When you load a skill with `use_skill`, it's inserted as a user message with `noReply: true`. This ensures skills persist throughout long conversations, even when OpenCode compacts context.
|
||||
|
||||
### Compaction Resilience
|
||||
|
||||
The plugin listens for `session.compacted` events and automatically re-injects the core superpowers bootstrap to maintain functionality after context compaction.
|
||||
|
||||
### Tool Mapping
|
||||
|
||||
Skills written for Claude Code are automatically adapted for OpenCode. The plugin provides mapping instructions:
|
||||
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → OpenCode's `@mention` system
|
||||
- `Skill` tool → `use_skill` custom tool
|
||||
- File operations → Native OpenCode tools
|
||||
|
||||
## Architecture
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
**Location:** `~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
|
||||
**Components:**
|
||||
- Two custom tools: `use_skill`, `find_skills`
|
||||
- chat.message hook for initial context injection
|
||||
- event handler for session.compacted re-injection
|
||||
- Uses shared `lib/skills-core.js` module (also used by Codex)
|
||||
|
||||
### Shared Core Module
|
||||
|
||||
**Location:** `~/.config/opencode/superpowers/lib/skills-core.js`
|
||||
|
||||
**Functions:**
|
||||
- `extractFrontmatter()` - Parse skill metadata
|
||||
- `stripFrontmatter()` - Remove metadata from content
|
||||
- `findSkillsInDir()` - Recursive skill discovery
|
||||
- `resolveSkillPath()` - Skill resolution with shadowing
|
||||
- `checkForUpdates()` - Git update detection
|
||||
|
||||
This module is shared between OpenCode and Codex implementations for code reuse.
|
||||
|
||||
## Updating
|
||||
|
||||
```bash
|
||||
cd ~/.config/opencode/superpowers
|
||||
git pull
|
||||
```
|
||||
|
||||
Restart OpenCode to load the updates.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin not loading
|
||||
|
||||
1. Check plugin file exists: `ls ~/.config/opencode/superpowers/.opencode/plugin/superpowers.js`
|
||||
2. Check symlink: `ls -l ~/.config/opencode/plugin/superpowers.js`
|
||||
3. Check OpenCode logs: `opencode run "test" --print-logs --log-level DEBUG`
|
||||
4. Look for: `service=plugin path=file:///.../superpowers.js loading plugin`
|
||||
|
||||
### Skills not found
|
||||
|
||||
1. Verify skills directory: `ls ~/.config/opencode/superpowers/skills`
|
||||
2. Use `find_skills` tool to see what's discovered
|
||||
3. Check skill structure: each skill needs a `SKILL.md` file
|
||||
|
||||
### Tools not working
|
||||
|
||||
1. Verify plugin loaded: Check OpenCode logs for plugin loading message
|
||||
2. Check Node.js version: The plugin requires Node.js for ES modules
|
||||
3. Test plugin manually: `node --input-type=module -e "import('file://~/.config/opencode/plugin/superpowers.js').then(m => console.log(Object.keys(m)))"`
|
||||
|
||||
### Context not injecting
|
||||
|
||||
1. Check if chat.message hook is working
|
||||
2. Verify using-superpowers skill exists
|
||||
3. Check OpenCode version (requires recent version with plugin support)
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Report issues: https://github.com/obra/superpowers/issues
|
||||
- Main documentation: https://github.com/obra/superpowers
|
||||
- OpenCode docs: https://opencode.ai/docs/
|
||||
|
||||
## Testing
|
||||
|
||||
The implementation includes an automated test suite at `tests/opencode/`:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./tests/opencode/run-tests.sh --integration --verbose
|
||||
|
||||
# Run specific test
|
||||
./tests/opencode/run-tests.sh --test test-tools.sh
|
||||
```
|
||||
|
||||
Tests verify:
|
||||
- Plugin loading
|
||||
- Skills-core library functionality
|
||||
- Tool execution (use_skill, find_skills)
|
||||
- Skill priority resolution
|
||||
- Proper isolation with temp HOME
|
||||
294
docs/plans/2025-11-22-opencode-support-design.md
Normal file
294
docs/plans/2025-11-22-opencode-support-design.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# OpenCode Support Design
|
||||
|
||||
**Date:** 2025-11-22
|
||||
**Author:** Bot & Jesse
|
||||
**Status:** Design Complete, Awaiting Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Add full superpowers support for OpenCode.ai using a native OpenCode plugin architecture that shares core functionality with the existing Codex implementation.
|
||||
|
||||
## Background
|
||||
|
||||
OpenCode.ai is a coding agent similar to Claude Code and Codex. Previous attempts to port superpowers to OpenCode (PR #93, PR #116) used file-copying approaches. This design takes a different approach: building a native OpenCode plugin using their JavaScript/TypeScript plugin system while sharing code with the Codex implementation.
|
||||
|
||||
### Key Differences Between Platforms
|
||||
|
||||
- **Claude Code**: Native Anthropic plugin system + file-based skills
|
||||
- **Codex**: No plugin system → bootstrap markdown + CLI script
|
||||
- **OpenCode**: JavaScript/TypeScript plugins with event hooks and custom tools API
|
||||
|
||||
### OpenCode's Agent System
|
||||
|
||||
- **Primary agents**: Build (default, full access) and Plan (restricted, read-only)
|
||||
- **Subagents**: General (research, searching, multi-step tasks)
|
||||
- **Invocation**: Automatic dispatch by primary agents OR manual `@mention` syntax
|
||||
- **Configuration**: Custom agents in `opencode.json` or `~/.config/opencode/agent/`
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Structure
|
||||
|
||||
1. **Shared Core Module** (`lib/skills-core.js`)
|
||||
- Common skill discovery and parsing logic
|
||||
- Used by both Codex and OpenCode implementations
|
||||
|
||||
2. **Platform-Specific Wrappers**
|
||||
- Codex: CLI script (`.codex/superpowers-codex`)
|
||||
- OpenCode: Plugin module (`.opencode/plugin/superpowers.js`)
|
||||
|
||||
3. **Skill Directories**
|
||||
- Core: `~/.config/opencode/superpowers/skills/` (or installed location)
|
||||
- Personal: `~/.config/opencode/skills/` (shadows core skills)
|
||||
|
||||
### Code Reuse Strategy
|
||||
|
||||
Extract common functionality from `.codex/superpowers-codex` into shared module:
|
||||
|
||||
```javascript
|
||||
// lib/skills-core.js
|
||||
module.exports = {
|
||||
extractFrontmatter(filePath), // Parse name + description from YAML
|
||||
findSkillsInDir(dir, maxDepth), // Recursive SKILL.md discovery
|
||||
findAllSkills(dirs), // Scan multiple directories
|
||||
resolveSkillPath(skillName, dirs), // Handle shadowing (personal > core)
|
||||
checkForUpdates(repoDir) // Git fetch/status check
|
||||
};
|
||||
```
|
||||
|
||||
### Skill Frontmatter Format
|
||||
|
||||
Current format (no `when_to_use` field):
|
||||
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: Use when [condition] - [what it does]; [additional context]
|
||||
---
|
||||
```
|
||||
|
||||
## OpenCode Plugin Implementation
|
||||
|
||||
### Custom Tools
|
||||
|
||||
**Tool 1: `use_skill`**
|
||||
|
||||
Loads a specific skill's content into the conversation (equivalent to Claude's Skill tool).
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'use_skill',
|
||||
description: 'Load and read a specific skill to guide your work',
|
||||
schema: z.object({
|
||||
skill_name: z.string().describe('Name of skill (e.g., "superpowers:brainstorming")')
|
||||
}),
|
||||
execute: async ({ skill_name }) => {
|
||||
const { skillPath, content, frontmatter } = resolveAndReadSkill(skill_name);
|
||||
const skillDir = path.dirname(skillPath);
|
||||
|
||||
return `# ${frontmatter.name}
|
||||
# ${frontmatter.description}
|
||||
# Supporting tools and docs are in ${skillDir}
|
||||
# ============================================
|
||||
|
||||
${content}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tool 2: `find_skills`**
|
||||
|
||||
Lists all available skills with metadata.
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'find_skills',
|
||||
description: 'List all available skills',
|
||||
schema: z.object({}),
|
||||
execute: async () => {
|
||||
const skills = discoverAllSkills();
|
||||
return skills.map(s =>
|
||||
`${s.namespace}:${s.name}
|
||||
${s.description}
|
||||
Directory: ${s.directory}
|
||||
`).join('\n');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Session Startup Hook
|
||||
|
||||
When a new session starts (`session.started` event):
|
||||
|
||||
1. **Inject using-superpowers content**
|
||||
- Full content of the using-superpowers skill
|
||||
- Establishes mandatory workflows
|
||||
|
||||
2. **Run find_skills automatically**
|
||||
- Display full list of available skills upfront
|
||||
- Include skill directories for each
|
||||
|
||||
3. **Inject tool mapping instructions**
|
||||
```markdown
|
||||
**Tool Mapping for OpenCode:**
|
||||
When skills reference tools you don't have, substitute:
|
||||
- `TodoWrite` → `update_plan`
|
||||
- `Task` with subagents → Use OpenCode subagent system (@mention)
|
||||
- `Skill` tool → `use_skill` custom tool
|
||||
- Read, Write, Edit, Bash → Your native equivalents
|
||||
|
||||
**Skill directories contain:**
|
||||
- Supporting scripts (run with bash)
|
||||
- Additional documentation (read with read tool)
|
||||
- Utilities specific to that skill
|
||||
```
|
||||
|
||||
4. **Check for updates** (non-blocking)
|
||||
- Quick git fetch with timeout
|
||||
- Notify if updates available
|
||||
|
||||
### Plugin Structure
|
||||
|
||||
```javascript
|
||||
// .opencode/plugin/superpowers.js
|
||||
const skillsCore = require('../../lib/skills-core');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { z } = require('zod');
|
||||
|
||||
export const SuperpowersPlugin = async ({ client, directory, $ }) => {
|
||||
const superpowersDir = path.join(process.env.HOME, '.config/opencode/superpowers');
|
||||
const personalDir = path.join(process.env.HOME, '.config/opencode/skills');
|
||||
|
||||
return {
|
||||
'session.started': async () => {
|
||||
const usingSuperpowers = await readSkill('using-superpowers');
|
||||
const skillsList = await findAllSkills();
|
||||
const toolMapping = getToolMappingInstructions();
|
||||
|
||||
return {
|
||||
context: `${usingSuperpowers}\n\n${skillsList}\n\n${toolMapping}`
|
||||
};
|
||||
},
|
||||
|
||||
tools: [
|
||||
{
|
||||
name: 'use_skill',
|
||||
description: 'Load and read a specific skill',
|
||||
schema: z.object({
|
||||
skill_name: z.string()
|
||||
}),
|
||||
execute: async ({ skill_name }) => {
|
||||
// Implementation using skillsCore
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_skills',
|
||||
description: 'List all available skills',
|
||||
schema: z.object({}),
|
||||
execute: async () => {
|
||||
// Implementation using skillsCore
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
superpowers/
|
||||
├── lib/
|
||||
│ └── skills-core.js # NEW: Shared skill logic
|
||||
├── .codex/
|
||||
│ ├── superpowers-codex # UPDATED: Use skills-core
|
||||
│ ├── superpowers-bootstrap.md
|
||||
│ └── INSTALL.md
|
||||
├── .opencode/
|
||||
│ ├── plugin/
|
||||
│ │ └── superpowers.js # NEW: OpenCode plugin
|
||||
│ └── INSTALL.md # NEW: Installation guide
|
||||
└── skills/ # Unchanged
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Refactor Shared Core
|
||||
|
||||
1. Create `lib/skills-core.js`
|
||||
- Extract frontmatter parsing from `.codex/superpowers-codex`
|
||||
- Extract skill discovery logic
|
||||
- Extract path resolution (with shadowing)
|
||||
- Update to use only `name` and `description` (no `when_to_use`)
|
||||
|
||||
2. Update `.codex/superpowers-codex` to use shared core
|
||||
- Import from `../lib/skills-core.js`
|
||||
- Remove duplicated code
|
||||
- Keep CLI wrapper logic
|
||||
|
||||
3. Test Codex implementation still works
|
||||
- Verify bootstrap command
|
||||
- Verify use-skill command
|
||||
- Verify find-skills command
|
||||
|
||||
### Phase 2: Build OpenCode Plugin
|
||||
|
||||
1. Create `.opencode/plugin/superpowers.js`
|
||||
- Import shared core from `../../lib/skills-core.js`
|
||||
- Implement plugin function
|
||||
- Define custom tools (use_skill, find_skills)
|
||||
- Implement session.started hook
|
||||
|
||||
2. Create `.opencode/INSTALL.md`
|
||||
- Installation instructions
|
||||
- Directory setup
|
||||
- Configuration guidance
|
||||
|
||||
3. Test OpenCode implementation
|
||||
- Verify session startup bootstrap
|
||||
- Verify use_skill tool works
|
||||
- Verify find_skills tool works
|
||||
- Verify skill directories are accessible
|
||||
|
||||
### Phase 3: Documentation & Polish
|
||||
|
||||
1. Update README with OpenCode support
|
||||
2. Add OpenCode installation to main docs
|
||||
3. Update RELEASE-NOTES
|
||||
4. Test both Codex and OpenCode work correctly
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Create isolated workspace** (using git worktrees)
|
||||
- Branch: `feature/opencode-support`
|
||||
|
||||
2. **Follow TDD where applicable**
|
||||
- Test shared core functions
|
||||
- Test skill discovery and parsing
|
||||
- Integration tests for both platforms
|
||||
|
||||
3. **Incremental implementation**
|
||||
- Phase 1: Refactor shared core + update Codex
|
||||
- Verify Codex still works before moving on
|
||||
- Phase 2: Build OpenCode plugin
|
||||
- Phase 3: Documentation and polish
|
||||
|
||||
4. **Testing strategy**
|
||||
- Manual testing with real OpenCode installation
|
||||
- Verify skill loading, directories, scripts work
|
||||
- Test both Codex and OpenCode side-by-side
|
||||
- Verify tool mappings work correctly
|
||||
|
||||
5. **PR and merge**
|
||||
- Create PR with complete implementation
|
||||
- Test in clean environment
|
||||
- Merge to main
|
||||
|
||||
## Benefits
|
||||
|
||||
- **Code reuse**: Single source of truth for skill discovery/parsing
|
||||
- **Maintainability**: Bug fixes apply to both platforms
|
||||
- **Extensibility**: Easy to add future platforms (Cursor, Windsurf, etc.)
|
||||
- **Native integration**: Uses OpenCode's plugin system properly
|
||||
- **Consistency**: Same skill experience across all platforms
|
||||
1095
docs/plans/2025-11-22-opencode-support-implementation.md
Normal file
1095
docs/plans/2025-11-22-opencode-support-implementation.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,35 +3,30 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Run personal superpowers setup
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
setup_output=$("${SCRIPT_DIR}/setup-personal-superpowers.sh" 2>&1 || echo "setup_failed=true")
|
||||
# Determine plugin root directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# Use same directory resolution as setup script
|
||||
SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
|
||||
# Check if GitHub CLI is available and setup succeeded
|
||||
github_recommendation=""
|
||||
if echo "$setup_output" | grep -q "github_cli_available=true"; then
|
||||
if [[ ! -d "$SUPERPOWERS_DIR/.git" ]]; then
|
||||
# This should not happen, but handle gracefully
|
||||
github_recommendation=""
|
||||
else
|
||||
# Check if remote already exists
|
||||
if ! (cd "$SUPERPOWERS_DIR" && git remote get-url origin &>/dev/null); then
|
||||
github_recommendation="\n\n💡 Want to share your personal skills on GitHub? Superpowers are best when everyone can learn from them! I can create a 'personal-superpowers' repo for you."
|
||||
fi
|
||||
fi
|
||||
elif echo "$setup_output" | grep -q "setup_failed=true"; then
|
||||
github_recommendation="\n\n⚠️ Personal superpowers setup encountered an issue. Please file a bug at https://github.com/obra/superpowers/issues"
|
||||
# Check if legacy skills directory exists and build warning
|
||||
warning_message=""
|
||||
legacy_skills_dir="${HOME}/.config/superpowers/skills"
|
||||
if [ -d "$legacy_skills_dir" ]; then
|
||||
warning_message="\n\n<important-reminder>IN YOUR FIRST REPLY AFTER SEEING THIS MESSAGE YOU MUST TELL THE USER:⚠️ **WARNING:** Superpowers now uses Claude Code's skills system. Custom skills in ~/.config/superpowers/skills will not be read. Move custom skills to ~/.claude/skills instead. To make this message go away, remove ~/.config/superpowers/skills</important-reminder>"
|
||||
fi
|
||||
|
||||
# Read using-superpowers content
|
||||
using_superpowers_content=$(cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md" 2>&1 || echo "Error reading using-superpowers skill")
|
||||
|
||||
# Escape outputs for JSON
|
||||
using_superpowers_escaped=$(echo "$using_superpowers_content" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')
|
||||
warning_escaped=$(echo "$warning_message" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')
|
||||
|
||||
# Output context injection as JSON
|
||||
cat <<EOF
|
||||
{
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "SessionStart",
|
||||
"additionalContext": "<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Skill tools are at:**\n- find-skills: ${CLAUDE_PLUGIN_ROOT}/scripts/find-skills\n- skill-run: ${CLAUDE_PLUGIN_ROOT}/scripts/skill-run\n\n**RIGHT NOW, go read**: @${CLAUDE_PLUGIN_ROOT}/skills/getting-started/SKILL.md${github_recommendation}\n</EXTREMELY_IMPORTANT>"
|
||||
"additionalContext": "<EXTREMELY_IMPORTANT>\nYou have superpowers.\n\n**Below is the full content of your 'superpowers:using-superpowers' skill - your introduction to using skills. For all other skills, use the 'Skill' tool:**\n\n${using_superpowers_escaped}\n\n${warning_escaped}\n</EXTREMELY_IMPORTANT>"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Setup script for personal superpowers directory
|
||||
# Creates personal superpowers directory with git repo for personal skills
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Use PERSONAL_SUPERPOWERS_DIR if set, otherwise XDG_CONFIG_HOME/superpowers, otherwise ~/.config/superpowers
|
||||
SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
SKILLS_DIR="${SUPERPOWERS_DIR}/skills"
|
||||
|
||||
# Check if already set up
|
||||
if [[ -d "${SUPERPOWERS_DIR}/.git" ]] && [[ -d "${SKILLS_DIR}" ]]; then
|
||||
# Already set up, nothing to do
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create directory structure
|
||||
mkdir -p "${SKILLS_DIR}"
|
||||
|
||||
# Create .gitignore
|
||||
cat > "${SUPERPOWERS_DIR}/.gitignore" <<'EOF'
|
||||
# Superpowers local data
|
||||
search-log.jsonl
|
||||
conversation-index/
|
||||
conversation-archive/
|
||||
EOF
|
||||
|
||||
# Create README
|
||||
cat > "${SUPERPOWERS_DIR}/README.md" <<'EOF'
|
||||
# My Personal Superpowers
|
||||
|
||||
Personal skills and techniques for Claude Code.
|
||||
|
||||
Learn more about Superpowers: https://github.com/obra/superpowers
|
||||
EOF
|
||||
|
||||
# Initialize git repo if not already initialized
|
||||
if [[ ! -d "${SUPERPOWERS_DIR}/.git" ]]; then
|
||||
cd "${SUPERPOWERS_DIR}"
|
||||
git init -q
|
||||
git add .gitignore README.md
|
||||
git commit -q -m "Initial commit: Personal superpowers setup"
|
||||
fi
|
||||
|
||||
# Check for gh and recommend GitHub setup
|
||||
if command -v gh &> /dev/null; then
|
||||
echo "github_cli_available=true"
|
||||
else
|
||||
echo "github_cli_available=false"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
208
lib/skills-core.js
Normal file
208
lib/skills-core.js
Normal file
@@ -0,0 +1,208 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
/**
|
||||
* Extract YAML frontmatter from a skill file.
|
||||
* Current format:
|
||||
* ---
|
||||
* name: skill-name
|
||||
* description: Use when [condition] - [what it does]
|
||||
* ---
|
||||
*
|
||||
* @param {string} filePath - Path to SKILL.md file
|
||||
* @returns {{name: string, description: string}}
|
||||
*/
|
||||
function extractFrontmatter(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
let inFrontmatter = false;
|
||||
let name = '';
|
||||
let description = '';
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '---') {
|
||||
if (inFrontmatter) break;
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFrontmatter) {
|
||||
const match = line.match(/^(\w+):\s*(.*)$/);
|
||||
if (match) {
|
||||
const [, key, value] = match;
|
||||
switch (key) {
|
||||
case 'name':
|
||||
name = value.trim();
|
||||
break;
|
||||
case 'description':
|
||||
description = value.trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { name, description };
|
||||
} catch (error) {
|
||||
return { name: '', description: '' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all SKILL.md files in a directory recursively.
|
||||
*
|
||||
* @param {string} dir - Directory to search
|
||||
* @param {string} sourceType - 'personal' or 'superpowers' for namespacing
|
||||
* @param {number} maxDepth - Maximum recursion depth (default: 3)
|
||||
* @returns {Array<{path: string, name: string, description: string, sourceType: string}>}
|
||||
*/
|
||||
function findSkillsInDir(dir, sourceType, maxDepth = 3) {
|
||||
const skills = [];
|
||||
|
||||
if (!fs.existsSync(dir)) return skills;
|
||||
|
||||
function recurse(currentDir, depth) {
|
||||
if (depth > maxDepth) return;
|
||||
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Check for SKILL.md in this directory
|
||||
const skillFile = path.join(fullPath, 'SKILL.md');
|
||||
if (fs.existsSync(skillFile)) {
|
||||
const { name, description } = extractFrontmatter(skillFile);
|
||||
skills.push({
|
||||
path: fullPath,
|
||||
skillFile: skillFile,
|
||||
name: name || entry.name,
|
||||
description: description || '',
|
||||
sourceType: sourceType
|
||||
});
|
||||
}
|
||||
|
||||
// Recurse into subdirectories
|
||||
recurse(fullPath, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(dir, 0);
|
||||
return skills;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a skill name to its file path, handling shadowing
|
||||
* (personal skills override superpowers skills).
|
||||
*
|
||||
* @param {string} skillName - Name like "superpowers:brainstorming" or "my-skill"
|
||||
* @param {string} superpowersDir - Path to superpowers skills directory
|
||||
* @param {string} personalDir - Path to personal skills directory
|
||||
* @returns {{skillFile: string, sourceType: string, skillPath: string} | null}
|
||||
*/
|
||||
function resolveSkillPath(skillName, superpowersDir, personalDir) {
|
||||
// Strip superpowers: prefix if present
|
||||
const forceSuperpowers = skillName.startsWith('superpowers:');
|
||||
const actualSkillName = forceSuperpowers ? skillName.replace(/^superpowers:/, '') : skillName;
|
||||
|
||||
// Try personal skills first (unless explicitly superpowers:)
|
||||
if (!forceSuperpowers && personalDir) {
|
||||
const personalPath = path.join(personalDir, actualSkillName);
|
||||
const personalSkillFile = path.join(personalPath, 'SKILL.md');
|
||||
if (fs.existsSync(personalSkillFile)) {
|
||||
return {
|
||||
skillFile: personalSkillFile,
|
||||
sourceType: 'personal',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Try superpowers skills
|
||||
if (superpowersDir) {
|
||||
const superpowersPath = path.join(superpowersDir, actualSkillName);
|
||||
const superpowersSkillFile = path.join(superpowersPath, 'SKILL.md');
|
||||
if (fs.existsSync(superpowersSkillFile)) {
|
||||
return {
|
||||
skillFile: superpowersSkillFile,
|
||||
sourceType: 'superpowers',
|
||||
skillPath: actualSkillName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a git repository has updates available.
|
||||
*
|
||||
* @param {string} repoDir - Path to git repository
|
||||
* @returns {boolean} - True if updates are available
|
||||
*/
|
||||
function checkForUpdates(repoDir) {
|
||||
try {
|
||||
// Quick check with 3 second timeout to avoid delays if network is down
|
||||
const output = execSync('git fetch origin && git status --porcelain=v1 --branch', {
|
||||
cwd: repoDir,
|
||||
timeout: 3000,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
// Parse git status output to see if we're behind
|
||||
const statusLines = output.split('\n');
|
||||
for (const line of statusLines) {
|
||||
if (line.startsWith('## ') && line.includes('[behind ')) {
|
||||
return true; // We're behind remote
|
||||
}
|
||||
}
|
||||
return false; // Up to date
|
||||
} catch (error) {
|
||||
// Network down, git error, timeout, etc. - don't block bootstrap
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip YAML frontmatter from skill content, returning just the content.
|
||||
*
|
||||
* @param {string} content - Full content including frontmatter
|
||||
* @returns {string} - Content without frontmatter
|
||||
*/
|
||||
function stripFrontmatter(content) {
|
||||
const lines = content.split('\n');
|
||||
let inFrontmatter = false;
|
||||
let frontmatterEnded = false;
|
||||
const contentLines = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === '---') {
|
||||
if (inFrontmatter) {
|
||||
frontmatterEnded = true;
|
||||
continue;
|
||||
}
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (frontmatterEnded || !inFrontmatter) {
|
||||
contentLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
return contentLines.join('\n').trim();
|
||||
}
|
||||
|
||||
export {
|
||||
extractFrontmatter,
|
||||
findSkillsInDir,
|
||||
resolveSkillPath,
|
||||
checkForUpdates,
|
||||
stripFrontmatter
|
||||
};
|
||||
@@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# find-skills - Find and list skills with descriptions
|
||||
# Shows all skills by default, filters by pattern if provided
|
||||
# Searches personal superpowers first, then core (personal shadows core)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Determine directories
|
||||
PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
PERSONAL_SKILLS_DIR="${PERSONAL_SUPERPOWERS_DIR}/skills"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
CORE_SKILLS_DIR="${PLUGIN_ROOT}/skills"
|
||||
|
||||
LOG_FILE="${PERSONAL_SUPERPOWERS_DIR}/search-log.jsonl"
|
||||
|
||||
# Show help
|
||||
if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
|
||||
cat <<'EOF'
|
||||
find-skills - Find and list skills with descriptions
|
||||
|
||||
USAGE:
|
||||
find-skills Show all skills with descriptions
|
||||
find-skills PATTERN Filter skills by grep pattern
|
||||
find-skills --help Show this help
|
||||
|
||||
EXAMPLES:
|
||||
find-skills # All skills
|
||||
find-skills test # Skills matching "test"
|
||||
find-skills 'test.*driven|TDD' # Regex pattern
|
||||
|
||||
OUTPUT:
|
||||
Each line shows: skill-path - description
|
||||
Personal skills listed first, then core skills
|
||||
Personal skills shadow core skills when paths match
|
||||
|
||||
SEARCH:
|
||||
Searches both skill content AND path names.
|
||||
Personal skills at: ~/.config/superpowers/skills/
|
||||
Core skills at: plugin installation directory
|
||||
EOF
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get pattern (optional)
|
||||
PATTERN="${1:-}"
|
||||
|
||||
# Function to extract description from SKILL.md
|
||||
get_description() {
|
||||
local file="$1"
|
||||
grep "^description:" "$file" 2>/dev/null | sed 's/description: *//' || echo ""
|
||||
}
|
||||
|
||||
# Function to get relative skill path
|
||||
get_skill_path() {
|
||||
local file="$1"
|
||||
local base_dir="$2"
|
||||
local rel_path="${file#$base_dir/}"
|
||||
echo "${rel_path%/SKILL.md}"
|
||||
}
|
||||
|
||||
# Collect all matching skills (use simple list for bash 3.2 compatibility)
|
||||
seen_skills_list=""
|
||||
results=()
|
||||
|
||||
# If pattern provided, log the search
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
echo "{\"timestamp\":\"$timestamp\",\"query\":\"$PATTERN\"}" >> "$LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Search personal skills first
|
||||
if [[ -d "$PERSONAL_SKILLS_DIR" ]]; then
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
|
||||
skill_path=$(get_skill_path "$file" "$PERSONAL_SKILLS_DIR")
|
||||
description=$(get_description "$file")
|
||||
|
||||
seen_skills_list="${seen_skills_list}${skill_path}"$'\n'
|
||||
results+=("$skill_path|$description")
|
||||
done < <(
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
# Pattern mode: search content and paths
|
||||
{
|
||||
grep -E -r "$PATTERN" "$PERSONAL_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
|
||||
find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
|
||||
} | sort -u
|
||||
else
|
||||
# Show all
|
||||
find "$PERSONAL_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
|
||||
fi
|
||||
)
|
||||
fi
|
||||
|
||||
# Search core skills (only if not shadowed)
|
||||
while IFS= read -r file; do
|
||||
[[ -z "$file" ]] && continue
|
||||
|
||||
skill_path=$(get_skill_path "$file" "$CORE_SKILLS_DIR")
|
||||
|
||||
# Skip if shadowed by personal skill
|
||||
echo "$seen_skills_list" | grep -q "^${skill_path}$" && continue
|
||||
|
||||
description=$(get_description "$file")
|
||||
results+=("$skill_path|$description")
|
||||
done < <(
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
# Pattern mode: search content and paths
|
||||
{
|
||||
grep -E -r "$PATTERN" "$CORE_SKILLS_DIR/" --include="SKILL.md" -l 2>/dev/null || true
|
||||
find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null | grep -E "$PATTERN" 2>/dev/null || true
|
||||
} | sort -u
|
||||
else
|
||||
# Show all
|
||||
find "$CORE_SKILLS_DIR/" -name "SKILL.md" -type f 2>/dev/null || true
|
||||
fi
|
||||
)
|
||||
|
||||
# Check if we found anything
|
||||
if [[ ${#results[@]} -eq 0 ]]; then
|
||||
if [[ -n "$PATTERN" ]]; then
|
||||
echo "❌ No skills found matching: $PATTERN"
|
||||
echo ""
|
||||
echo "Search logged. If a skill should exist, consider writing it!"
|
||||
else
|
||||
echo "❌ No skills found"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sort and display results
|
||||
printf "%s\n" "${results[@]}" | sort | while IFS='|' read -r skill_path description; do
|
||||
if [[ -n "$description" ]]; then
|
||||
echo "skills/$skill_path - $description"
|
||||
else
|
||||
echo "skills/$skill_path"
|
||||
fi
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generic runner for skill scripts
|
||||
# Searches personal superpowers first, then core plugin
|
||||
#
|
||||
# Usage: scripts/skill-run <skill-relative-path> [args...]
|
||||
# Example: scripts/skill-run skills/collaboration/remembering-conversations/tool/search-conversations "query"
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -eq 0 ]]; then
|
||||
cat <<'EOF'
|
||||
Usage: scripts/skill-run <skill-relative-path> [args...]
|
||||
|
||||
Runs scripts from skills, checking personal superpowers first, then core.
|
||||
|
||||
Examples:
|
||||
scripts/skill-run skills/collaboration/remembering-conversations/tool/search-conversations "query"
|
||||
scripts/skill-run skills/collaboration/remembering-conversations/tool/index-conversations --cleanup
|
||||
|
||||
The script will be found at:
|
||||
1. ~/.config/superpowers/<skill-relative-path> (personal, if exists)
|
||||
2. ${CLAUDE_PLUGIN_ROOT}/<skill-relative-path> (core plugin)
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the script path to run
|
||||
SCRIPT_PATH="$1"
|
||||
shift # Remove script path from args, leaving remaining args
|
||||
|
||||
# Determine directories
|
||||
PERSONAL_SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Try personal superpowers first
|
||||
PERSONAL_SCRIPT="${PERSONAL_SUPERPOWERS_DIR}/${SCRIPT_PATH}"
|
||||
if [[ -x "$PERSONAL_SCRIPT" ]]; then
|
||||
exec "$PERSONAL_SCRIPT" "$@"
|
||||
fi
|
||||
|
||||
# Fall back to core plugin
|
||||
CORE_SCRIPT="${PLUGIN_ROOT}/${SCRIPT_PATH}"
|
||||
if [[ -x "$CORE_SCRIPT" ]]; then
|
||||
exec "$CORE_SCRIPT" "$@"
|
||||
fi
|
||||
|
||||
# Not found
|
||||
echo "Error: Script not found: $SCRIPT_PATH" >&2
|
||||
echo "" >&2
|
||||
echo "Searched:" >&2
|
||||
echo " $PERSONAL_SCRIPT (personal)" >&2
|
||||
echo " $CORE_SCRIPT (core)" >&2
|
||||
exit 1
|
||||
@@ -1,36 +0,0 @@
|
||||
# Skill Requests
|
||||
|
||||
Use this page to document skills you wish existed. Add requests here when you encounter situations where a skill would have helped.
|
||||
|
||||
## Format
|
||||
|
||||
```markdown
|
||||
## [Short Descriptive Name]
|
||||
**What I need:** One-line description
|
||||
**When I'd use it:** Specific situations/symptoms
|
||||
**Why I need this:** What makes this non-obvious or worth capturing
|
||||
**Added:** YYYY-MM-DD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current Requests
|
||||
|
||||
(None yet - add requests below as you discover needs)
|
||||
|
||||
---
|
||||
|
||||
## Completed Requests
|
||||
|
||||
Skills that have been created from this list will move here with links.
|
||||
|
||||
---
|
||||
|
||||
## Guidelines
|
||||
|
||||
- **Be specific** - "Flaky test debugging" not "testing help"
|
||||
- **Include symptoms** - Error messages, behavior patterns
|
||||
- **Explain non-obvious** - Why can't you just figure this out?
|
||||
- **One skill per request** - Keep them focused
|
||||
|
||||
your human partner reviews this periodically and we create skills together.
|
||||
54
skills/brainstorming/SKILL.md
Normal file
54
skills/brainstorming/SKILL.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: brainstorming
|
||||
description: Use when creating or developing, before writing code or implementation plans - refines rough ideas into fully-formed designs through collaborative questioning, alternative exploration, and incremental validation. Don't use during clear 'mechanical' processes
|
||||
---
|
||||
|
||||
# Brainstorming Ideas Into Designs
|
||||
|
||||
## Overview
|
||||
|
||||
Help turn ideas into fully formed designs and specs through natural collaborative dialogue.
|
||||
|
||||
Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design in small sections (200-300 words), checking after each section whether it looks right so far.
|
||||
|
||||
## The Process
|
||||
|
||||
**Understanding the idea:**
|
||||
- Check out the current project state first (files, docs, recent commits)
|
||||
- Ask questions one at a time to refine the idea
|
||||
- Prefer multiple choice questions when possible, but open-ended is fine too
|
||||
- Only one question per message - if a topic needs more exploration, break it into multiple questions
|
||||
- Focus on understanding: purpose, constraints, success criteria
|
||||
|
||||
**Exploring approaches:**
|
||||
- Propose 2-3 different approaches with trade-offs
|
||||
- Present options conversationally with your recommendation and reasoning
|
||||
- Lead with your recommended option and explain why
|
||||
|
||||
**Presenting the design:**
|
||||
- Once you believe you understand what you're building, present the design
|
||||
- Break it into sections of 200-300 words
|
||||
- Ask after each section whether it looks right so far
|
||||
- Cover: architecture, components, data flow, error handling, testing
|
||||
- Be ready to go back and clarify if something doesn't make sense
|
||||
|
||||
## After the Design
|
||||
|
||||
**Documentation:**
|
||||
- Write the validated design to `docs/plans/YYYY-MM-DD-<topic>-design.md`
|
||||
- Use elements-of-style:writing-clearly-and-concisely skill if available
|
||||
- Commit the design document to git
|
||||
|
||||
**Implementation (if continuing):**
|
||||
- Ask: "Ready to set up for implementation?"
|
||||
- Use superpowers:using-git-worktrees to create isolated workspace
|
||||
- Use superpowers:writing-plans to create detailed implementation plan
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **One question at a time** - Don't overwhelm with multiple questions
|
||||
- **Multiple choice preferred** - Easier to answer than open-ended when possible
|
||||
- **YAGNI ruthlessly** - Remove unnecessary features from all designs
|
||||
- **Explore alternatives** - Always propose 2-3 approaches before settling
|
||||
- **Incremental validation** - Present design in sections, validate each
|
||||
- **Be flexible** - Go back and clarify when something doesn't make sense
|
||||
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: Brainstorming Ideas Into Designs
|
||||
description: Interactive idea refinement using Socratic method to develop fully-formed designs
|
||||
when_to_use: When your human partner says "I've got an idea", "Let's make/build/create", "I want to implement/add", "What if we". When starting design for complex feature. Before writing implementation plans. When idea needs refinement and exploration. ACTIVATE THIS AUTOMATICALLY when your human partner describes a feature or project idea - don't wait for /brainstorm command.
|
||||
version: 2.0.0
|
||||
---
|
||||
|
||||
# Brainstorming Ideas Into Designs
|
||||
|
||||
## Overview
|
||||
|
||||
Transform rough ideas into fully-formed designs through structured questioning and alternative exploration.
|
||||
|
||||
**Core principle:** Ask questions to understand, explore alternatives, present design incrementally for validation.
|
||||
|
||||
**Announce at start:** "I'm using the Brainstorming skill to refine your idea into a design."
|
||||
|
||||
## The Process
|
||||
|
||||
### Phase 1: Understanding
|
||||
- Check current project state in working directory
|
||||
- Ask ONE question at a time to refine the idea
|
||||
- Prefer multiple choice when possible
|
||||
- Gather: Purpose, constraints, success criteria
|
||||
|
||||
### Phase 2: Exploration
|
||||
- Propose 2-3 different approaches (reference skills/coding/exploring-alternatives)
|
||||
- For each: Core architecture, trade-offs, complexity assessment
|
||||
- Ask your human partner which approach resonates
|
||||
|
||||
### Phase 3: Design Presentation
|
||||
- Present in 200-300 word sections
|
||||
- Cover: Architecture, components, data flow, error handling, testing
|
||||
- Ask after each section: "Does this look right so far?"
|
||||
|
||||
### Phase 4: Worktree Setup (for implementation)
|
||||
When design is approved and implementation will follow:
|
||||
- Announce: "I'm using the Using Git Worktrees skill to set up an isolated workspace."
|
||||
- Switch to skills/collaboration/using-git-worktrees
|
||||
- Follow that skill's process for directory selection, safety verification, and setup
|
||||
- Return here when worktree ready
|
||||
|
||||
### Phase 5: Planning Handoff
|
||||
Ask: "Ready to create the implementation plan?"
|
||||
|
||||
When your human partner confirms (any affirmative response):
|
||||
- Announce: "I'm using the Writing Plans skill to create the implementation plan."
|
||||
- Switch to skills/collaboration/writing-plans skill
|
||||
- Create detailed plan in the worktree
|
||||
|
||||
## Remember
|
||||
- One question per message during Phase 1
|
||||
- Apply YAGNI ruthlessly (reference skills/architecture/reducing-complexity)
|
||||
- Explore 2-3 alternatives before settling
|
||||
- Present incrementally, validate as you go
|
||||
- Announce skill usage at start
|
||||
@@ -1,329 +0,0 @@
|
||||
# Conversation Search Deployment Guide
|
||||
|
||||
Quick reference for deploying and maintaining the conversation indexing system.
|
||||
|
||||
## Initial Deployment
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/collaboration/remembering-conversations/tool
|
||||
|
||||
# 1. Install hook
|
||||
./install-hook
|
||||
|
||||
# 2. Index existing conversations (with parallel summarization)
|
||||
./index-conversations --cleanup --concurrency 8
|
||||
|
||||
# 3. Verify index health
|
||||
./index-conversations --verify
|
||||
|
||||
# 4. Test search
|
||||
./search-conversations "test query"
|
||||
```
|
||||
|
||||
**Expected results:**
|
||||
- Hook installed at `~/.claude/hooks/sessionEnd`
|
||||
- Summaries created for all conversations (50-120 words each)
|
||||
- Search returns relevant results in <1 second
|
||||
- No verification errors
|
||||
|
||||
**Performance tip:** Use `--concurrency 8` or `--concurrency 16` for 8-16x faster summarization on initial indexing. Hook uses concurrency=1 (safe for background).
|
||||
|
||||
## Ongoing Maintenance
|
||||
|
||||
### Automatic (No Action Required)
|
||||
|
||||
- Hook runs after every session ends
|
||||
- New conversations indexed in background (<30 sec per conversation)
|
||||
- Summaries generated automatically
|
||||
|
||||
### Weekly Health Check
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/collaboration/remembering-conversations/tool
|
||||
./index-conversations --verify
|
||||
```
|
||||
|
||||
If issues found:
|
||||
```bash
|
||||
./index-conversations --repair
|
||||
```
|
||||
|
||||
### After System Changes
|
||||
|
||||
| Change | Action |
|
||||
|--------|--------|
|
||||
| Moved conversation archive | Update paths in code, run `--rebuild` |
|
||||
| Updated CLAUDE.md | Run `--verify` to check for issues |
|
||||
| Changed database schema | Backup DB, run `--rebuild` |
|
||||
| Hook not running | Check executable: `chmod +x ~/.claude/hooks/sessionEnd` |
|
||||
|
||||
## Recovery Scenarios
|
||||
|
||||
| Issue | Diagnosis | Fix |
|
||||
|-------|-----------|-----|
|
||||
| **Missing summaries** | `--verify` shows "Missing summaries: N" | `--repair` regenerates missing summaries |
|
||||
| **Orphaned DB entries** | `--verify` shows "Orphaned entries: N" | `--repair` removes orphaned entries |
|
||||
| **Outdated indexes** | `--verify` shows "Outdated files: N" | `--repair` re-indexes modified files |
|
||||
| **Corrupted database** | Errors during search/verify | `--rebuild` (re-indexes everything, requires confirmation) |
|
||||
| **Hook not running** | No summaries for new conversations | See Troubleshooting below |
|
||||
| **Slow indexing** | Takes >30 sec per conversation | Check API key, network, Haiku fallback in logs |
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
```bash
|
||||
# Check hook installed and executable
|
||||
ls -l ~/.claude/hooks/sessionEnd
|
||||
|
||||
# Check recent conversations
|
||||
ls -lt ~/.config/superpowers/conversation-archive/*/*.jsonl | head -5
|
||||
|
||||
# Check database size
|
||||
ls -lh ~/.config/superpowers/conversation-index/db.sqlite
|
||||
|
||||
# Full verification
|
||||
./index-conversations --verify
|
||||
```
|
||||
|
||||
### Expected Behavior Metrics
|
||||
|
||||
- **Hook execution:** Within seconds of session end
|
||||
- **Indexing speed:** <30 seconds per conversation
|
||||
- **Summary length:** 50-120 words
|
||||
- **Search latency:** <1 second
|
||||
- **Verification:** 0 errors when healthy
|
||||
|
||||
### Log Output
|
||||
|
||||
Normal indexing:
|
||||
```
|
||||
Initializing database...
|
||||
Loading embedding model...
|
||||
Processing project: my-project (3 conversations)
|
||||
Summary: 87 words
|
||||
Indexed conversation.jsonl: 5 exchanges
|
||||
✅ Indexing complete! Conversations: 3, Exchanges: 15
|
||||
```
|
||||
|
||||
Verification with issues:
|
||||
```
|
||||
Verifying conversation index...
|
||||
Verified 100 conversations.
|
||||
|
||||
=== Verification Results ===
|
||||
Missing summaries: 2
|
||||
Orphaned entries: 0
|
||||
Outdated files: 1
|
||||
Corrupted files: 0
|
||||
|
||||
Run with --repair to fix these issues.
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Hook Not Running
|
||||
|
||||
**Symptoms:** New conversations not indexed automatically
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# 1. Check hook exists and is executable
|
||||
ls -l ~/.claude/hooks/sessionEnd
|
||||
# Should show: -rwxr-xr-x ... sessionEnd
|
||||
|
||||
# 2. Check $SESSION_ID is set during sessions
|
||||
echo $SESSION_ID
|
||||
# Should show: session ID when in active session
|
||||
|
||||
# 3. Check indexer exists
|
||||
ls -l ~/.claude/skills/collaboration/remembering-conversations/tool/index-conversations
|
||||
# Should show: -rwxr-xr-x ... index-conversations
|
||||
|
||||
# 4. Test hook manually
|
||||
SESSION_ID=test-$(date +%s) ~/.claude/hooks/sessionEnd
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# Make hook executable
|
||||
chmod +x ~/.claude/hooks/sessionEnd
|
||||
|
||||
# Reinstall if needed
|
||||
./install-hook
|
||||
```
|
||||
|
||||
### Summaries Failing
|
||||
|
||||
**Symptoms:** Verify shows missing summaries, repair fails
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# Check API key
|
||||
echo $ANTHROPIC_API_KEY
|
||||
# Should show: sk-ant-...
|
||||
|
||||
# Try manual indexing with logging
|
||||
./index-conversations 2>&1 | tee index.log
|
||||
grep -i error index.log
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# Set API key if missing
|
||||
export ANTHROPIC_API_KEY="your-key-here"
|
||||
|
||||
# Check for rate limits (wait and retry)
|
||||
sleep 60 && ./index-conversations --repair
|
||||
|
||||
# Fallback uses claude-3-haiku-20240307 (cheaper)
|
||||
# Check logs for: "Summary: N words" to confirm success
|
||||
```
|
||||
|
||||
### Search Not Finding Results
|
||||
|
||||
**Symptoms:** `./search-conversations "query"` returns no results
|
||||
|
||||
**Diagnosis:**
|
||||
```bash
|
||||
# 1. Verify conversations indexed
|
||||
./index-conversations --verify
|
||||
|
||||
# 2. Check database exists and has data
|
||||
ls -lh ~/.config/superpowers/conversation-index/db.sqlite
|
||||
# Should be > 100KB if conversations indexed
|
||||
|
||||
# 3. Try text search (exact match)
|
||||
./search-conversations --text "exact phrase from conversation"
|
||||
|
||||
# 4. Check for corruption
|
||||
sqlite3 ~/.config/superpowers/conversation-index/db.sqlite "SELECT COUNT(*) FROM exchanges;"
|
||||
# Should show number > 0
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# If database missing or corrupt
|
||||
./index-conversations --rebuild
|
||||
|
||||
# If specific conversations missing
|
||||
./index-conversations --repair
|
||||
|
||||
# If still failing, check embedding model
|
||||
rm -rf ~/.cache/transformers # Force re-download
|
||||
./index-conversations
|
||||
```
|
||||
|
||||
### Database Corruption
|
||||
|
||||
**Symptoms:** Errors like "database disk image is malformed"
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# 1. Backup current database
|
||||
cp ~/.config/superpowers/conversation-index/db.sqlite ~/.config/superpowers/conversation-index/db.sqlite.backup
|
||||
|
||||
# 2. Rebuild from scratch
|
||||
./index-conversations --rebuild
|
||||
# Confirms with: "Are you sure? [yes/NO]:"
|
||||
# Type: yes
|
||||
|
||||
# 3. Verify rebuild
|
||||
./index-conversations --verify
|
||||
```
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Index all conversations
|
||||
./index-conversations
|
||||
|
||||
# Index specific session (called by hook)
|
||||
./index-conversations --session <session-id>
|
||||
|
||||
# Index only unprocessed conversations
|
||||
./index-conversations --cleanup
|
||||
|
||||
# Verify index health
|
||||
./index-conversations --verify
|
||||
|
||||
# Repair issues found by verify
|
||||
./index-conversations --repair
|
||||
|
||||
# Rebuild everything (with confirmation)
|
||||
./index-conversations --rebuild
|
||||
|
||||
# Search conversations (semantic)
|
||||
./search-conversations "query"
|
||||
|
||||
# Search conversations (text match)
|
||||
./search-conversations --text "exact phrase"
|
||||
|
||||
# Install/reinstall hook
|
||||
./install-hook
|
||||
```
|
||||
|
||||
## Subagent Workflow
|
||||
|
||||
**For searching conversations from within Claude Code sessions**, use the subagent pattern (see `skills/getting-started` for complete workflow).
|
||||
|
||||
**Template:** `tool/prompts/search-agent.md`
|
||||
|
||||
**Key requirements:**
|
||||
- Synthesis must be 200-1000 words (Summary section)
|
||||
- All sources must include: project, date, file path, status
|
||||
- No raw conversation excerpts (synthesize instead)
|
||||
- Follow-up via subagent (not direct file reads)
|
||||
|
||||
**Manual test checklist:**
|
||||
1. ✓ Dispatch subagent with search template
|
||||
2. ✓ Verify synthesis 200-1000 words
|
||||
3. ✓ Verify all sources have metadata (project, date, path, status)
|
||||
4. ✓ Ask follow-up → dispatch second subagent to dig deeper
|
||||
5. ✓ Confirm no raw conversations in main context
|
||||
|
||||
## Files and Directories
|
||||
|
||||
```
|
||||
~/.claude/
|
||||
├── hooks/
|
||||
│ └── sessionEnd # Hook that triggers indexing
|
||||
└── skills/collaboration/remembering-conversations/
|
||||
├── SKILL.md # Main documentation
|
||||
├── DEPLOYMENT.md # This file
|
||||
└── tool/
|
||||
├── index-conversations # Main indexer
|
||||
├── search-conversations # Search interface
|
||||
├── install-hook # Hook installer
|
||||
├── test-deployment.sh # End-to-end tests
|
||||
├── src/ # TypeScript source
|
||||
└── prompts/
|
||||
└── search-agent.md # Subagent template
|
||||
|
||||
~/.config/superpowers/
|
||||
├── conversation-archive/ # Archived conversations
|
||||
│ └── <project>/
|
||||
│ ├── <uuid>.jsonl # Conversation file
|
||||
│ └── <uuid>-summary.txt # AI summary (50-120 words)
|
||||
└── conversation-index/
|
||||
└── db.sqlite # SQLite database with embeddings
|
||||
```
|
||||
|
||||
## Deployment Checklist
|
||||
|
||||
### Initial Setup
|
||||
- [ ] Hook installed: `./install-hook`
|
||||
- [ ] Existing conversations indexed: `./index-conversations`
|
||||
- [ ] Verification clean: `./index-conversations --verify`
|
||||
- [ ] Search working: `./search-conversations "test"`
|
||||
- [ ] Subagent template exists: `ls tool/prompts/search-agent.md`
|
||||
|
||||
### Ongoing
|
||||
- [ ] Weekly: Run `--verify` and `--repair` if needed
|
||||
- [ ] After system changes: Re-verify
|
||||
- [ ] Monitor: Check hook runs (summaries appear for new conversations)
|
||||
|
||||
### Testing
|
||||
- [ ] Run end-to-end tests: `./test-deployment.sh`
|
||||
- [ ] All 5 scenarios pass
|
||||
- [ ] Manual subagent test (see scenario 5 in test output)
|
||||
@@ -1,133 +0,0 @@
|
||||
# Managing Conversation Index
|
||||
|
||||
Index, archive, and maintain conversations for search.
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Install auto-indexing hook:**
|
||||
```bash
|
||||
~/.claude/skills/collaboration/remembering-conversations/tool/install-hook
|
||||
```
|
||||
|
||||
**Index all conversations:**
|
||||
```bash
|
||||
~/.claude/skills/collaboration/remembering-conversations/tool/index-conversations
|
||||
```
|
||||
|
||||
**Process unindexed only:**
|
||||
```bash
|
||||
~/.claude/skills/collaboration/remembering-conversations/tool/index-conversations --cleanup
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic indexing** via sessionEnd hook (install once, forget)
|
||||
- **Semantic search** across all past conversations
|
||||
- **AI summaries** (Claude Haiku with Sonnet fallback)
|
||||
- **Recovery modes** (verify, repair, rebuild)
|
||||
- **Permanent archive** at `~/.config/superpowers/conversation-archive/`
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. Install Hook (One-Time)
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/collaboration/remembering-conversations/tool
|
||||
./install-hook
|
||||
```
|
||||
|
||||
Handles existing hooks gracefully (merge or replace). Runs in background after each session.
|
||||
|
||||
### 2. Index Existing Conversations
|
||||
|
||||
```bash
|
||||
# Index everything
|
||||
./index-conversations
|
||||
|
||||
# Or just unindexed (faster, cheaper)
|
||||
./index-conversations --cleanup
|
||||
```
|
||||
|
||||
## Index Modes
|
||||
|
||||
```bash
|
||||
# Index all (first run or full rebuild)
|
||||
./index-conversations
|
||||
|
||||
# Index specific session (used by hook)
|
||||
./index-conversations --session <uuid>
|
||||
|
||||
# Process only unindexed (missing summaries)
|
||||
./index-conversations --cleanup
|
||||
|
||||
# Check index health
|
||||
./index-conversations --verify
|
||||
|
||||
# Fix detected issues
|
||||
./index-conversations --repair
|
||||
|
||||
# Nuclear option (deletes DB, re-indexes everything)
|
||||
./index-conversations --rebuild
|
||||
```
|
||||
|
||||
## Recovery Scenarios
|
||||
|
||||
| Situation | Command |
|
||||
|-----------|---------|
|
||||
| Missed conversations | `--cleanup` |
|
||||
| Hook didn't run | `--cleanup` |
|
||||
| Updated conversation | `--verify` then `--repair` |
|
||||
| Corrupted database | `--rebuild` |
|
||||
| Index health check | `--verify` |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Hook not running:**
|
||||
- Check: `ls -l ~/.claude/hooks/sessionEnd` (should be executable)
|
||||
- Test: `SESSION_ID=test-$(date +%s) ~/.claude/hooks/sessionEnd`
|
||||
- Re-install: `./install-hook`
|
||||
|
||||
**Summaries failing:**
|
||||
- Check API key: `echo $ANTHROPIC_API_KEY`
|
||||
- Check logs in ~/.config/superpowers/conversation-index/
|
||||
- Try manual: `./index-conversations --session <uuid>`
|
||||
|
||||
**Search not finding results:**
|
||||
- Verify indexed: `./index-conversations --verify`
|
||||
- Try text search: `./search-conversations --text "exact phrase"`
|
||||
- Rebuild if needed: `./index-conversations --rebuild`
|
||||
|
||||
## Excluding Projects
|
||||
|
||||
To exclude specific projects from indexing (e.g., meta-conversations), create:
|
||||
|
||||
`~/.config/superpowers/conversation-index/exclude.txt`
|
||||
```
|
||||
# One project name per line
|
||||
# Lines starting with # are comments
|
||||
-Users-yourname-Documents-some-project
|
||||
```
|
||||
|
||||
Or set env variable:
|
||||
```bash
|
||||
export CONVERSATION_SEARCH_EXCLUDE_PROJECTS="project1,project2"
|
||||
```
|
||||
|
||||
## Storage
|
||||
|
||||
- **Archive:** `~/.config/superpowers/conversation-archive/<project>/<uuid>.jsonl`
|
||||
- **Summaries:** `~/.config/superpowers/conversation-archive/<project>/<uuid>-summary.txt`
|
||||
- **Database:** `~/.config/superpowers/conversation-index/db.sqlite`
|
||||
- **Exclusions:** `~/.config/superpowers/conversation-index/exclude.txt` (optional)
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Embeddings:** @xenova/transformers (all-MiniLM-L6-v2, 384 dimensions, local/free)
|
||||
- **Vector search:** sqlite-vec (local/free)
|
||||
- **Summaries:** Claude Haiku with Sonnet fallback (~$0.01-0.02/conversation)
|
||||
- **Parser:** Handles multi-message exchanges and sidechains
|
||||
|
||||
## See Also
|
||||
|
||||
- **Searching:** See SKILL.md for search modes (vector, text, time filtering)
|
||||
- **Deployment:** See DEPLOYMENT.md for production runbook
|
||||
@@ -1,69 +0,0 @@
|
||||
---
|
||||
name: Remembering Conversations
|
||||
description: Search previous Claude Code conversations for facts, patterns, decisions, and context using semantic or text search
|
||||
when_to_use: When your human partner mentions "we discussed this before". When debugging similar issues. When looking for architectural decisions or code patterns from past work. Before reinventing solutions. When you need to find a specific git SHA or error message.
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Remembering Conversations
|
||||
|
||||
Search archived conversations using semantic similarity or exact text matching.
|
||||
|
||||
**Core principle:** Search before reinventing.
|
||||
|
||||
**Announce:** "I'm searching previous conversations for [topic]."
|
||||
|
||||
**Setup:** See INDEXING.md
|
||||
|
||||
## When to Use
|
||||
|
||||
**Search when:**
|
||||
- Your human partner mentions "we discussed this before"
|
||||
- Debugging similar issues
|
||||
- Looking for architectural decisions or patterns
|
||||
- Before implementing something familiar
|
||||
|
||||
**Don't search when:**
|
||||
- Info in current conversation
|
||||
- Question about current codebase (use Grep/Read)
|
||||
|
||||
## In-Session Use
|
||||
|
||||
**Always use subagents** (50-100x context savings). See skills/getting-started for workflow.
|
||||
|
||||
**Manual/CLI use:** Direct search (below) for humans outside Claude Code sessions.
|
||||
|
||||
## Direct Search (Manual/CLI)
|
||||
|
||||
**Tool:** `${CLAUDE_PLUGIN_ROOT}/skills/collaboration/remembering-conversations/tool/search-conversations`
|
||||
|
||||
**Modes:**
|
||||
```bash
|
||||
search-conversations "query" # Vector similarity (default)
|
||||
search-conversations --text "exact" # Exact string match
|
||||
search-conversations --both "query" # Both modes
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
```bash
|
||||
--after YYYY-MM-DD # Filter by date
|
||||
--before YYYY-MM-DD # Filter by date
|
||||
--limit N # Max results (default: 10)
|
||||
--help # Full usage
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Semantic search
|
||||
search-conversations "React Router authentication errors"
|
||||
|
||||
# Find git SHA
|
||||
search-conversations --text "a1b2c3d4"
|
||||
|
||||
# Time range
|
||||
search-conversations --after 2025-09-01 "refactoring"
|
||||
```
|
||||
|
||||
Returns: project, date, conversation summary, matched exchange, similarity %, file path.
|
||||
|
||||
**For details:** Run `search-conversations --help`
|
||||
@@ -1,8 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
# Local data (database and archives are at ~/.clank/, not in repo)
|
||||
*.sqlite*
|
||||
.cache/
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Auto-index conversation after session ends
|
||||
# Copy to ~/.claude/hooks/sessionEnd to enable
|
||||
|
||||
INDEXER="$HOME/.claude/skills/collaboration/remembering-conversations/tool/index-conversations"
|
||||
|
||||
if [ -n "$SESSION_ID" ] && [ -x "$INDEXER" ]; then
|
||||
# Run in background, suppress output
|
||||
"$INDEXER" --session "$SESSION_ID" > /dev/null 2>&1 &
|
||||
fi
|
||||
@@ -1,83 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
SCRIPT_DIR="$(pwd)"
|
||||
|
||||
case "$1" in
|
||||
--help|-h)
|
||||
cat <<'EOF'
|
||||
index-conversations - Index and manage conversation archives
|
||||
|
||||
USAGE:
|
||||
index-conversations [COMMAND] [OPTIONS]
|
||||
|
||||
COMMANDS:
|
||||
(default) Index all conversations
|
||||
--cleanup Process only unindexed conversations (fast, cheap)
|
||||
--session ID Index specific session (used by hook)
|
||||
--verify Check index health
|
||||
--repair Fix detected issues
|
||||
--rebuild Delete DB and re-index everything (requires confirmation)
|
||||
|
||||
OPTIONS:
|
||||
--concurrency N Parallel summarization (1-16, default: 1)
|
||||
-c N Short form of --concurrency
|
||||
--no-summaries Skip AI summary generation (free, but no summaries in results)
|
||||
--help, -h Show this help
|
||||
|
||||
EXAMPLES:
|
||||
# Index all unprocessed (recommended for backfill)
|
||||
index-conversations --cleanup
|
||||
|
||||
# Index with 8 parallel summarizations (8x faster)
|
||||
index-conversations --cleanup --concurrency 8
|
||||
|
||||
# Index without AI summaries (free, fast)
|
||||
index-conversations --cleanup --no-summaries
|
||||
|
||||
# Check index health
|
||||
index-conversations --verify
|
||||
|
||||
# Fix any issues found
|
||||
index-conversations --repair
|
||||
|
||||
# Nuclear option (deletes everything, re-indexes)
|
||||
index-conversations --rebuild
|
||||
|
||||
WORKFLOW:
|
||||
1. Initial setup: index-conversations --cleanup
|
||||
2. Ongoing: Auto-indexed by sessionEnd hook
|
||||
3. Health check: index-conversations --verify (weekly)
|
||||
4. Recovery: index-conversations --repair (if issues found)
|
||||
|
||||
SEE ALSO:
|
||||
INDEXING.md - Setup and maintenance guide
|
||||
DEPLOYMENT.md - Production runbook
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--session)
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" index-session "$@"
|
||||
;;
|
||||
--cleanup)
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" index-cleanup "$@"
|
||||
;;
|
||||
--verify)
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" verify "$@"
|
||||
;;
|
||||
--repair)
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" repair "$@"
|
||||
;;
|
||||
--rebuild)
|
||||
echo "⚠️ This will DELETE the entire database and re-index everything."
|
||||
read -p "Are you sure? [yes/NO]: " confirm
|
||||
if [ "$confirm" = "yes" ]; then
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" rebuild "$@"
|
||||
else
|
||||
echo "Cancelled"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
npx tsx "$SCRIPT_DIR/src/index-cli.ts" index-all "$@"
|
||||
;;
|
||||
esac
|
||||
@@ -1,82 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Install sessionEnd hook with merge support
|
||||
|
||||
HOOK_DIR="$HOME/.claude/hooks"
|
||||
HOOK_FILE="$HOOK_DIR/sessionEnd"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SOURCE_HOOK="$SCRIPT_DIR/hooks/sessionEnd"
|
||||
|
||||
echo "Installing conversation indexing hook..."
|
||||
|
||||
# Create hooks directory
|
||||
mkdir -p "$HOOK_DIR"
|
||||
|
||||
# Handle existing hook
|
||||
if [ -f "$HOOK_FILE" ]; then
|
||||
echo "⚠️ Existing sessionEnd hook found"
|
||||
|
||||
# Check if our indexer is already installed
|
||||
if grep -q "remembering-conversations.*index-conversations" "$HOOK_FILE"; then
|
||||
echo "✓ Indexer already installed in existing hook"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create backup
|
||||
BACKUP="$HOOK_FILE.backup.$(date +%s)"
|
||||
cp "$HOOK_FILE" "$BACKUP"
|
||||
echo "Created backup: $BACKUP"
|
||||
|
||||
# Offer merge or replace
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " (m) Merge - Add indexer to existing hook"
|
||||
echo " (r) Replace - Overwrite with our hook"
|
||||
echo " (c) Cancel - Exit without changes"
|
||||
echo ""
|
||||
read -p "Choose [m/r/c]: " choice
|
||||
|
||||
case "$choice" in
|
||||
m|M)
|
||||
# Append our indexer
|
||||
cat >> "$HOOK_FILE" <<'EOF'
|
||||
|
||||
# Auto-index conversations (remembering-conversations skill)
|
||||
INDEXER="$HOME/.claude/skills/collaboration/remembering-conversations/tool/index-conversations"
|
||||
if [ -n "$SESSION_ID" ] && [ -x "$INDEXER" ]; then
|
||||
"$INDEXER" --session "$SESSION_ID" > /dev/null 2>&1 &
|
||||
fi
|
||||
EOF
|
||||
echo "✓ Merged indexer into existing hook"
|
||||
;;
|
||||
r|R)
|
||||
cp "$SOURCE_HOOK" "$HOOK_FILE"
|
||||
chmod +x "$HOOK_FILE"
|
||||
echo "✓ Replaced hook with our version"
|
||||
;;
|
||||
c|C)
|
||||
echo "Installation cancelled"
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "Invalid choice. Exiting."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
# No existing hook, install fresh
|
||||
cp "$SOURCE_HOOK" "$HOOK_FILE"
|
||||
chmod +x "$HOOK_FILE"
|
||||
echo "✓ Installed sessionEnd hook"
|
||||
fi
|
||||
|
||||
# Verify executable
|
||||
if [ ! -x "$HOOK_FILE" ]; then
|
||||
chmod +x "$HOOK_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Hook installed successfully!"
|
||||
echo "Location: $HOOK_FILE"
|
||||
echo ""
|
||||
echo "Test it:"
|
||||
echo " SESSION_ID=test-\$(date +%s) $HOOK_FILE"
|
||||
@@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Migrate conversation archive and index from ~/.clank to ~/.config/superpowers
|
||||
#
|
||||
# IMPORTANT: This preserves all data. The old ~/.clank directory is not deleted,
|
||||
# allowing you to verify the migration before removing it manually.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Determine target directory
|
||||
SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
|
||||
OLD_ARCHIVE="$HOME/.clank/conversation-archive"
|
||||
OLD_INDEX="$HOME/.clank/conversation-index"
|
||||
|
||||
NEW_ARCHIVE="${SUPERPOWERS_DIR}/conversation-archive"
|
||||
NEW_INDEX="${SUPERPOWERS_DIR}/conversation-index"
|
||||
|
||||
echo "Migration: ~/.clank → ${SUPERPOWERS_DIR}"
|
||||
echo ""
|
||||
|
||||
# Check if source exists
|
||||
if [[ ! -d "$HOME/.clank" ]]; then
|
||||
echo "✅ No ~/.clank directory found. Nothing to migrate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check if already migrated
|
||||
if [[ -d "$NEW_ARCHIVE" ]] || [[ -d "$NEW_INDEX" ]]; then
|
||||
echo "⚠️ Destination already exists:"
|
||||
[[ -d "$NEW_ARCHIVE" ]] && echo " - ${NEW_ARCHIVE}"
|
||||
[[ -d "$NEW_INDEX" ]] && echo " - ${NEW_INDEX}"
|
||||
echo ""
|
||||
echo "Migration appears to have already run."
|
||||
echo "To re-run migration, manually remove destination directories first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show what will be migrated
|
||||
echo "Source directories:"
|
||||
if [[ -d "$OLD_ARCHIVE" ]]; then
|
||||
archive_size=$(du -sh "$OLD_ARCHIVE" | cut -f1)
|
||||
archive_count=$(find "$OLD_ARCHIVE" -name "*.jsonl" | wc -l | tr -d ' ')
|
||||
echo " Archive: ${OLD_ARCHIVE} (${archive_count} conversations, ${archive_size})"
|
||||
else
|
||||
echo " Archive: Not found"
|
||||
fi
|
||||
|
||||
if [[ -d "$OLD_INDEX" ]]; then
|
||||
index_size=$(du -sh "$OLD_INDEX" | cut -f1)
|
||||
echo " Index: ${OLD_INDEX} (${index_size})"
|
||||
else
|
||||
echo " Index: Not found"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Destination: ${SUPERPOWERS_DIR}"
|
||||
echo ""
|
||||
|
||||
# Confirm
|
||||
read -p "Proceed with migration? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Migration cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure destination base exists
|
||||
mkdir -p "${SUPERPOWERS_DIR}"
|
||||
|
||||
# Migrate archive
|
||||
if [[ -d "$OLD_ARCHIVE" ]]; then
|
||||
echo "Copying conversation archive..."
|
||||
cp -r "$OLD_ARCHIVE" "$NEW_ARCHIVE"
|
||||
echo " ✓ Archive migrated"
|
||||
fi
|
||||
|
||||
# Migrate index
|
||||
if [[ -d "$OLD_INDEX" ]]; then
|
||||
echo "Copying conversation index..."
|
||||
cp -r "$OLD_INDEX" "$NEW_INDEX"
|
||||
echo " ✓ Index migrated"
|
||||
fi
|
||||
|
||||
# Update database paths to point to new location
|
||||
if [[ -f "$NEW_INDEX/db.sqlite" ]]; then
|
||||
echo "Updating database paths..."
|
||||
sqlite3 "$NEW_INDEX/db.sqlite" "UPDATE exchanges SET archive_path = REPLACE(archive_path, '/.clank/', '/.config/superpowers/') WHERE archive_path LIKE '%/.clank/%';"
|
||||
echo " ✓ Database paths updated"
|
||||
fi
|
||||
|
||||
# Verify migration
|
||||
echo ""
|
||||
echo "Verifying migration..."
|
||||
|
||||
if [[ -d "$OLD_ARCHIVE" ]]; then
|
||||
old_count=$(find "$OLD_ARCHIVE" -name "*.jsonl" | wc -l | tr -d ' ')
|
||||
new_count=$(find "$NEW_ARCHIVE" -name "*.jsonl" | wc -l | tr -d ' ')
|
||||
|
||||
if [[ "$old_count" -eq "$new_count" ]]; then
|
||||
echo " ✓ All $new_count conversations migrated"
|
||||
else
|
||||
echo " ⚠️ Conversation count mismatch: old=$old_count, new=$new_count"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -f "$OLD_INDEX/db.sqlite" ]]; then
|
||||
old_size=$(stat -f%z "$OLD_INDEX/db.sqlite" 2>/dev/null || stat --format=%s "$OLD_INDEX/db.sqlite" 2>/dev/null)
|
||||
new_size=$(stat -f%z "$NEW_INDEX/db.sqlite" 2>/dev/null || stat --format=%s "$NEW_INDEX/db.sqlite" 2>/dev/null)
|
||||
echo " ✓ Database migrated (${new_size} bytes)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Migration complete!"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Test search: ./search-conversations 'test query'"
|
||||
echo " 2. Verify results look correct"
|
||||
echo " 3. Once verified, manually remove old directory:"
|
||||
echo " rm -rf ~/.clank"
|
||||
echo ""
|
||||
echo "The old ~/.clank directory is preserved for safety."
|
||||
|
||||
exit 0
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "conversation-search",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"index": "./index-conversations",
|
||||
"search": "./search-conversations",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.1.9",
|
||||
"@xenova/transformers": "^2.17.2",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"sqlite-vec": "^0.1.7-alpha.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^24.7.0",
|
||||
"tsx": "^4.20.6",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
# Conversation Search Agent
|
||||
|
||||
You are searching historical Claude Code conversations for relevant context.
|
||||
|
||||
**Your task:**
|
||||
1. Search conversations for: {TOPIC}
|
||||
2. Read the top 2-5 most relevant results
|
||||
3. Synthesize key findings (max 1000 words)
|
||||
4. Return synthesis + source pointers (so main agent can dig deeper)
|
||||
|
||||
## Search Query
|
||||
|
||||
{SEARCH_QUERY}
|
||||
|
||||
## What to Look For
|
||||
|
||||
{FOCUS_AREAS}
|
||||
|
||||
Example focus areas:
|
||||
- What was the problem or question?
|
||||
- What solution was chosen and why?
|
||||
- What alternatives were considered and rejected?
|
||||
- Any gotchas, edge cases, or lessons learned?
|
||||
- Relevant code patterns, APIs, or approaches used
|
||||
- Architectural decisions and rationale
|
||||
|
||||
## How to Search
|
||||
|
||||
Run:
|
||||
```bash
|
||||
~/.claude/skills/collaboration/remembering-conversations/tool/search-conversations "{SEARCH_QUERY}"
|
||||
```
|
||||
|
||||
This returns:
|
||||
- Project name and date
|
||||
- Conversation summary (AI-generated)
|
||||
- Matched exchange with similarity score
|
||||
- File path and line numbers
|
||||
|
||||
Read the full conversations for top 2-5 results to get complete context.
|
||||
|
||||
## Output Format
|
||||
|
||||
**Required structure:**
|
||||
|
||||
### Summary
|
||||
[Synthesize findings in 200-1000 words. Adapt structure to what you found:
|
||||
- Quick answer? 1-2 paragraphs.
|
||||
- Complex topic? Use sections (Context/Solution/Rationale/Lessons/Code).
|
||||
- Multiple approaches? Compare and contrast.
|
||||
- Historical evolution? Show progression chronologically.
|
||||
|
||||
Focus on actionable insights for the current task.]
|
||||
|
||||
### Sources
|
||||
[List ALL conversations examined, in order of relevance:]
|
||||
|
||||
**1. [project-name, YYYY-MM-DD]** - X% match
|
||||
Conversation summary: [One sentence - what was this conversation about?]
|
||||
File: ~/.clank/conversation-archive/.../uuid.jsonl:start-end
|
||||
Status: [Read in detail | Reviewed summary only | Skimmed]
|
||||
|
||||
**2. [project-name, YYYY-MM-DD]** - X% match
|
||||
Conversation summary: ...
|
||||
File: ...
|
||||
Status: ...
|
||||
|
||||
[Continue for all examined sources...]
|
||||
|
||||
### For Follow-Up
|
||||
|
||||
Main agent can:
|
||||
- Ask you to dig deeper into specific source (#1, #2, etc.)
|
||||
- Ask you to read adjacent exchanges in a conversation
|
||||
- Ask you to search with refined query
|
||||
- Read sources directly (discouraged - risks context bloat)
|
||||
|
||||
## Critical Rules
|
||||
|
||||
**DO:**
|
||||
- Search using the provided query
|
||||
- Read full conversations for top results
|
||||
- Synthesize into actionable insights (200-1000 words)
|
||||
- Include ALL sources with metadata (project, date, summary, file, status)
|
||||
- Focus on what will help the current task
|
||||
- Include specific details (function names, error messages, line numbers)
|
||||
|
||||
**DO NOT:**
|
||||
- Include raw conversation excerpts (synthesize instead)
|
||||
- Paste full file contents
|
||||
- Add meta-commentary ("I searched and found...")
|
||||
- Exceed 1000 words in Summary section
|
||||
- Return search results verbatim
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
### Summary
|
||||
|
||||
developer needed to handle authentication errors in React Router 7 data loaders
|
||||
without crashing the app. The solution uses RR7's errorElement + useRouteError()
|
||||
to catch 401s and redirect to login.
|
||||
|
||||
**Key implementation:**
|
||||
Protected route wrapper catches loader errors, checks error.status === 401.
|
||||
If 401, redirects to /login with return URL. Otherwise shows error boundary.
|
||||
|
||||
**Why this works:**
|
||||
Loaders can't use hooks (tried useNavigate, failed). Throwing redirect()
|
||||
bypasses error handling. Final approach lets errors bubble to errorElement
|
||||
where component context is available.
|
||||
|
||||
**Critical gotchas:**
|
||||
- Test with expired tokens, not just missing tokens
|
||||
- Error boundaries need unique keys per route or won't reset
|
||||
- Always include return URL in redirect
|
||||
- Loaders execute before components, no hook access
|
||||
|
||||
**Code pattern:**
|
||||
```typescript
|
||||
// In loader
|
||||
if (!response.ok) throw { status: response.status, message: 'Failed' };
|
||||
|
||||
// In ErrorBoundary
|
||||
const error = useRouteError();
|
||||
if (error.status === 401) navigate('/login?return=' + location.pathname);
|
||||
```
|
||||
|
||||
### Sources
|
||||
|
||||
**1. [react-router-7-starter, 2024-09-17]** - 92% match
|
||||
Conversation summary: Built authentication system with JWT, implemented protected routes
|
||||
File: ~/.clank/conversation-archive/react-router-7-starter/19df92b9.jsonl:145-289
|
||||
Status: Read in detail (multiple exchanges on error handling evolution)
|
||||
|
||||
**2. [react-router-docs-reading, 2024-09-10]** - 78% match
|
||||
Conversation summary: Read RR7 docs, discussed new loader patterns and errorElement
|
||||
File: ~/.clank/conversation-archive/react-router-docs-reading/a3c871f2.jsonl:56-98
|
||||
Status: Reviewed summary only (confirmed errorElement usage)
|
||||
|
||||
**3. [auth-debugging, 2024-09-18]** - 73% match
|
||||
Conversation summary: Fixed token expiration handling and error boundary reset issues
|
||||
File: ~/.clank/conversation-archive/react-router-7-starter/7b2e8d91.jsonl:201-345
|
||||
Status: Read in detail (discovered gotchas about keys and expired tokens)
|
||||
|
||||
### For Follow-Up
|
||||
|
||||
Main agent can ask me to:
|
||||
- Dig deeper into source #1 (full error handling evolution)
|
||||
- Read adjacent exchanges in #3 (more debugging context)
|
||||
- Search for "React Router error boundary patterns" more broadly
|
||||
```
|
||||
|
||||
This output:
|
||||
- Synthesis: ~350 words (actionable, specific)
|
||||
- Sources: Full metadata for 3 conversations
|
||||
- Enables iteration without context bloat
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/bin/bash
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Parse arguments
|
||||
MODE="vector"
|
||||
AFTER=""
|
||||
BEFORE=""
|
||||
LIMIT="10"
|
||||
QUERY=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--help|-h)
|
||||
cat <<'EOF'
|
||||
search-conversations - Search previous Claude Code conversations
|
||||
|
||||
USAGE:
|
||||
search-conversations [OPTIONS] <query>
|
||||
|
||||
MODES:
|
||||
(default) Vector similarity search (semantic)
|
||||
--text Exact string matching (for git SHAs, error codes)
|
||||
--both Combine vector + text search
|
||||
|
||||
OPTIONS:
|
||||
--after DATE Only conversations after YYYY-MM-DD
|
||||
--before DATE Only conversations before YYYY-MM-DD
|
||||
--limit N Max results (default: 10)
|
||||
--help, -h Show this help
|
||||
|
||||
EXAMPLES:
|
||||
# Semantic search
|
||||
search-conversations "React Router authentication errors"
|
||||
|
||||
# Find exact string (git SHA, error message)
|
||||
search-conversations --text "a1b2c3d4e5f6"
|
||||
|
||||
# Time filtering
|
||||
search-conversations --after 2025-09-01 "refactoring"
|
||||
search-conversations --before 2025-10-01 --limit 20 "bug fix"
|
||||
|
||||
# Combine modes
|
||||
search-conversations --both "React Router data loading"
|
||||
|
||||
OUTPUT FORMAT:
|
||||
For each result:
|
||||
- Project name and date
|
||||
- Conversation summary (AI-generated)
|
||||
- Matched exchange with similarity % (vector mode)
|
||||
- File path with line numbers
|
||||
|
||||
Example:
|
||||
1. [react-router-7-starter, 2025-09-17]
|
||||
Built authentication with JWT, implemented protected routes.
|
||||
|
||||
92% match: "How do I handle auth errors in loaders?"
|
||||
~/.clank/conversation-archive/.../uuid.jsonl:145-167
|
||||
|
||||
QUERY TIPS:
|
||||
- Use natural language: "How did we handle X?"
|
||||
- Be specific: "React Router data loading" not "routing"
|
||||
- Include context: "TypeScript type narrowing in guards"
|
||||
|
||||
SEE ALSO:
|
||||
skills/collaboration/remembering-conversations/INDEXING.md - Manage index
|
||||
skills/collaboration/remembering-conversations/SKILL.md - Usage guide
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
--text)
|
||||
MODE="text"
|
||||
shift
|
||||
;;
|
||||
--both)
|
||||
MODE="both"
|
||||
shift
|
||||
;;
|
||||
--after)
|
||||
AFTER="$2"
|
||||
shift 2
|
||||
;;
|
||||
--before)
|
||||
BEFORE="$2"
|
||||
shift 2
|
||||
;;
|
||||
--limit)
|
||||
LIMIT="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
QUERY="$QUERY $1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
QUERY=$(echo "$QUERY" | sed 's/^ *//')
|
||||
|
||||
if [ -z "$QUERY" ]; then
|
||||
echo "Usage: search-conversations [options] <query>"
|
||||
echo "Try: search-conversations --help"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npx tsx src/search-cli.ts "$QUERY" "$MODE" "$LIMIT" "$AFTER" "$BEFORE"
|
||||
@@ -1,112 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { initDatabase, migrateSchema, insertExchange } from './db.js';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import Database from 'better-sqlite3';
|
||||
|
||||
describe('database migration', () => {
|
||||
const testDir = path.join(os.tmpdir(), 'db-migration-test-' + Date.now());
|
||||
const dbPath = path.join(testDir, 'test.db');
|
||||
|
||||
beforeEach(() => {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
process.env.TEST_DB_PATH = dbPath;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.TEST_DB_PATH;
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('adds last_indexed column to existing database', () => {
|
||||
// Create a database with old schema (no last_indexed)
|
||||
const db = new Database(dbPath);
|
||||
db.exec(`
|
||||
CREATE TABLE exchanges (
|
||||
id TEXT PRIMARY KEY,
|
||||
project TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
user_message TEXT NOT NULL,
|
||||
assistant_message TEXT NOT NULL,
|
||||
archive_path TEXT NOT NULL,
|
||||
line_start INTEGER NOT NULL,
|
||||
line_end INTEGER NOT NULL,
|
||||
embedding BLOB
|
||||
)
|
||||
`);
|
||||
|
||||
// Verify column doesn't exist
|
||||
const columnsBefore = db.prepare(`PRAGMA table_info(exchanges)`).all();
|
||||
const hasLastIndexedBefore = columnsBefore.some((col: any) => col.name === 'last_indexed');
|
||||
expect(hasLastIndexedBefore).toBe(false);
|
||||
|
||||
db.close();
|
||||
|
||||
// Run migration
|
||||
const migratedDb = initDatabase();
|
||||
|
||||
// Verify column now exists
|
||||
const columnsAfter = migratedDb.prepare(`PRAGMA table_info(exchanges)`).all();
|
||||
const hasLastIndexedAfter = columnsAfter.some((col: any) => col.name === 'last_indexed');
|
||||
expect(hasLastIndexedAfter).toBe(true);
|
||||
|
||||
migratedDb.close();
|
||||
});
|
||||
|
||||
it('handles existing last_indexed column gracefully', () => {
|
||||
// Create database with migration already applied
|
||||
const db = initDatabase();
|
||||
|
||||
// Run migration again - should not error
|
||||
expect(() => migrateSchema(db)).not.toThrow();
|
||||
|
||||
db.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('insertExchange with last_indexed', () => {
|
||||
const testDir = path.join(os.tmpdir(), 'insert-test-' + Date.now());
|
||||
const dbPath = path.join(testDir, 'test.db');
|
||||
|
||||
beforeEach(() => {
|
||||
fs.mkdirSync(testDir, { recursive: true });
|
||||
process.env.TEST_DB_PATH = dbPath;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.TEST_DB_PATH;
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('sets last_indexed timestamp when inserting exchange', () => {
|
||||
const db = initDatabase();
|
||||
|
||||
const exchange: ConversationExchange = {
|
||||
id: 'test-id-1',
|
||||
project: 'test-project',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
userMessage: 'Hello',
|
||||
assistantMessage: 'Hi there!',
|
||||
archivePath: '/test/path.jsonl',
|
||||
lineStart: 1,
|
||||
lineEnd: 2
|
||||
};
|
||||
|
||||
const beforeInsert = Date.now();
|
||||
// Create proper 384-dimensional embedding
|
||||
const embedding = new Array(384).fill(0.1);
|
||||
insertExchange(db, exchange, embedding);
|
||||
const afterInsert = Date.now();
|
||||
|
||||
// Query the exchange
|
||||
const row = db.prepare(`SELECT last_indexed FROM exchanges WHERE id = ?`).get('test-id-1') as any;
|
||||
|
||||
expect(row.last_indexed).toBeDefined();
|
||||
expect(row.last_indexed).toBeGreaterThanOrEqual(beforeInsert);
|
||||
expect(row.last_indexed).toBeLessThanOrEqual(afterInsert);
|
||||
|
||||
db.close();
|
||||
});
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import * as sqliteVec from 'sqlite-vec';
|
||||
import { getDbPath } from './paths.js';
|
||||
|
||||
export function migrateSchema(db: Database.Database): void {
|
||||
const hasColumn = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM pragma_table_info('exchanges')
|
||||
WHERE name='last_indexed'
|
||||
`).get() as { count: number };
|
||||
|
||||
if (hasColumn.count === 0) {
|
||||
console.log('Migrating schema: adding last_indexed column...');
|
||||
db.prepare('ALTER TABLE exchanges ADD COLUMN last_indexed INTEGER').run();
|
||||
console.log('Migration complete.');
|
||||
}
|
||||
}
|
||||
|
||||
export function initDatabase(): Database.Database {
|
||||
const dbPath = getDbPath();
|
||||
|
||||
// Ensure directory exists
|
||||
const dbDir = path.dirname(dbPath);
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Load sqlite-vec extension
|
||||
sqliteVec.load(db);
|
||||
|
||||
// Enable WAL mode for better concurrency
|
||||
db.pragma('journal_mode = WAL');
|
||||
|
||||
// Create exchanges table
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS exchanges (
|
||||
id TEXT PRIMARY KEY,
|
||||
project TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
user_message TEXT NOT NULL,
|
||||
assistant_message TEXT NOT NULL,
|
||||
archive_path TEXT NOT NULL,
|
||||
line_start INTEGER NOT NULL,
|
||||
line_end INTEGER NOT NULL,
|
||||
embedding BLOB
|
||||
)
|
||||
`);
|
||||
|
||||
// Create vector search index
|
||||
db.exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS vec_exchanges USING vec0(
|
||||
id TEXT PRIMARY KEY,
|
||||
embedding FLOAT[384]
|
||||
)
|
||||
`);
|
||||
|
||||
// Create index on timestamp for sorting
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_timestamp ON exchanges(timestamp DESC)
|
||||
`);
|
||||
|
||||
// Run migrations
|
||||
migrateSchema(db);
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
export function insertExchange(
|
||||
db: Database.Database,
|
||||
exchange: ConversationExchange,
|
||||
embedding: number[]
|
||||
): void {
|
||||
const now = Date.now();
|
||||
|
||||
const stmt = db.prepare(`
|
||||
INSERT OR REPLACE INTO exchanges
|
||||
(id, project, timestamp, user_message, assistant_message, archive_path, line_start, line_end, last_indexed)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
stmt.run(
|
||||
exchange.id,
|
||||
exchange.project,
|
||||
exchange.timestamp,
|
||||
exchange.userMessage,
|
||||
exchange.assistantMessage,
|
||||
exchange.archivePath,
|
||||
exchange.lineStart,
|
||||
exchange.lineEnd,
|
||||
now
|
||||
);
|
||||
|
||||
// Insert into vector table (delete first since virtual tables don't support REPLACE)
|
||||
const delStmt = db.prepare(`DELETE FROM vec_exchanges WHERE id = ?`);
|
||||
delStmt.run(exchange.id);
|
||||
|
||||
const vecStmt = db.prepare(`
|
||||
INSERT INTO vec_exchanges (id, embedding)
|
||||
VALUES (?, ?)
|
||||
`);
|
||||
|
||||
vecStmt.run(exchange.id, Buffer.from(new Float32Array(embedding).buffer));
|
||||
}
|
||||
|
||||
export function getAllExchanges(db: Database.Database): Array<{ id: string; archivePath: string }> {
|
||||
const stmt = db.prepare(`SELECT id, archive_path as archivePath FROM exchanges`);
|
||||
return stmt.all() as Array<{ id: string; archivePath: string }>;
|
||||
}
|
||||
|
||||
export function getFileLastIndexed(db: Database.Database, archivePath: string): number | null {
|
||||
const stmt = db.prepare(`
|
||||
SELECT MAX(last_indexed) as lastIndexed
|
||||
FROM exchanges
|
||||
WHERE archive_path = ?
|
||||
`);
|
||||
const row = stmt.get(archivePath) as { lastIndexed: number | null };
|
||||
return row.lastIndexed;
|
||||
}
|
||||
|
||||
export function deleteExchange(db: Database.Database, id: string): void {
|
||||
// Delete from vector table
|
||||
db.prepare(`DELETE FROM vec_exchanges WHERE id = ?`).run(id);
|
||||
|
||||
// Delete from main table
|
||||
db.prepare(`DELETE FROM exchanges WHERE id = ?`).run(id);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import { pipeline, Pipeline } from '@xenova/transformers';
|
||||
|
||||
let embeddingPipeline: Pipeline | null = null;
|
||||
|
||||
export async function initEmbeddings(): Promise<void> {
|
||||
if (!embeddingPipeline) {
|
||||
console.log('Loading embedding model (first run may take time)...');
|
||||
embeddingPipeline = await pipeline(
|
||||
'feature-extraction',
|
||||
'Xenova/all-MiniLM-L6-v2'
|
||||
);
|
||||
console.log('Embedding model loaded');
|
||||
}
|
||||
}
|
||||
|
||||
export async function generateEmbedding(text: string): Promise<number[]> {
|
||||
if (!embeddingPipeline) {
|
||||
await initEmbeddings();
|
||||
}
|
||||
|
||||
// Truncate text to avoid token limits (512 tokens max for this model)
|
||||
const truncated = text.substring(0, 2000);
|
||||
|
||||
const output = await embeddingPipeline!(truncated, {
|
||||
pooling: 'mean',
|
||||
normalize: true
|
||||
});
|
||||
|
||||
return Array.from(output.data);
|
||||
}
|
||||
|
||||
export async function generateExchangeEmbedding(
|
||||
userMessage: string,
|
||||
assistantMessage: string
|
||||
): Promise<number[]> {
|
||||
// Combine user question and assistant answer for better searchability
|
||||
const combined = `User: ${userMessage}\n\nAssistant: ${assistantMessage}`;
|
||||
return generateEmbedding(combined);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
import { verifyIndex, repairIndex } from './verify.js';
|
||||
import { indexSession, indexUnprocessed, indexConversations } from './indexer.js';
|
||||
import { initDatabase } from './db.js';
|
||||
import { getDbPath, getArchiveDir } from './paths.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
// Parse --concurrency flag from remaining args
|
||||
function getConcurrency(): number {
|
||||
const concurrencyIndex = process.argv.findIndex(arg => arg === '--concurrency' || arg === '-c');
|
||||
if (concurrencyIndex !== -1 && process.argv[concurrencyIndex + 1]) {
|
||||
const value = parseInt(process.argv[concurrencyIndex + 1], 10);
|
||||
if (value >= 1 && value <= 16) return value;
|
||||
}
|
||||
return 1; // default
|
||||
}
|
||||
|
||||
// Parse --no-summaries flag
|
||||
function getNoSummaries(): boolean {
|
||||
return process.argv.includes('--no-summaries');
|
||||
}
|
||||
|
||||
const concurrency = getConcurrency();
|
||||
const noSummaries = getNoSummaries();
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
switch (command) {
|
||||
case 'index-session':
|
||||
const sessionId = process.argv[3];
|
||||
if (!sessionId) {
|
||||
console.error('Usage: index-cli index-session <session-id>');
|
||||
process.exit(1);
|
||||
}
|
||||
await indexSession(sessionId, concurrency, noSummaries);
|
||||
break;
|
||||
|
||||
case 'index-cleanup':
|
||||
await indexUnprocessed(concurrency, noSummaries);
|
||||
break;
|
||||
|
||||
case 'verify':
|
||||
console.log('Verifying conversation index...');
|
||||
const issues = await verifyIndex();
|
||||
|
||||
console.log('\n=== Verification Results ===');
|
||||
console.log(`Missing summaries: ${issues.missing.length}`);
|
||||
console.log(`Orphaned entries: ${issues.orphaned.length}`);
|
||||
console.log(`Outdated files: ${issues.outdated.length}`);
|
||||
console.log(`Corrupted files: ${issues.corrupted.length}`);
|
||||
|
||||
if (issues.missing.length > 0) {
|
||||
console.log('\nMissing summaries:');
|
||||
issues.missing.forEach(m => console.log(` ${m.path}`));
|
||||
}
|
||||
|
||||
if (issues.missing.length + issues.orphaned.length + issues.outdated.length + issues.corrupted.length > 0) {
|
||||
console.log('\nRun with --repair to fix these issues.');
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log('\n✅ Index is healthy!');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'repair':
|
||||
console.log('Verifying conversation index...');
|
||||
const repairIssues = await verifyIndex();
|
||||
|
||||
if (repairIssues.missing.length + repairIssues.orphaned.length + repairIssues.outdated.length > 0) {
|
||||
await repairIndex(repairIssues);
|
||||
} else {
|
||||
console.log('✅ No issues to repair!');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'rebuild':
|
||||
console.log('Rebuilding entire index...');
|
||||
|
||||
// Delete database
|
||||
const dbPath = getDbPath();
|
||||
if (fs.existsSync(dbPath)) {
|
||||
fs.unlinkSync(dbPath);
|
||||
console.log('Deleted existing database');
|
||||
}
|
||||
|
||||
// Delete all summary files
|
||||
const archiveDir = getArchiveDir();
|
||||
if (fs.existsSync(archiveDir)) {
|
||||
const projects = fs.readdirSync(archiveDir);
|
||||
for (const project of projects) {
|
||||
const projectPath = path.join(archiveDir, project);
|
||||
if (!fs.statSync(projectPath).isDirectory()) continue;
|
||||
|
||||
const summaries = fs.readdirSync(projectPath).filter(f => f.endsWith('-summary.txt'));
|
||||
for (const summary of summaries) {
|
||||
fs.unlinkSync(path.join(projectPath, summary));
|
||||
}
|
||||
}
|
||||
console.log('Deleted all summary files');
|
||||
}
|
||||
|
||||
// Re-index everything
|
||||
console.log('Re-indexing all conversations...');
|
||||
await indexConversations(undefined, undefined, concurrency, noSummaries);
|
||||
break;
|
||||
|
||||
case 'index-all':
|
||||
default:
|
||||
await indexConversations(undefined, undefined, concurrency, noSummaries);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,374 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { initDatabase, insertExchange } from './db.js';
|
||||
import { parseConversation } from './parser.js';
|
||||
import { initEmbeddings, generateExchangeEmbedding } from './embeddings.js';
|
||||
import { summarizeConversation } from './summarizer.js';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import { getArchiveDir, getExcludeConfigPath } from './paths.js';
|
||||
|
||||
// Set max output tokens for Claude SDK (used by summarizer)
|
||||
process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = '20000';
|
||||
|
||||
// Increase max listeners for concurrent API calls
|
||||
import { EventEmitter } from 'events';
|
||||
EventEmitter.defaultMaxListeners = 20;
|
||||
|
||||
// Allow overriding paths for testing
|
||||
function getProjectsDir(): string {
|
||||
return process.env.TEST_PROJECTS_DIR || path.join(os.homedir(), '.claude', 'projects');
|
||||
}
|
||||
|
||||
// Projects to exclude from indexing (configurable via env or config file)
|
||||
function getExcludedProjects(): string[] {
|
||||
// Check env variable first
|
||||
if (process.env.CONVERSATION_SEARCH_EXCLUDE_PROJECTS) {
|
||||
return process.env.CONVERSATION_SEARCH_EXCLUDE_PROJECTS.split(',').map(p => p.trim());
|
||||
}
|
||||
|
||||
// Check for config file
|
||||
const configPath = getExcludeConfigPath();
|
||||
if (fs.existsSync(configPath)) {
|
||||
const content = fs.readFileSync(configPath, 'utf-8');
|
||||
return content.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'));
|
||||
}
|
||||
|
||||
// Default: no exclusions
|
||||
return [];
|
||||
}
|
||||
|
||||
// Process items in batches with limited concurrency
|
||||
async function processBatch<T, R>(
|
||||
items: T[],
|
||||
processor: (item: T) => Promise<R>,
|
||||
concurrency: number
|
||||
): Promise<R[]> {
|
||||
const results: R[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i += concurrency) {
|
||||
const batch = items.slice(i, i + concurrency);
|
||||
const batchResults = await Promise.all(batch.map(processor));
|
||||
results.push(...batchResults);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export async function indexConversations(
|
||||
limitToProject?: string,
|
||||
maxConversations?: number,
|
||||
concurrency: number = 1,
|
||||
noSummaries: boolean = false
|
||||
): Promise<void> {
|
||||
console.log('Initializing database...');
|
||||
const db = initDatabase();
|
||||
|
||||
console.log('Loading embedding model...');
|
||||
await initEmbeddings();
|
||||
|
||||
if (noSummaries) {
|
||||
console.log('⚠️ Running in no-summaries mode (skipping AI summaries)');
|
||||
}
|
||||
|
||||
console.log('Scanning for conversation files...');
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
|
||||
let totalExchanges = 0;
|
||||
let conversationsProcessed = 0;
|
||||
|
||||
const excludedProjects = getExcludedProjects();
|
||||
|
||||
for (const project of projects) {
|
||||
// Skip excluded projects
|
||||
if (excludedProjects.includes(project)) {
|
||||
console.log(`\nSkipping excluded project: ${project}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if limiting to specific project
|
||||
if (limitToProject && project !== limitToProject) continue;
|
||||
const projectPath = path.join(PROJECTS_DIR, project);
|
||||
const stat = fs.statSync(projectPath);
|
||||
|
||||
if (!stat.isDirectory()) continue;
|
||||
|
||||
const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl'));
|
||||
|
||||
if (files.length === 0) continue;
|
||||
|
||||
console.log(`\nProcessing project: ${project} (${files.length} conversations)`);
|
||||
if (concurrency > 1) console.log(` Concurrency: ${concurrency}`);
|
||||
|
||||
// Create archive directory for this project
|
||||
const projectArchive = path.join(ARCHIVE_DIR, project);
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
// Prepare all conversations first
|
||||
type ConvToProcess = {
|
||||
file: string;
|
||||
sourcePath: string;
|
||||
archivePath: string;
|
||||
summaryPath: string;
|
||||
exchanges: ConversationExchange[];
|
||||
};
|
||||
|
||||
const toProcess: ConvToProcess[] = [];
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = path.join(projectPath, file);
|
||||
const archivePath = path.join(projectArchive, file);
|
||||
|
||||
// Copy to archive
|
||||
if (!fs.existsSync(archivePath)) {
|
||||
fs.copyFileSync(sourcePath, archivePath);
|
||||
console.log(` Archived: ${file}`);
|
||||
}
|
||||
|
||||
// Parse conversation
|
||||
const exchanges = await parseConversation(sourcePath, project, archivePath);
|
||||
|
||||
if (exchanges.length === 0) {
|
||||
console.log(` Skipped ${file} (no exchanges)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
toProcess.push({
|
||||
file,
|
||||
sourcePath,
|
||||
archivePath,
|
||||
summaryPath: archivePath.replace('.jsonl', '-summary.txt'),
|
||||
exchanges
|
||||
});
|
||||
}
|
||||
|
||||
// Batch summarize conversations in parallel (unless --no-summaries)
|
||||
if (!noSummaries) {
|
||||
const needsSummary = toProcess.filter(c => !fs.existsSync(c.summaryPath));
|
||||
|
||||
if (needsSummary.length > 0) {
|
||||
console.log(` Generating ${needsSummary.length} summaries (concurrency: ${concurrency})...`);
|
||||
|
||||
await processBatch(needsSummary, async (conv) => {
|
||||
try {
|
||||
const summary = await summarizeConversation(conv.exchanges);
|
||||
fs.writeFileSync(conv.summaryPath, summary, 'utf-8');
|
||||
const wordCount = summary.split(/\s+/).length;
|
||||
console.log(` ✓ ${conv.file}: ${wordCount} words`);
|
||||
return summary;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${conv.file}: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}, concurrency);
|
||||
}
|
||||
} else {
|
||||
console.log(` Skipping ${toProcess.length} summaries (--no-summaries mode)`);
|
||||
}
|
||||
|
||||
// Now process embeddings and DB inserts (fast, sequential is fine)
|
||||
for (const conv of toProcess) {
|
||||
for (const exchange of conv.exchanges) {
|
||||
const embedding = await generateExchangeEmbedding(
|
||||
exchange.userMessage,
|
||||
exchange.assistantMessage
|
||||
);
|
||||
|
||||
insertExchange(db, exchange, embedding);
|
||||
}
|
||||
|
||||
totalExchanges += conv.exchanges.length;
|
||||
conversationsProcessed++;
|
||||
|
||||
// Check if we hit the limit
|
||||
if (maxConversations && conversationsProcessed >= maxConversations) {
|
||||
console.log(`\nReached limit of ${maxConversations} conversations`);
|
||||
db.close();
|
||||
console.log(`✅ Indexing complete! Conversations: ${conversationsProcessed}, Exchanges: ${totalExchanges}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log(`\n✅ Indexing complete! Conversations: ${conversationsProcessed}, Exchanges: ${totalExchanges}`);
|
||||
}
|
||||
|
||||
export async function indexSession(sessionId: string, concurrency: number = 1, noSummaries: boolean = false): Promise<void> {
|
||||
console.log(`Indexing session: ${sessionId}`);
|
||||
|
||||
// Find the conversation file for this session
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
const excludedProjects = getExcludedProjects();
|
||||
let found = false;
|
||||
|
||||
for (const project of projects) {
|
||||
if (excludedProjects.includes(project)) continue;
|
||||
|
||||
const projectPath = path.join(PROJECTS_DIR, project);
|
||||
if (!fs.statSync(projectPath).isDirectory()) continue;
|
||||
|
||||
const files = fs.readdirSync(projectPath).filter(f => f.includes(sessionId) && f.endsWith('.jsonl'));
|
||||
|
||||
if (files.length > 0) {
|
||||
found = true;
|
||||
const file = files[0];
|
||||
const sourcePath = path.join(projectPath, file);
|
||||
|
||||
const db = initDatabase();
|
||||
await initEmbeddings();
|
||||
|
||||
const projectArchive = path.join(ARCHIVE_DIR, project);
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
const archivePath = path.join(projectArchive, file);
|
||||
|
||||
// Archive
|
||||
if (!fs.existsSync(archivePath)) {
|
||||
fs.copyFileSync(sourcePath, archivePath);
|
||||
}
|
||||
|
||||
// Parse and summarize
|
||||
const exchanges = await parseConversation(sourcePath, project, archivePath);
|
||||
|
||||
if (exchanges.length > 0) {
|
||||
// Generate summary (unless --no-summaries)
|
||||
const summaryPath = archivePath.replace('.jsonl', '-summary.txt');
|
||||
if (!noSummaries && !fs.existsSync(summaryPath)) {
|
||||
const summary = await summarizeConversation(exchanges);
|
||||
fs.writeFileSync(summaryPath, summary, 'utf-8');
|
||||
console.log(`Summary: ${summary.split(/\s+/).length} words`);
|
||||
}
|
||||
|
||||
// Index
|
||||
for (const exchange of exchanges) {
|
||||
const embedding = await generateExchangeEmbedding(
|
||||
exchange.userMessage,
|
||||
exchange.assistantMessage
|
||||
);
|
||||
insertExchange(db, exchange, embedding);
|
||||
}
|
||||
|
||||
console.log(`✅ Indexed session ${sessionId}: ${exchanges.length} exchanges`);
|
||||
}
|
||||
|
||||
db.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
console.log(`Session ${sessionId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function indexUnprocessed(concurrency: number = 1, noSummaries: boolean = false): Promise<void> {
|
||||
console.log('Finding unprocessed conversations...');
|
||||
if (concurrency > 1) console.log(`Concurrency: ${concurrency}`);
|
||||
if (noSummaries) console.log('⚠️ Running in no-summaries mode (skipping AI summaries)');
|
||||
|
||||
const db = initDatabase();
|
||||
await initEmbeddings();
|
||||
|
||||
const PROJECTS_DIR = getProjectsDir();
|
||||
const ARCHIVE_DIR = getArchiveDir(); // Now uses paths.ts
|
||||
const projects = fs.readdirSync(PROJECTS_DIR);
|
||||
const excludedProjects = getExcludedProjects();
|
||||
|
||||
type UnprocessedConv = {
|
||||
project: string;
|
||||
file: string;
|
||||
sourcePath: string;
|
||||
archivePath: string;
|
||||
summaryPath: string;
|
||||
exchanges: ConversationExchange[];
|
||||
};
|
||||
|
||||
const unprocessed: UnprocessedConv[] = [];
|
||||
|
||||
// Collect all unprocessed conversations
|
||||
for (const project of projects) {
|
||||
if (excludedProjects.includes(project)) continue;
|
||||
|
||||
const projectPath = path.join(PROJECTS_DIR, project);
|
||||
if (!fs.statSync(projectPath).isDirectory()) continue;
|
||||
|
||||
const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl'));
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = path.join(projectPath, file);
|
||||
const projectArchive = path.join(ARCHIVE_DIR, project);
|
||||
const archivePath = path.join(projectArchive, file);
|
||||
const summaryPath = archivePath.replace('.jsonl', '-summary.txt');
|
||||
|
||||
// Check if already indexed in database
|
||||
const alreadyIndexed = db.prepare('SELECT COUNT(*) as count FROM exchanges WHERE archive_path = ?')
|
||||
.get(archivePath) as { count: number };
|
||||
|
||||
if (alreadyIndexed.count > 0) continue;
|
||||
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
// Archive if needed
|
||||
if (!fs.existsSync(archivePath)) {
|
||||
fs.copyFileSync(sourcePath, archivePath);
|
||||
}
|
||||
|
||||
// Parse and check
|
||||
const exchanges = await parseConversation(sourcePath, project, archivePath);
|
||||
if (exchanges.length === 0) continue;
|
||||
|
||||
unprocessed.push({ project, file, sourcePath, archivePath, summaryPath, exchanges });
|
||||
}
|
||||
}
|
||||
|
||||
if (unprocessed.length === 0) {
|
||||
console.log('✅ All conversations are already processed!');
|
||||
db.close();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${unprocessed.length} unprocessed conversations`);
|
||||
|
||||
// Batch process summaries (unless --no-summaries)
|
||||
if (!noSummaries) {
|
||||
const needsSummary = unprocessed.filter(c => !fs.existsSync(c.summaryPath));
|
||||
if (needsSummary.length > 0) {
|
||||
console.log(`Generating ${needsSummary.length} summaries (concurrency: ${concurrency})...\n`);
|
||||
|
||||
await processBatch(needsSummary, async (conv) => {
|
||||
try {
|
||||
const summary = await summarizeConversation(conv.exchanges);
|
||||
fs.writeFileSync(conv.summaryPath, summary, 'utf-8');
|
||||
const wordCount = summary.split(/\s+/).length;
|
||||
console.log(` ✓ ${conv.project}/${conv.file}: ${wordCount} words`);
|
||||
return summary;
|
||||
} catch (error) {
|
||||
console.log(` ✗ ${conv.project}/${conv.file}: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}, concurrency);
|
||||
}
|
||||
} else {
|
||||
console.log(`Skipping summaries for ${unprocessed.length} conversations (--no-summaries mode)\n`);
|
||||
}
|
||||
|
||||
// Now index embeddings
|
||||
console.log(`\nIndexing embeddings...`);
|
||||
for (const conv of unprocessed) {
|
||||
for (const exchange of conv.exchanges) {
|
||||
const embedding = await generateExchangeEmbedding(
|
||||
exchange.userMessage,
|
||||
exchange.assistantMessage
|
||||
);
|
||||
insertExchange(db, exchange, embedding);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
console.log(`\n✅ Processed ${unprocessed.length} conversations`);
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import readline from 'readline';
|
||||
import { ConversationExchange } from './types.js';
|
||||
import crypto from 'crypto';
|
||||
|
||||
interface JSONLMessage {
|
||||
type: string;
|
||||
message?: {
|
||||
role: 'user' | 'assistant';
|
||||
content: string | Array<{ type: string; text?: string }>;
|
||||
};
|
||||
timestamp?: string;
|
||||
uuid?: string;
|
||||
}
|
||||
|
||||
export async function parseConversation(
|
||||
filePath: string,
|
||||
projectName: string,
|
||||
archivePath: string
|
||||
): Promise<ConversationExchange[]> {
|
||||
const exchanges: ConversationExchange[] = [];
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
const rl = readline.createInterface({
|
||||
input: fileStream,
|
||||
crlfDelay: Infinity
|
||||
});
|
||||
|
||||
let lineNumber = 0;
|
||||
let currentExchange: {
|
||||
userMessage: string;
|
||||
userLine: number;
|
||||
assistantMessages: string[];
|
||||
lastAssistantLine: number;
|
||||
timestamp: string;
|
||||
} | null = null;
|
||||
|
||||
const finalizeExchange = () => {
|
||||
if (currentExchange && currentExchange.assistantMessages.length > 0) {
|
||||
const exchange: ConversationExchange = {
|
||||
id: crypto
|
||||
.createHash('md5')
|
||||
.update(`${archivePath}:${currentExchange.userLine}-${currentExchange.lastAssistantLine}`)
|
||||
.digest('hex'),
|
||||
project: projectName,
|
||||
timestamp: currentExchange.timestamp,
|
||||
userMessage: currentExchange.userMessage,
|
||||
assistantMessage: currentExchange.assistantMessages.join('\n\n'),
|
||||
archivePath,
|
||||
lineStart: currentExchange.userLine,
|
||||
lineEnd: currentExchange.lastAssistantLine
|
||||
};
|
||||
exchanges.push(exchange);
|
||||
}
|
||||
};
|
||||
|
||||
for await (const line of rl) {
|
||||
lineNumber++;
|
||||
|
||||
try {
|
||||
const parsed: JSONLMessage = JSON.parse(line);
|
||||
|
||||
// Skip non-message types
|
||||
if (parsed.type !== 'user' && parsed.type !== 'assistant') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!parsed.message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract text from message content
|
||||
let text = '';
|
||||
if (typeof parsed.message.content === 'string') {
|
||||
text = parsed.message.content;
|
||||
} else if (Array.isArray(parsed.message.content)) {
|
||||
text = parsed.message.content
|
||||
.filter(block => block.type === 'text' && block.text)
|
||||
.map(block => block.text)
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
// Skip empty messages
|
||||
if (!text.trim()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parsed.message.role === 'user') {
|
||||
// Finalize previous exchange before starting new one
|
||||
finalizeExchange();
|
||||
|
||||
// Start new exchange
|
||||
currentExchange = {
|
||||
userMessage: text,
|
||||
userLine: lineNumber,
|
||||
assistantMessages: [],
|
||||
lastAssistantLine: lineNumber,
|
||||
timestamp: parsed.timestamp || new Date().toISOString()
|
||||
};
|
||||
} else if (parsed.message.role === 'assistant' && currentExchange) {
|
||||
// Accumulate assistant messages
|
||||
currentExchange.assistantMessages.push(text);
|
||||
currentExchange.lastAssistantLine = lineNumber;
|
||||
// Update timestamp to last assistant message
|
||||
if (parsed.timestamp) {
|
||||
currentExchange.timestamp = parsed.timestamp;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Skip malformed JSON lines
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize last exchange
|
||||
finalizeExchange();
|
||||
|
||||
return exchanges;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Get the personal superpowers directory
|
||||
*
|
||||
* Precedence:
|
||||
* 1. PERSONAL_SUPERPOWERS_DIR env var (if set)
|
||||
* 2. XDG_CONFIG_HOME/superpowers (if XDG_CONFIG_HOME is set)
|
||||
* 3. ~/.config/superpowers (default)
|
||||
*/
|
||||
export function getSuperpowersDir(): string {
|
||||
if (process.env.PERSONAL_SUPERPOWERS_DIR) {
|
||||
return process.env.PERSONAL_SUPERPOWERS_DIR;
|
||||
}
|
||||
|
||||
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
||||
if (xdgConfigHome) {
|
||||
return path.join(xdgConfigHome, 'superpowers');
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.config', 'superpowers');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation archive directory
|
||||
*/
|
||||
export function getArchiveDir(): string {
|
||||
// Allow test override
|
||||
if (process.env.TEST_ARCHIVE_DIR) {
|
||||
return process.env.TEST_ARCHIVE_DIR;
|
||||
}
|
||||
|
||||
return path.join(getSuperpowersDir(), 'conversation-archive');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation index directory
|
||||
*/
|
||||
export function getIndexDir(): string {
|
||||
return path.join(getSuperpowersDir(), 'conversation-index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database path
|
||||
*/
|
||||
export function getDbPath(): string {
|
||||
return path.join(getIndexDir(), 'db.sqlite');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exclude config path
|
||||
*/
|
||||
export function getExcludeConfigPath(): string {
|
||||
return path.join(getIndexDir(), 'exclude.txt');
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
describe('search-agent template', () => {
|
||||
const templatePath = path.join(__dirname, '..', 'prompts', 'search-agent.md');
|
||||
|
||||
it('exists at expected location', () => {
|
||||
expect(fs.existsSync(templatePath)).toBe(true);
|
||||
});
|
||||
|
||||
it('contains required placeholders', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Check for all required placeholders
|
||||
expect(content).toContain('{TOPIC}');
|
||||
expect(content).toContain('{SEARCH_QUERY}');
|
||||
expect(content).toContain('{FOCUS_AREAS}');
|
||||
});
|
||||
|
||||
it('contains required output sections', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Check for required output format sections
|
||||
expect(content).toContain('### Summary');
|
||||
expect(content).toContain('### Sources');
|
||||
expect(content).toContain('### For Follow-Up');
|
||||
});
|
||||
|
||||
it('specifies word count requirements', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Should specify 200-1000 words for synthesis
|
||||
expect(content).toMatch(/200-1000 words/);
|
||||
expect(content).toMatch(/max 1000 words/);
|
||||
});
|
||||
|
||||
it('includes source metadata requirements', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Check for source metadata fields
|
||||
expect(content).toContain('project-name');
|
||||
expect(content).toContain('YYYY-MM-DD');
|
||||
expect(content).toContain('% match');
|
||||
expect(content).toContain('Conversation summary:');
|
||||
expect(content).toContain('File:');
|
||||
expect(content).toContain('Status:');
|
||||
expect(content).toContain('Read in detail');
|
||||
expect(content).toContain('Reviewed summary only');
|
||||
expect(content).toContain('Skimmed');
|
||||
});
|
||||
|
||||
it('provides search command', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Should include the search command
|
||||
expect(content).toContain('~/.claude/skills/collaboration/remembering-conversations/tool/search-conversations');
|
||||
});
|
||||
|
||||
it('includes critical rules', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Check for DO and DO NOT sections
|
||||
expect(content).toContain('## Critical Rules');
|
||||
expect(content).toContain('**DO:**');
|
||||
expect(content).toContain('**DO NOT:**');
|
||||
});
|
||||
|
||||
it('includes complete example output', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Check example has all required components
|
||||
expect(content).toContain('## Example Output');
|
||||
|
||||
// Example should show Summary, Sources, and For Follow-Up
|
||||
const exampleSection = content.substring(content.indexOf('## Example Output'));
|
||||
expect(exampleSection).toContain('### Summary');
|
||||
expect(exampleSection).toContain('### Sources');
|
||||
expect(exampleSection).toContain('### For Follow-Up');
|
||||
|
||||
// Example should show specific details
|
||||
expect(exampleSection).toContain('react-router-7-starter');
|
||||
expect(exampleSection).toContain('92% match');
|
||||
expect(exampleSection).toContain('.jsonl');
|
||||
});
|
||||
|
||||
it('emphasizes synthesis over raw excerpts', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Should explicitly discourage raw conversation excerpts
|
||||
expect(content).toContain('synthesize');
|
||||
expect(content).toContain('raw conversation excerpts');
|
||||
expect(content).toContain('synthesize instead');
|
||||
});
|
||||
|
||||
it('provides follow-up options', () => {
|
||||
const content = fs.readFileSync(templatePath, 'utf-8');
|
||||
|
||||
// Should explain how main agent can follow up
|
||||
expect(content).toContain('Main agent can:');
|
||||
expect(content).toContain('dig deeper');
|
||||
expect(content).toContain('refined query');
|
||||
expect(content).toContain('context bloat');
|
||||
});
|
||||
});
|
||||
@@ -1,28 +0,0 @@
|
||||
import { searchConversations, formatResults, SearchOptions } from './search.js';
|
||||
|
||||
const query = process.argv[2];
|
||||
const mode = (process.argv[3] || 'vector') as 'vector' | 'text' | 'both';
|
||||
const limit = parseInt(process.argv[4] || '10');
|
||||
const after = process.argv[5] || undefined;
|
||||
const before = process.argv[6] || undefined;
|
||||
|
||||
if (!query) {
|
||||
console.error('Usage: search-conversations <query> [mode] [limit] [after] [before]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const options: SearchOptions = {
|
||||
mode,
|
||||
limit,
|
||||
after,
|
||||
before
|
||||
};
|
||||
|
||||
searchConversations(query, options)
|
||||
.then(results => {
|
||||
console.log(formatResults(results));
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error searching:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,173 +0,0 @@
|
||||
import Database from 'better-sqlite3';
|
||||
import { initDatabase } from './db.js';
|
||||
import { initEmbeddings, generateEmbedding } from './embeddings.js';
|
||||
import { SearchResult, ConversationExchange } from './types.js';
|
||||
import fs from 'fs';
|
||||
|
||||
export interface SearchOptions {
|
||||
limit?: number;
|
||||
mode?: 'vector' | 'text' | 'both';
|
||||
after?: string; // ISO date string
|
||||
before?: string; // ISO date string
|
||||
}
|
||||
|
||||
function validateISODate(dateStr: string, paramName: string): void {
|
||||
const isoDateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
if (!isoDateRegex.test(dateStr)) {
|
||||
throw new Error(`Invalid ${paramName} date: "${dateStr}". Expected YYYY-MM-DD format (e.g., 2025-10-01)`);
|
||||
}
|
||||
// Verify it's actually a valid date
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error(`Invalid ${paramName} date: "${dateStr}". Not a valid calendar date.`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function searchConversations(
|
||||
query: string,
|
||||
options: SearchOptions = {}
|
||||
): Promise<SearchResult[]> {
|
||||
const { limit = 10, mode = 'vector', after, before } = options;
|
||||
|
||||
// Validate date parameters
|
||||
if (after) validateISODate(after, '--after');
|
||||
if (before) validateISODate(before, '--before');
|
||||
|
||||
const db = initDatabase();
|
||||
|
||||
let results: any[] = [];
|
||||
|
||||
// Build time filter clause
|
||||
const timeFilter = [];
|
||||
if (after) timeFilter.push(`e.timestamp >= '${after}'`);
|
||||
if (before) timeFilter.push(`e.timestamp <= '${before}'`);
|
||||
const timeClause = timeFilter.length > 0 ? `AND ${timeFilter.join(' AND ')}` : '';
|
||||
|
||||
if (mode === 'vector' || mode === 'both') {
|
||||
// Vector similarity search
|
||||
await initEmbeddings();
|
||||
const queryEmbedding = await generateEmbedding(query);
|
||||
|
||||
const stmt = db.prepare(`
|
||||
SELECT
|
||||
e.id,
|
||||
e.project,
|
||||
e.timestamp,
|
||||
e.user_message,
|
||||
e.assistant_message,
|
||||
e.archive_path,
|
||||
e.line_start,
|
||||
e.line_end,
|
||||
vec.distance
|
||||
FROM vec_exchanges AS vec
|
||||
JOIN exchanges AS e ON vec.id = e.id
|
||||
WHERE vec.embedding MATCH ?
|
||||
AND k = ?
|
||||
${timeClause}
|
||||
ORDER BY vec.distance ASC
|
||||
`);
|
||||
|
||||
results = stmt.all(
|
||||
Buffer.from(new Float32Array(queryEmbedding).buffer),
|
||||
limit
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === 'text' || mode === 'both') {
|
||||
// Text search
|
||||
const textStmt = db.prepare(`
|
||||
SELECT
|
||||
e.id,
|
||||
e.project,
|
||||
e.timestamp,
|
||||
e.user_message,
|
||||
e.assistant_message,
|
||||
e.archive_path,
|
||||
e.line_start,
|
||||
e.line_end,
|
||||
0 as distance
|
||||
FROM exchanges AS e
|
||||
WHERE (e.user_message LIKE ? OR e.assistant_message LIKE ?)
|
||||
${timeClause}
|
||||
ORDER BY e.timestamp DESC
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
const textResults = textStmt.all(`%${query}%`, `%${query}%`, limit);
|
||||
|
||||
if (mode === 'both') {
|
||||
// Merge and deduplicate by ID
|
||||
const seenIds = new Set(results.map(r => r.id));
|
||||
for (const textResult of textResults) {
|
||||
if (!seenIds.has(textResult.id)) {
|
||||
results.push(textResult);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results = textResults;
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
return results.map((row: any) => {
|
||||
const exchange: ConversationExchange = {
|
||||
id: row.id,
|
||||
project: row.project,
|
||||
timestamp: row.timestamp,
|
||||
userMessage: row.user_message,
|
||||
assistantMessage: row.assistant_message,
|
||||
archivePath: row.archive_path,
|
||||
lineStart: row.line_start,
|
||||
lineEnd: row.line_end
|
||||
};
|
||||
|
||||
// Try to load summary if available
|
||||
const summaryPath = row.archive_path.replace('.jsonl', '-summary.txt');
|
||||
let summary: string | undefined;
|
||||
if (fs.existsSync(summaryPath)) {
|
||||
summary = fs.readFileSync(summaryPath, 'utf-8').trim();
|
||||
}
|
||||
|
||||
// Create snippet (first 200 chars)
|
||||
const snippet = exchange.userMessage.substring(0, 200) +
|
||||
(exchange.userMessage.length > 200 ? '...' : '');
|
||||
|
||||
return {
|
||||
exchange,
|
||||
similarity: mode === 'text' ? undefined : 1 - row.distance,
|
||||
snippet,
|
||||
summary
|
||||
} as SearchResult & { summary?: string };
|
||||
});
|
||||
}
|
||||
|
||||
export function formatResults(results: Array<SearchResult & { summary?: string }>): string {
|
||||
if (results.length === 0) {
|
||||
return 'No results found.';
|
||||
}
|
||||
|
||||
let output = `Found ${results.length} relevant conversations:\n\n`;
|
||||
|
||||
results.forEach((result, index) => {
|
||||
const date = new Date(result.exchange.timestamp).toISOString().split('T')[0];
|
||||
output += `${index + 1}. [${result.exchange.project}, ${date}]\n`;
|
||||
|
||||
// Show conversation summary if available
|
||||
if (result.summary) {
|
||||
output += ` ${result.summary}\n\n`;
|
||||
}
|
||||
|
||||
// Show match with similarity percentage
|
||||
if (result.similarity !== undefined) {
|
||||
const pct = Math.round(result.similarity * 100);
|
||||
output += ` ${pct}% match: "${result.snippet}"\n`;
|
||||
} else {
|
||||
output += ` Match: "${result.snippet}"\n`;
|
||||
}
|
||||
|
||||
output += ` ${result.exchange.archivePath}:${result.exchange.lineStart}-${result.exchange.lineEnd}\n\n`;
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
import { ConversationExchange } from './types.js';
|
||||
import { query } from '@anthropic-ai/claude-agent-sdk';
|
||||
|
||||
export function formatConversationText(exchanges: ConversationExchange[]): string {
|
||||
return exchanges.map(ex => {
|
||||
return `User: ${ex.userMessage}\n\nAgent: ${ex.assistantMessage}`;
|
||||
}).join('\n\n---\n\n');
|
||||
}
|
||||
|
||||
function extractSummary(text: string): string {
|
||||
const match = text.match(/<summary>(.*?)<\/summary>/s);
|
||||
if (match) {
|
||||
return match[1].trim();
|
||||
}
|
||||
// Fallback if no tags found
|
||||
return text.trim();
|
||||
}
|
||||
|
||||
async function callClaude(prompt: string, useSonnet = false): Promise<string> {
|
||||
const model = useSonnet ? 'sonnet' : 'haiku';
|
||||
|
||||
for await (const message of query({
|
||||
prompt,
|
||||
options: {
|
||||
model,
|
||||
maxTokens: 4096,
|
||||
maxThinkingTokens: 0, // Disable extended thinking
|
||||
systemPrompt: 'Write concise, factual summaries. Output ONLY the summary - no preamble, no "Here is", no "I will". Your output will be indexed directly.'
|
||||
}
|
||||
})) {
|
||||
if (message && typeof message === 'object' && 'type' in message && message.type === 'result') {
|
||||
const result = (message as any).result;
|
||||
|
||||
// Check if result is an API error (SDK returns errors as result strings)
|
||||
if (typeof result === 'string' && result.includes('API Error') && result.includes('thinking.budget_tokens')) {
|
||||
if (!useSonnet) {
|
||||
console.log(` Haiku hit thinking budget error, retrying with Sonnet`);
|
||||
return await callClaude(prompt, true);
|
||||
}
|
||||
// If Sonnet also fails, return error message
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function chunkExchanges(exchanges: ConversationExchange[], chunkSize: number): ConversationExchange[][] {
|
||||
const chunks: ConversationExchange[][] = [];
|
||||
for (let i = 0; i < exchanges.length; i += chunkSize) {
|
||||
chunks.push(exchanges.slice(i, i + chunkSize));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
export async function summarizeConversation(exchanges: ConversationExchange[]): Promise<string> {
|
||||
// Handle trivial conversations
|
||||
if (exchanges.length === 0) {
|
||||
return 'Trivial conversation with no substantive content.';
|
||||
}
|
||||
|
||||
if (exchanges.length === 1) {
|
||||
const text = formatConversationText(exchanges);
|
||||
if (text.length < 100 || exchanges[0].userMessage.trim() === '/exit') {
|
||||
return 'Trivial conversation with no substantive content.';
|
||||
}
|
||||
}
|
||||
|
||||
// For short conversations (≤15 exchanges), summarize directly
|
||||
if (exchanges.length <= 15) {
|
||||
const conversationText = formatConversationText(exchanges);
|
||||
const prompt = `Context: This summary will be shown in a list to help users and Claude choose which conversations are relevant to a future activity.
|
||||
|
||||
Summarize what happened in 2-4 sentences. Be factual and specific. Output in <summary></summary> tags.
|
||||
|
||||
Include:
|
||||
- What was built/changed/discussed (be specific)
|
||||
- Key technical decisions or approaches
|
||||
- Problems solved or current state
|
||||
|
||||
Exclude:
|
||||
- Apologies, meta-commentary, or your questions
|
||||
- Raw logs or debug output
|
||||
- Generic descriptions - focus on what makes THIS conversation unique
|
||||
|
||||
Good:
|
||||
<summary>Built JWT authentication for React app with refresh tokens and protected routes. Fixed token expiration bug by implementing refresh-during-request logic.</summary>
|
||||
|
||||
Bad:
|
||||
<summary>I apologize. The conversation discussed authentication and various approaches were considered...</summary>
|
||||
|
||||
${conversationText}`;
|
||||
|
||||
const result = await callClaude(prompt);
|
||||
return extractSummary(result);
|
||||
}
|
||||
|
||||
// For long conversations, use hierarchical summarization
|
||||
console.log(` Long conversation (${exchanges.length} exchanges) - using hierarchical summarization`);
|
||||
|
||||
// Chunk into groups of 8 exchanges
|
||||
const chunks = chunkExchanges(exchanges, 8);
|
||||
console.log(` Split into ${chunks.length} chunks`);
|
||||
|
||||
// Summarize each chunk
|
||||
const chunkSummaries: string[] = [];
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const chunkText = formatConversationText(chunks[i]);
|
||||
const prompt = `Summarize this part of a conversation in 2-3 sentences. What happened, what was built/discussed. Use <summary></summary> tags.
|
||||
|
||||
${chunkText}
|
||||
|
||||
Example: <summary>Implemented HID keyboard functionality for ESP32. Hit Bluetooth controller initialization error, fixed by adjusting memory allocation.</summary>`;
|
||||
|
||||
try {
|
||||
const summary = await callClaude(prompt);
|
||||
const extracted = extractSummary(summary);
|
||||
chunkSummaries.push(extracted);
|
||||
console.log(` Chunk ${i + 1}/${chunks.length}: ${extracted.split(/\s+/).length} words`);
|
||||
} catch (error) {
|
||||
console.log(` Chunk ${i + 1} failed, skipping`);
|
||||
}
|
||||
}
|
||||
|
||||
if (chunkSummaries.length === 0) {
|
||||
return 'Error: Unable to summarize conversation.';
|
||||
}
|
||||
|
||||
// Synthesize chunks into final summary
|
||||
const synthesisPrompt = `Context: This summary will be shown in a list to help users and Claude choose which past conversations are relevant to a future activity.
|
||||
|
||||
Synthesize these part-summaries into one cohesive paragraph. Focus on what was accomplished and any notable technical decisions or challenges. Output in <summary></summary> tags.
|
||||
|
||||
Part summaries:
|
||||
${chunkSummaries.map((s, i) => `${i + 1}. ${s}`).join('\n')}
|
||||
|
||||
Good:
|
||||
<summary>Built conversation search system with JavaScript, sqlite-vec, and local embeddings. Implemented hierarchical summarization for long conversations. System archives conversations permanently and provides semantic search via CLI.</summary>
|
||||
|
||||
Bad:
|
||||
<summary>This conversation synthesizes several topics discussed across multiple parts...</summary>
|
||||
|
||||
Your summary (max 200 words):`;
|
||||
|
||||
console.log(` Synthesizing final summary...`);
|
||||
try {
|
||||
const result = await callClaude(synthesisPrompt);
|
||||
return extractSummary(result);
|
||||
} catch (error) {
|
||||
console.log(` Synthesis failed, using chunk summaries`);
|
||||
return chunkSummaries.join(' ');
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export interface ConversationExchange {
|
||||
id: string;
|
||||
project: string;
|
||||
timestamp: string;
|
||||
userMessage: string;
|
||||
assistantMessage: string;
|
||||
archivePath: string;
|
||||
lineStart: number;
|
||||
lineEnd: number;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
exchange: ConversationExchange;
|
||||
similarity: number;
|
||||
snippet: string;
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { verifyIndex, repairIndex, VerificationResult } from './verify.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { initDatabase, insertExchange } from './db.js';
|
||||
import { ConversationExchange } from './types.js';
|
||||
|
||||
describe('verifyIndex', () => {
|
||||
const testDir = path.join(os.tmpdir(), 'conversation-search-test-' + Date.now());
|
||||
const projectsDir = path.join(testDir, '.claude', 'projects');
|
||||
const archiveDir = path.join(testDir, '.clank', 'conversation-archive');
|
||||
const dbPath = path.join(testDir, '.clank', 'conversation-index', 'db.sqlite');
|
||||
|
||||
beforeEach(() => {
|
||||
// Create test directories
|
||||
fs.mkdirSync(path.join(testDir, '.clank', 'conversation-index'), { recursive: true });
|
||||
fs.mkdirSync(projectsDir, { recursive: true });
|
||||
fs.mkdirSync(archiveDir, { recursive: true });
|
||||
|
||||
// Override environment paths for testing
|
||||
process.env.TEST_PROJECTS_DIR = projectsDir;
|
||||
process.env.TEST_ARCHIVE_DIR = archiveDir;
|
||||
process.env.TEST_DB_PATH = dbPath;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up test directory
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
delete process.env.TEST_PROJECTS_DIR;
|
||||
delete process.env.TEST_ARCHIVE_DIR;
|
||||
delete process.env.TEST_DB_PATH;
|
||||
});
|
||||
|
||||
it('detects missing summaries', async () => {
|
||||
// Create a test conversation file without a summary
|
||||
const projectArchive = path.join(archiveDir, 'test-project');
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
const conversationPath = path.join(projectArchive, 'test-conversation.jsonl');
|
||||
|
||||
// Create proper JSONL format (one JSON object per line)
|
||||
const messages = [
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'Hello' }, timestamp: '2024-01-01T00:00:00Z' }),
|
||||
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: 'Hi there!' }, timestamp: '2024-01-01T00:00:01Z' })
|
||||
];
|
||||
fs.writeFileSync(conversationPath, messages.join('\n'));
|
||||
|
||||
const result = await verifyIndex();
|
||||
|
||||
expect(result.missing.length).toBe(1);
|
||||
expect(result.missing[0].path).toBe(conversationPath);
|
||||
expect(result.missing[0].reason).toBe('No summary file');
|
||||
});
|
||||
|
||||
it('detects orphaned database entries', async () => {
|
||||
// Initialize database
|
||||
const db = initDatabase();
|
||||
|
||||
// Create an exchange in the database
|
||||
const exchange: ConversationExchange = {
|
||||
id: 'orphan-id-1',
|
||||
project: 'deleted-project',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
userMessage: 'This conversation was deleted',
|
||||
assistantMessage: 'But still in database',
|
||||
archivePath: path.join(archiveDir, 'deleted-project', 'deleted.jsonl'),
|
||||
lineStart: 1,
|
||||
lineEnd: 2
|
||||
};
|
||||
|
||||
const embedding = new Array(384).fill(0.1);
|
||||
insertExchange(db, exchange, embedding);
|
||||
db.close();
|
||||
|
||||
// Verify detects orphaned entry (file doesn't exist)
|
||||
const result = await verifyIndex();
|
||||
|
||||
expect(result.orphaned.length).toBe(1);
|
||||
expect(result.orphaned[0].uuid).toBe('orphan-id-1');
|
||||
expect(result.orphaned[0].path).toBe(exchange.archivePath);
|
||||
});
|
||||
|
||||
it('detects outdated files (file modified after last_indexed)', async () => {
|
||||
// Create conversation file with summary
|
||||
const projectArchive = path.join(archiveDir, 'test-project');
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
const conversationPath = path.join(projectArchive, 'updated-conversation.jsonl');
|
||||
const summaryPath = conversationPath.replace('.jsonl', '-summary.txt');
|
||||
|
||||
// Create initial conversation
|
||||
const messages = [
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'Hello' }, timestamp: '2024-01-01T00:00:00Z' }),
|
||||
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: 'Hi there!' }, timestamp: '2024-01-01T00:00:01Z' })
|
||||
];
|
||||
fs.writeFileSync(conversationPath, messages.join('\n'));
|
||||
fs.writeFileSync(summaryPath, 'Test summary');
|
||||
|
||||
// Index it
|
||||
const db = initDatabase();
|
||||
const exchange: ConversationExchange = {
|
||||
id: 'updated-id-1',
|
||||
project: 'test-project',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
userMessage: 'Hello',
|
||||
assistantMessage: 'Hi there!',
|
||||
archivePath: conversationPath,
|
||||
lineStart: 1,
|
||||
lineEnd: 2
|
||||
};
|
||||
|
||||
const embedding = new Array(384).fill(0.1);
|
||||
insertExchange(db, exchange, embedding);
|
||||
|
||||
// Get the last_indexed timestamp
|
||||
const row = db.prepare(`SELECT last_indexed FROM exchanges WHERE id = ?`).get('updated-id-1') as any;
|
||||
const lastIndexed = row.last_indexed;
|
||||
db.close();
|
||||
|
||||
// Wait a bit, then modify the file
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// Update the conversation file
|
||||
const updatedMessages = [
|
||||
...messages,
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'New message' }, timestamp: '2024-01-01T00:00:02Z' })
|
||||
];
|
||||
fs.writeFileSync(conversationPath, updatedMessages.join('\n'));
|
||||
|
||||
// Verify detects outdated file
|
||||
const result = await verifyIndex();
|
||||
|
||||
expect(result.outdated.length).toBe(1);
|
||||
expect(result.outdated[0].path).toBe(conversationPath);
|
||||
expect(result.outdated[0].dbTime).toBe(lastIndexed);
|
||||
expect(result.outdated[0].fileTime).toBeGreaterThan(lastIndexed);
|
||||
});
|
||||
|
||||
// Note: Parser is resilient to malformed JSON - it skips bad lines
|
||||
// Corruption detection would require file system errors or permission issues
|
||||
// which are harder to test. Skipping for now as missing summaries is the
|
||||
// primary use case for verification.
|
||||
});
|
||||
|
||||
describe('repairIndex', () => {
|
||||
const testDir = path.join(os.tmpdir(), 'conversation-repair-test-' + Date.now());
|
||||
const projectsDir = path.join(testDir, '.claude', 'projects');
|
||||
const archiveDir = path.join(testDir, '.clank', 'conversation-archive');
|
||||
const dbPath = path.join(testDir, '.clank', 'conversation-index', 'db.sqlite');
|
||||
|
||||
beforeEach(() => {
|
||||
// Create test directories
|
||||
fs.mkdirSync(path.join(testDir, '.clank', 'conversation-index'), { recursive: true });
|
||||
fs.mkdirSync(projectsDir, { recursive: true });
|
||||
fs.mkdirSync(archiveDir, { recursive: true });
|
||||
|
||||
// Override environment paths for testing
|
||||
process.env.TEST_PROJECTS_DIR = projectsDir;
|
||||
process.env.TEST_ARCHIVE_DIR = archiveDir;
|
||||
process.env.TEST_DB_PATH = dbPath;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Clean up test directory
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
delete process.env.TEST_PROJECTS_DIR;
|
||||
delete process.env.TEST_ARCHIVE_DIR;
|
||||
delete process.env.TEST_DB_PATH;
|
||||
});
|
||||
|
||||
it('deletes orphaned database entries during repair', async () => {
|
||||
// Initialize database with orphaned entry
|
||||
const db = initDatabase();
|
||||
|
||||
const exchange: ConversationExchange = {
|
||||
id: 'orphan-repair-1',
|
||||
project: 'deleted-project',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
userMessage: 'This conversation was deleted',
|
||||
assistantMessage: 'But still in database',
|
||||
archivePath: path.join(archiveDir, 'deleted-project', 'deleted.jsonl'),
|
||||
lineStart: 1,
|
||||
lineEnd: 2
|
||||
};
|
||||
|
||||
const embedding = new Array(384).fill(0.1);
|
||||
insertExchange(db, exchange, embedding);
|
||||
db.close();
|
||||
|
||||
// Verify it's there
|
||||
const dbBefore = initDatabase();
|
||||
const beforeCount = dbBefore.prepare(`SELECT COUNT(*) as count FROM exchanges WHERE id = ?`).get('orphan-repair-1') as { count: number };
|
||||
expect(beforeCount.count).toBe(1);
|
||||
dbBefore.close();
|
||||
|
||||
// Run repair
|
||||
const issues = await verifyIndex();
|
||||
expect(issues.orphaned.length).toBe(1);
|
||||
await repairIndex(issues);
|
||||
|
||||
// Verify it's gone
|
||||
const dbAfter = initDatabase();
|
||||
const afterCount = dbAfter.prepare(`SELECT COUNT(*) as count FROM exchanges WHERE id = ?`).get('orphan-repair-1') as { count: number };
|
||||
expect(afterCount.count).toBe(0);
|
||||
dbAfter.close();
|
||||
});
|
||||
|
||||
it('re-indexes outdated files during repair', { timeout: 30000 }, async () => {
|
||||
// Create conversation file with summary
|
||||
const projectArchive = path.join(archiveDir, 'test-project');
|
||||
fs.mkdirSync(projectArchive, { recursive: true });
|
||||
|
||||
const conversationPath = path.join(projectArchive, 'outdated-repair.jsonl');
|
||||
const summaryPath = conversationPath.replace('.jsonl', '-summary.txt');
|
||||
|
||||
// Create initial conversation
|
||||
const messages = [
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'Hello' }, timestamp: '2024-01-01T00:00:00Z' }),
|
||||
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: 'Hi there!' }, timestamp: '2024-01-01T00:00:01Z' })
|
||||
];
|
||||
fs.writeFileSync(conversationPath, messages.join('\n'));
|
||||
fs.writeFileSync(summaryPath, 'Old summary');
|
||||
|
||||
// Index it
|
||||
const db = initDatabase();
|
||||
const exchange: ConversationExchange = {
|
||||
id: 'outdated-repair-1',
|
||||
project: 'test-project',
|
||||
timestamp: '2024-01-01T00:00:00Z',
|
||||
userMessage: 'Hello',
|
||||
assistantMessage: 'Hi there!',
|
||||
archivePath: conversationPath,
|
||||
lineStart: 1,
|
||||
lineEnd: 2
|
||||
};
|
||||
|
||||
const embedding = new Array(384).fill(0.1);
|
||||
insertExchange(db, exchange, embedding);
|
||||
|
||||
// Get the last_indexed timestamp
|
||||
const beforeRow = db.prepare(`SELECT last_indexed FROM exchanges WHERE id = ?`).get('outdated-repair-1') as any;
|
||||
const beforeIndexed = beforeRow.last_indexed;
|
||||
db.close();
|
||||
|
||||
// Wait a bit, then modify the file
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// Update the conversation file (add new exchange)
|
||||
const updatedMessages = [
|
||||
...messages,
|
||||
JSON.stringify({ type: 'user', message: { role: 'user', content: 'New message' }, timestamp: '2024-01-01T00:00:02Z' }),
|
||||
JSON.stringify({ type: 'assistant', message: { role: 'assistant', content: 'New response' }, timestamp: '2024-01-01T00:00:03Z' })
|
||||
];
|
||||
fs.writeFileSync(conversationPath, updatedMessages.join('\n'));
|
||||
|
||||
// Verify detects outdated
|
||||
const issues = await verifyIndex();
|
||||
expect(issues.outdated.length).toBe(1);
|
||||
|
||||
// Wait a bit to ensure different timestamp
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
|
||||
// Run repair
|
||||
await repairIndex(issues);
|
||||
|
||||
// Verify it was re-indexed with new timestamp
|
||||
const dbAfter = initDatabase();
|
||||
const afterRow = dbAfter.prepare(`SELECT MAX(last_indexed) as last_indexed FROM exchanges WHERE archive_path = ?`).get(conversationPath) as any;
|
||||
expect(afterRow.last_indexed).toBeGreaterThan(beforeIndexed);
|
||||
|
||||
// Verify no longer outdated
|
||||
const verifyAfter = await verifyIndex();
|
||||
expect(verifyAfter.outdated.length).toBe(0);
|
||||
|
||||
dbAfter.close();
|
||||
});
|
||||
});
|
||||
@@ -1,177 +0,0 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { parseConversation } from './parser.js';
|
||||
import { initDatabase, getAllExchanges, getFileLastIndexed } from './db.js';
|
||||
import { getArchiveDir } from './paths.js';
|
||||
|
||||
export interface VerificationResult {
|
||||
missing: Array<{ path: string; reason: string }>;
|
||||
orphaned: Array<{ uuid: string; path: string }>;
|
||||
outdated: Array<{ path: string; fileTime: number; dbTime: number }>;
|
||||
corrupted: Array<{ path: string; error: string }>;
|
||||
}
|
||||
|
||||
export async function verifyIndex(): Promise<VerificationResult> {
|
||||
const result: VerificationResult = {
|
||||
missing: [],
|
||||
orphaned: [],
|
||||
outdated: [],
|
||||
corrupted: []
|
||||
};
|
||||
|
||||
const archiveDir = getArchiveDir();
|
||||
|
||||
// Track all files we find
|
||||
const foundFiles = new Set<string>();
|
||||
|
||||
// Find all conversation files
|
||||
if (!fs.existsSync(archiveDir)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Initialize database once for all checks
|
||||
const db = initDatabase();
|
||||
|
||||
const projects = fs.readdirSync(archiveDir);
|
||||
let totalChecked = 0;
|
||||
|
||||
for (const project of projects) {
|
||||
const projectPath = path.join(archiveDir, project);
|
||||
const stat = fs.statSync(projectPath);
|
||||
|
||||
if (!stat.isDirectory()) continue;
|
||||
|
||||
const files = fs.readdirSync(projectPath).filter(f => f.endsWith('.jsonl'));
|
||||
|
||||
for (const file of files) {
|
||||
totalChecked++;
|
||||
|
||||
if (totalChecked % 100 === 0) {
|
||||
console.log(` Checked ${totalChecked} conversations...`);
|
||||
}
|
||||
|
||||
const conversationPath = path.join(projectPath, file);
|
||||
foundFiles.add(conversationPath);
|
||||
|
||||
const summaryPath = conversationPath.replace('.jsonl', '-summary.txt');
|
||||
|
||||
// Check for missing summary
|
||||
if (!fs.existsSync(summaryPath)) {
|
||||
result.missing.push({ path: conversationPath, reason: 'No summary file' });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if file is outdated (modified after last_indexed)
|
||||
const lastIndexed = getFileLastIndexed(db, conversationPath);
|
||||
if (lastIndexed !== null) {
|
||||
const fileStat = fs.statSync(conversationPath);
|
||||
if (fileStat.mtimeMs > lastIndexed) {
|
||||
result.outdated.push({
|
||||
path: conversationPath,
|
||||
fileTime: fileStat.mtimeMs,
|
||||
dbTime: lastIndexed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing to detect corruption
|
||||
try {
|
||||
await parseConversation(conversationPath, project, conversationPath);
|
||||
} catch (error) {
|
||||
result.corrupted.push({
|
||||
path: conversationPath,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Verified ${totalChecked} conversations.`);
|
||||
|
||||
// Check for orphaned database entries
|
||||
const dbExchanges = getAllExchanges(db);
|
||||
db.close();
|
||||
|
||||
for (const exchange of dbExchanges) {
|
||||
if (!foundFiles.has(exchange.archivePath)) {
|
||||
result.orphaned.push({
|
||||
uuid: exchange.id,
|
||||
path: exchange.archivePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function repairIndex(issues: VerificationResult): Promise<void> {
|
||||
console.log('Repairing index...');
|
||||
|
||||
// To avoid circular dependencies, we import the indexer functions dynamically
|
||||
const { initDatabase, insertExchange, deleteExchange } = await import('./db.js');
|
||||
const { parseConversation } = await import('./parser.js');
|
||||
const { initEmbeddings, generateExchangeEmbedding } = await import('./embeddings.js');
|
||||
const { summarizeConversation } = await import('./summarizer.js');
|
||||
|
||||
const db = initDatabase();
|
||||
await initEmbeddings();
|
||||
|
||||
// Remove orphaned entries first
|
||||
for (const orphan of issues.orphaned) {
|
||||
console.log(`Removing orphaned entry: ${orphan.uuid}`);
|
||||
deleteExchange(db, orphan.uuid);
|
||||
}
|
||||
|
||||
// Re-index missing and outdated conversations
|
||||
const toReindex = [
|
||||
...issues.missing.map(m => m.path),
|
||||
...issues.outdated.map(o => o.path)
|
||||
];
|
||||
|
||||
for (const conversationPath of toReindex) {
|
||||
console.log(`Re-indexing: ${conversationPath}`);
|
||||
try {
|
||||
// Extract project name from path
|
||||
const archiveDir = getArchiveDir();
|
||||
const relativePath = conversationPath.replace(archiveDir + path.sep, '');
|
||||
const project = relativePath.split(path.sep)[0];
|
||||
|
||||
// Parse conversation
|
||||
const exchanges = await parseConversation(conversationPath, project, conversationPath);
|
||||
|
||||
if (exchanges.length === 0) {
|
||||
console.log(` Skipped (no exchanges)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate/update summary
|
||||
const summaryPath = conversationPath.replace('.jsonl', '-summary.txt');
|
||||
const summary = await summarizeConversation(exchanges);
|
||||
fs.writeFileSync(summaryPath, summary, 'utf-8');
|
||||
console.log(` Created summary: ${summary.split(/\s+/).length} words`);
|
||||
|
||||
// Index exchanges
|
||||
for (const exchange of exchanges) {
|
||||
const embedding = await generateExchangeEmbedding(
|
||||
exchange.userMessage,
|
||||
exchange.assistantMessage
|
||||
);
|
||||
insertExchange(db, exchange, embedding);
|
||||
}
|
||||
|
||||
console.log(` Indexed ${exchanges.length} exchanges`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to re-index ${conversationPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
db.close();
|
||||
|
||||
// Report corrupted files (manual intervention needed)
|
||||
if (issues.corrupted.length > 0) {
|
||||
console.log('\n⚠️ Corrupted files (manual review needed):');
|
||||
issues.corrupted.forEach(c => console.log(` ${c.path}: ${c.error}`));
|
||||
}
|
||||
|
||||
console.log('✅ Repair complete.');
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
#!/bin/bash
|
||||
# End-to-end deployment testing
|
||||
# Tests all deployment scenarios from docs/plans/2025-10-07-deployment-plan.md
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
INSTALL_HOOK="$SCRIPT_DIR/install-hook"
|
||||
INDEX_CONVERSATIONS="$SCRIPT_DIR/index-conversations"
|
||||
|
||||
# Test counter
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Helper functions
|
||||
setup_test() {
|
||||
TEST_DIR=$(mktemp -d)
|
||||
export HOME="$TEST_DIR"
|
||||
export TEST_PROJECTS_DIR="$TEST_DIR/.claude/projects"
|
||||
export TEST_ARCHIVE_DIR="$TEST_DIR/.clank/conversation-archive"
|
||||
export TEST_DB_PATH="$TEST_DIR/.clank/conversation-index/db.sqlite"
|
||||
|
||||
mkdir -p "$HOME/.claude/hooks"
|
||||
mkdir -p "$TEST_PROJECTS_DIR"
|
||||
mkdir -p "$TEST_ARCHIVE_DIR"
|
||||
mkdir -p "$TEST_DIR/.clank/conversation-index"
|
||||
}
|
||||
|
||||
cleanup_test() {
|
||||
if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
|
||||
rm -rf "$TEST_DIR"
|
||||
fi
|
||||
unset TEST_PROJECTS_DIR
|
||||
unset TEST_ARCHIVE_DIR
|
||||
unset TEST_DB_PATH
|
||||
}
|
||||
|
||||
assert_file_exists() {
|
||||
if [ ! -f "$1" ]; then
|
||||
echo -e "${RED}❌ FAIL: File does not exist: $1${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_file_executable() {
|
||||
if [ ! -x "$1" ]; then
|
||||
echo -e "${RED}❌ FAIL: File is not executable: $1${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
if ! grep -q "$2" "$1"; then
|
||||
echo -e "${RED}❌ FAIL: File $1 does not contain: $2${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_summary_exists() {
|
||||
local jsonl_file="$1"
|
||||
|
||||
# If file is in projects dir, convert to archive path
|
||||
if [[ "$jsonl_file" == *"/.claude/projects/"* ]]; then
|
||||
jsonl_file=$(echo "$jsonl_file" | sed "s|/.claude/projects/|/.clank/conversation-archive/|")
|
||||
fi
|
||||
|
||||
local summary_file="${jsonl_file%.jsonl}-summary.txt"
|
||||
if [ ! -f "$summary_file" ]; then
|
||||
echo -e "${RED}❌ FAIL: Summary does not exist: $summary_file${NC}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
create_test_conversation() {
|
||||
local project="$1"
|
||||
local uuid="${2:-test-$(date +%s)}"
|
||||
|
||||
mkdir -p "$TEST_PROJECTS_DIR/$project"
|
||||
local conv_file="$TEST_PROJECTS_DIR/$project/${uuid}.jsonl"
|
||||
|
||||
cat > "$conv_file" <<'EOF'
|
||||
{"type":"user","message":{"role":"user","content":"What is TDD?"},"timestamp":"2024-01-01T00:00:00Z"}
|
||||
{"type":"assistant","message":{"role":"assistant","content":"TDD stands for Test-Driven Development. You write tests first."},"timestamp":"2024-01-01T00:00:01Z"}
|
||||
EOF
|
||||
|
||||
echo "$conv_file"
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
local test_func="$2"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
echo -e "\n${YELLOW}Running test: $test_name${NC}"
|
||||
|
||||
setup_test
|
||||
|
||||
if $test_func; then
|
||||
echo -e "${GREEN}✓ PASS: $test_name${NC}"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo -e "${RED}❌ FAIL: $test_name${NC}"
|
||||
fi
|
||||
|
||||
cleanup_test
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scenario 1: Fresh Installation
|
||||
# ============================================================================
|
||||
|
||||
test_scenario_1_fresh_install() {
|
||||
echo " 1. Installing hook with no existing hook..."
|
||||
"$INSTALL_HOOK" > /dev/null 2>&1 || true
|
||||
|
||||
assert_file_exists "$HOME/.claude/hooks/sessionEnd" || return 1
|
||||
assert_file_executable "$HOME/.claude/hooks/sessionEnd" || return 1
|
||||
|
||||
echo " 2. Creating test conversation..."
|
||||
local conv_file=$(create_test_conversation "test-project" "conv-1")
|
||||
|
||||
echo " 3. Indexing conversation..."
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" > /dev/null 2>&1
|
||||
|
||||
echo " 4. Verifying summary was created..."
|
||||
assert_summary_exists "$conv_file" || return 1
|
||||
|
||||
echo " 5. Testing hook triggers indexing..."
|
||||
export SESSION_ID="hook-session-$(date +%s)"
|
||||
|
||||
# Create conversation file with SESSION_ID in name
|
||||
mkdir -p "$TEST_PROJECTS_DIR/test-project"
|
||||
local new_conv="$TEST_PROJECTS_DIR/test-project/${SESSION_ID}.jsonl"
|
||||
cat > "$new_conv" <<'EOF'
|
||||
{"type":"user","message":{"role":"user","content":"What is TDD?"},"timestamp":"2024-01-01T00:00:00Z"}
|
||||
{"type":"assistant","message":{"role":"assistant","content":"TDD stands for Test-Driven Development. You write tests first."},"timestamp":"2024-01-01T00:00:01Z"}
|
||||
EOF
|
||||
|
||||
# Verify hook runs the index command (manually call indexer with --session)
|
||||
# In real environment, hook would do this automatically
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --session "$SESSION_ID" > /dev/null 2>&1
|
||||
|
||||
echo " 6. Verifying session was indexed..."
|
||||
assert_summary_exists "$new_conv" || return 1
|
||||
|
||||
echo " 7. Testing search functionality..."
|
||||
local search_result=$(cd "$SCRIPT_DIR" && "$SCRIPT_DIR/search-conversations" "TDD" 2>/dev/null || echo "")
|
||||
if [ -z "$search_result" ]; then
|
||||
echo -e "${RED}❌ Search returned no results${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scenario 2: Existing Hook (merge)
|
||||
# ============================================================================
|
||||
|
||||
test_scenario_2_existing_hook_merge() {
|
||||
echo " 1. Creating existing hook..."
|
||||
cat > "$HOME/.claude/hooks/sessionEnd" <<'EOF'
|
||||
#!/bin/bash
|
||||
# Existing hook
|
||||
echo "Existing hook running"
|
||||
EOF
|
||||
chmod +x "$HOME/.claude/hooks/sessionEnd"
|
||||
|
||||
echo " 2. Installing with merge option..."
|
||||
echo "m" | "$INSTALL_HOOK" > /dev/null 2>&1 || true
|
||||
|
||||
echo " 3. Verifying backup created..."
|
||||
local backup_count=$(ls -1 "$HOME/.claude/hooks/sessionEnd.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$backup_count" -lt 1 ]; then
|
||||
echo -e "${RED}❌ No backup created${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " 4. Verifying merge preserved existing content..."
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "Existing hook running" || return 1
|
||||
|
||||
echo " 5. Verifying indexer was appended..."
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "remembering-conversations.*index-conversations" || return 1
|
||||
|
||||
echo " 6. Testing merged hook runs both parts..."
|
||||
local conv_file=$(create_test_conversation "merge-project" "merge-conv")
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" > /dev/null 2>&1
|
||||
|
||||
export SESSION_ID="merge-session-$(date +%s)"
|
||||
local hook_output=$("$HOME/.claude/hooks/sessionEnd" 2>&1)
|
||||
|
||||
if ! echo "$hook_output" | grep -q "Existing hook running"; then
|
||||
echo -e "${RED}❌ Existing hook logic not executed${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scenario 3: Recovery (verify/repair)
|
||||
# ============================================================================
|
||||
|
||||
test_scenario_3_recovery_verify_repair() {
|
||||
echo " 1. Creating conversations and indexing..."
|
||||
local conv1=$(create_test_conversation "recovery-project" "conv-1")
|
||||
local conv2=$(create_test_conversation "recovery-project" "conv-2")
|
||||
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" > /dev/null 2>&1
|
||||
|
||||
echo " 2. Verifying summaries exist..."
|
||||
assert_summary_exists "$conv1" || return 1
|
||||
assert_summary_exists "$conv2" || return 1
|
||||
|
||||
echo " 3. Deleting summary to simulate missing file..."
|
||||
# Delete from archive (where summaries are stored)
|
||||
local archive_conv1=$(echo "$conv1" | sed "s|/.claude/projects/|/.clank/conversation-archive/|")
|
||||
rm "${archive_conv1%.jsonl}-summary.txt"
|
||||
|
||||
echo " 4. Running verify (should detect missing)..."
|
||||
local verify_output=$(cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --verify 2>&1)
|
||||
|
||||
if ! echo "$verify_output" | grep -q "Missing summaries: 1"; then
|
||||
echo -e "${RED}❌ Verify did not detect missing summary${NC}"
|
||||
echo "Verify output: $verify_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " 5. Running repair..."
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --repair > /dev/null 2>&1
|
||||
|
||||
echo " 6. Verifying summary was regenerated..."
|
||||
assert_summary_exists "$conv1" || return 1
|
||||
|
||||
echo " 7. Running verify again (should be clean)..."
|
||||
verify_output=$(cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --verify 2>&1)
|
||||
|
||||
# Verify should report no missing issues
|
||||
if ! echo "$verify_output" | grep -q "Missing summaries: 0"; then
|
||||
echo -e "${RED}❌ Verify still reports missing issues after repair${NC}"
|
||||
echo "Verify output: $verify_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scenario 4: Change Detection
|
||||
# ============================================================================
|
||||
|
||||
test_scenario_4_change_detection() {
|
||||
echo " 1. Creating and indexing conversation..."
|
||||
local conv=$(create_test_conversation "change-project" "conv-1")
|
||||
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" > /dev/null 2>&1
|
||||
|
||||
echo " 2. Verifying initial index..."
|
||||
assert_summary_exists "$conv" || return 1
|
||||
|
||||
echo " 3. Modifying conversation (adding exchange)..."
|
||||
# Wait to ensure different mtime
|
||||
sleep 1
|
||||
|
||||
# Modify the archive file (that's what verify checks)
|
||||
local archive_conv=$(echo "$conv" | sed "s|/.claude/projects/|/.clank/conversation-archive/|")
|
||||
cat >> "$archive_conv" <<'EOF'
|
||||
{"type":"user","message":{"role":"user","content":"Tell me more about TDD"},"timestamp":"2024-01-01T00:00:02Z"}
|
||||
{"type":"assistant","message":{"role":"assistant","content":"TDD has three phases: Red, Green, Refactor."},"timestamp":"2024-01-01T00:00:03Z"}
|
||||
EOF
|
||||
|
||||
echo " 4. Running verify (should detect outdated)..."
|
||||
local verify_output=$(cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --verify 2>&1)
|
||||
|
||||
if ! echo "$verify_output" | grep -q "Outdated files: 1"; then
|
||||
echo -e "${RED}❌ Verify did not detect outdated file${NC}"
|
||||
echo "Verify output: $verify_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " 5. Running repair (should re-index)..."
|
||||
cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --repair > /dev/null 2>&1
|
||||
|
||||
echo " 6. Verifying conversation is up to date..."
|
||||
verify_output=$(cd "$SCRIPT_DIR" && "$INDEX_CONVERSATIONS" --verify 2>&1)
|
||||
|
||||
if ! echo "$verify_output" | grep -q "Outdated files: 0"; then
|
||||
echo -e "${RED}❌ File still outdated after repair${NC}"
|
||||
echo "Verify output: $verify_output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " 7. Verifying new content is searchable..."
|
||||
local search_result=$(cd "$SCRIPT_DIR" && "$SCRIPT_DIR/search-conversations" "Red Green Refactor" 2>/dev/null || echo "")
|
||||
if [ -z "$search_result" ]; then
|
||||
echo -e "${RED}❌ New content not found in search${NC}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Scenario 5: Subagent Workflow (Manual Testing Required)
|
||||
# ============================================================================
|
||||
|
||||
test_scenario_5_subagent_workflow_docs() {
|
||||
echo " This scenario requires manual testing with a live subagent."
|
||||
echo " Automated checks:"
|
||||
|
||||
echo " 1. Verifying search-agent template exists..."
|
||||
local template_file="$SCRIPT_DIR/prompts/search-agent.md"
|
||||
assert_file_exists "$template_file" || return 1
|
||||
|
||||
echo " 2. Verifying template has required sections..."
|
||||
assert_file_contains "$template_file" "### Summary" || return 1
|
||||
assert_file_contains "$template_file" "### Sources" || return 1
|
||||
assert_file_contains "$template_file" "### For Follow-Up" || return 1
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW} MANUAL TESTING REQUIRED:${NC}"
|
||||
echo " To complete Scenario 5 testing:"
|
||||
echo " 1. Start a new Claude Code session"
|
||||
echo " 2. Ask about a past conversation topic"
|
||||
echo " 3. Dispatch subagent using: skills/collaboration/remembering-conversations/tool/prompts/search-agent.md"
|
||||
echo " 4. Verify synthesis is 200-1000 words"
|
||||
echo " 5. Verify all sources include: project, date, file path, status"
|
||||
echo " 6. Ask follow-up question to test iterative refinement"
|
||||
echo " 7. Verify no raw conversations loaded into main context"
|
||||
echo ""
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Run All Tests
|
||||
# ============================================================================
|
||||
|
||||
echo "=========================================="
|
||||
echo " End-to-End Deployment Testing"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Testing deployment scenarios from:"
|
||||
echo " docs/plans/2025-10-07-deployment-plan.md"
|
||||
echo ""
|
||||
|
||||
run_test "Scenario 1: Fresh Installation" test_scenario_1_fresh_install
|
||||
run_test "Scenario 2: Existing Hook (merge)" test_scenario_2_existing_hook_merge
|
||||
run_test "Scenario 3: Recovery (verify/repair)" test_scenario_3_recovery_verify_repair
|
||||
run_test "Scenario 4: Change Detection" test_scenario_4_change_detection
|
||||
run_test "Scenario 5: Subagent Workflow (docs check)" test_scenario_5_subagent_workflow_docs
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo -e " Test Results: ${GREEN}$TESTS_PASSED${NC}/${TESTS_RUN} passed"
|
||||
echo "=========================================="
|
||||
|
||||
if [ $TESTS_PASSED -eq $TESTS_RUN ]; then
|
||||
echo -e "${GREEN}✅ All tests passed!${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${RED}❌ Some tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,226 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Test suite for install-hook script
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
INSTALL_HOOK="$SCRIPT_DIR/install-hook"
|
||||
|
||||
# Test counter
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
|
||||
# Helper functions
|
||||
setup_test() {
|
||||
TEST_DIR=$(mktemp -d)
|
||||
export HOME="$TEST_DIR"
|
||||
mkdir -p "$HOME/.claude/hooks"
|
||||
}
|
||||
|
||||
cleanup_test() {
|
||||
if [ -n "$TEST_DIR" ] && [ -d "$TEST_DIR" ]; then
|
||||
rm -rf "$TEST_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_exists() {
|
||||
if [ ! -f "$1" ]; then
|
||||
echo "❌ FAIL: File does not exist: $1"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_file_not_exists() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "❌ FAIL: File should not exist: $1"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_file_executable() {
|
||||
if [ ! -x "$1" ]; then
|
||||
echo "❌ FAIL: File is not executable: $1"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
if ! grep -q "$2" "$1"; then
|
||||
echo "❌ FAIL: File $1 does not contain: $2"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
local test_func="$2"
|
||||
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
echo "Running test: $test_name"
|
||||
|
||||
setup_test
|
||||
|
||||
if $test_func; then
|
||||
echo "✓ PASS: $test_name"
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
else
|
||||
echo "❌ FAIL: $test_name"
|
||||
fi
|
||||
|
||||
cleanup_test
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Test 1: Fresh installation with no existing hook
|
||||
test_fresh_installation() {
|
||||
# Run installer with no input (non-interactive fresh install)
|
||||
if [ ! -x "$INSTALL_HOOK" ]; then
|
||||
echo "❌ install-hook script not found or not executable"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Should fail because script doesn't exist yet
|
||||
"$INSTALL_HOOK" 2>&1 || true
|
||||
|
||||
# Verify hook was created
|
||||
assert_file_exists "$HOME/.claude/hooks/sessionEnd" || return 1
|
||||
|
||||
# Verify hook is executable
|
||||
assert_file_executable "$HOME/.claude/hooks/sessionEnd" || return 1
|
||||
|
||||
# Verify hook contains indexer reference
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "remembering-conversations.*index-conversations" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 2: Merge with existing hook (user chooses merge)
|
||||
test_merge_with_existing_hook() {
|
||||
# Create existing hook
|
||||
cat > "$HOME/.claude/hooks/sessionEnd" <<'EOF'
|
||||
#!/bin/bash
|
||||
# Existing hook content
|
||||
echo "Existing hook running"
|
||||
EOF
|
||||
chmod +x "$HOME/.claude/hooks/sessionEnd"
|
||||
|
||||
# Run installer and choose merge
|
||||
echo "m" | "$INSTALL_HOOK" 2>&1 || true
|
||||
|
||||
# Verify backup was created
|
||||
local backup_count=$(ls -1 "$HOME/.claude/hooks/sessionEnd.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$backup_count" -lt 1 ]; then
|
||||
echo "❌ No backup created"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify original content is preserved
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "Existing hook running" || return 1
|
||||
|
||||
# Verify indexer was appended
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "remembering-conversations.*index-conversations" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 3: Replace with existing hook (user chooses replace)
|
||||
test_replace_with_existing_hook() {
|
||||
# Create existing hook
|
||||
cat > "$HOME/.claude/hooks/sessionEnd" <<'EOF'
|
||||
#!/bin/bash
|
||||
# Old hook to be replaced
|
||||
echo "Old hook"
|
||||
EOF
|
||||
chmod +x "$HOME/.claude/hooks/sessionEnd"
|
||||
|
||||
# Run installer and choose replace
|
||||
echo "r" | "$INSTALL_HOOK" 2>&1 || true
|
||||
|
||||
# Verify backup was created
|
||||
local backup_count=$(ls -1 "$HOME/.claude/hooks/sessionEnd.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$backup_count" -lt 1 ]; then
|
||||
echo "❌ No backup created"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify old content is gone
|
||||
if grep -q "Old hook" "$HOME/.claude/hooks/sessionEnd"; then
|
||||
echo "❌ Old hook content still present"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify new hook contains indexer
|
||||
assert_file_contains "$HOME/.claude/hooks/sessionEnd" "remembering-conversations.*index-conversations" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 4: Detection of already-installed indexer (idempotent)
|
||||
test_already_installed_detection() {
|
||||
# Create hook with indexer already installed
|
||||
cat > "$HOME/.claude/hooks/sessionEnd" <<'EOF'
|
||||
#!/bin/bash
|
||||
# Auto-index conversations (remembering-conversations skill)
|
||||
INDEXER="$HOME/.claude/skills/collaboration/remembering-conversations/tool/index-conversations"
|
||||
if [ -n "$SESSION_ID" ] && [ -x "$INDEXER" ]; then
|
||||
"$INDEXER" --session "$SESSION_ID" > /dev/null 2>&1 &
|
||||
fi
|
||||
EOF
|
||||
chmod +x "$HOME/.claude/hooks/sessionEnd"
|
||||
|
||||
# Run installer - should detect and exit
|
||||
local output=$("$INSTALL_HOOK" 2>&1 || true)
|
||||
|
||||
# Verify it detected existing installation
|
||||
if ! echo "$output" | grep -q "already installed"; then
|
||||
echo "❌ Did not detect existing installation"
|
||||
echo "Output: $output"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Verify no backup was created (since nothing changed)
|
||||
local backup_count=$(ls -1 "$HOME/.claude/hooks/sessionEnd.backup."* 2>/dev/null | wc -l)
|
||||
if [ "$backup_count" -gt 0 ]; then
|
||||
echo "❌ Backup created when it shouldn't have been"
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Test 5: Executable permissions are set
|
||||
test_executable_permissions() {
|
||||
# Run installer
|
||||
"$INSTALL_HOOK" 2>&1 || true
|
||||
|
||||
# Verify hook is executable
|
||||
assert_file_executable "$HOME/.claude/hooks/sessionEnd" || return 1
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Run all tests
|
||||
echo "=========================================="
|
||||
echo "Testing install-hook script"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
run_test "Fresh installation with no existing hook" test_fresh_installation
|
||||
run_test "Merge with existing hook" test_merge_with_existing_hook
|
||||
run_test "Replace with existing hook" test_replace_with_existing_hook
|
||||
run_test "Detection of already-installed indexer" test_already_installed_detection
|
||||
run_test "Executable permissions are set" test_executable_permissions
|
||||
|
||||
echo "=========================================="
|
||||
echo "Test Results: $TESTS_PASSED/$TESTS_RUN passed"
|
||||
echo "=========================================="
|
||||
|
||||
if [ $TESTS_PASSED -eq $TESTS_RUN ]; then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Condition-Based Waiting
|
||||
description: Replace arbitrary timeouts with condition polling for reliable async tests
|
||||
when_to_use: When tests use setTimeout/sleep and are flaky or timing-dependent
|
||||
version: 1.0.0
|
||||
languages: all
|
||||
name: condition-based-waiting
|
||||
description: Use when tests have race conditions, timing dependencies, or inconsistent pass/fail behavior - replaces arbitrary timeouts with condition polling to wait for actual state changes, eliminating flaky tests from timing guesses
|
||||
---
|
||||
|
||||
# Condition-Based Waiting
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Defense-in-Depth Validation
|
||||
description: Validate at every layer data passes through to make bugs impossible
|
||||
when_to_use: Found a bug where invalid data causes problems deep in call stack
|
||||
version: 1.0.0
|
||||
languages: all
|
||||
name: defense-in-depth
|
||||
description: Use when invalid data causes failures deep in execution, requiring validation at multiple system layers - validates at every layer data passes through to make bugs structurally impossible
|
||||
---
|
||||
|
||||
# Defense-in-Depth Validation
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
name: Dispatching Parallel Agents
|
||||
description: Use multiple Claude agents to investigate and fix independent problems concurrently
|
||||
when_to_use: Multiple unrelated failures that can be investigated independently
|
||||
version: 1.0.0
|
||||
languages: all
|
||||
context: AI-assisted development (Claude Code or similar)
|
||||
name: dispatching-parallel-agents
|
||||
description: Use when facing 3+ independent failures that can be investigated without shared state or dependencies - dispatches multiple Claude agents to investigate and fix independent problems concurrently
|
||||
---
|
||||
|
||||
# Dispatching Parallel Agents
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Executing Plans
|
||||
description: Execute detailed plans in batches with review checkpoints
|
||||
when_to_use: When have a complete implementation plan to execute. When implementing in separate session from planning. When your human partner points you to a plan file to implement.
|
||||
version: 2.0.0
|
||||
name: executing-plans
|
||||
description: Use when partner provides a complete implementation plan to execute in controlled batches with review checkpoints - loads plan, reviews critically, executes tasks in batches, reports for review between batches
|
||||
---
|
||||
|
||||
# Executing Plans
|
||||
@@ -13,7 +11,7 @@ Load plan, review critically, execute tasks in batches, report for review betwee
|
||||
|
||||
**Core principle:** Batch execution with checkpoints for architect review.
|
||||
|
||||
**Announce at start:** "I'm using the Executing Plans skill to implement this plan."
|
||||
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
||||
|
||||
## The Process
|
||||
|
||||
@@ -47,13 +45,32 @@ Based on feedback:
|
||||
### Step 5: Complete Development
|
||||
|
||||
After all tasks complete and verified:
|
||||
- Announce: "I'm using the Finishing a Development Branch skill to complete this work."
|
||||
- Switch to skills/collaboration/finishing-a-development-branch
|
||||
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch
|
||||
- Follow that skill to verify tests, present options, execute choice
|
||||
|
||||
## When to Stop and Ask for Help
|
||||
|
||||
**STOP executing immediately when:**
|
||||
- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear)
|
||||
- Plan has critical gaps preventing starting
|
||||
- You don't understand an instruction
|
||||
- Verification fails repeatedly
|
||||
|
||||
**Ask for clarification rather than guessing.**
|
||||
|
||||
## When to Revisit Earlier Steps
|
||||
|
||||
**Return to Review (Step 1) when:**
|
||||
- Partner updates the plan based on your feedback
|
||||
- Fundamental approach needs rethinking
|
||||
|
||||
**Don't force through blockers** - stop and ask.
|
||||
|
||||
## Remember
|
||||
- Review plan critically first
|
||||
- Follow plan steps exactly
|
||||
- Don't skip verifications
|
||||
- Reference skills when plan says to
|
||||
- Between batches: just report and wait
|
||||
- Stop when blocked, don't guess
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Finishing a Development Branch
|
||||
description: Complete feature development with structured options for merge, PR, or cleanup
|
||||
when_to_use: After completing implementation. When all tests passing. At end of executing-plans or subagent-driven-development. When feature work is done.
|
||||
version: 1.0.0
|
||||
name: finishing-a-development-branch
|
||||
description: Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
|
||||
---
|
||||
|
||||
# Finishing a Development Branch
|
||||
@@ -13,7 +11,7 @@ Guide completion of development work by presenting clear options and handling ch
|
||||
|
||||
**Core principle:** Verify tests → Present options → Execute choice → Clean up.
|
||||
|
||||
**Announce at start:** "I'm using the Finishing a Development Branch skill to complete this work."
|
||||
**Announce at start:** "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
|
||||
## The Process
|
||||
|
||||
@@ -195,8 +193,8 @@ git worktree remove <worktree-path>
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- skills/collaboration/subagent-driven-development (Step 7)
|
||||
- skills/collaboration/executing-plans (Step 5)
|
||||
- **subagent-driven-development** (Step 7) - After all tasks complete
|
||||
- **executing-plans** (Step 5) - After all batches complete
|
||||
|
||||
**Pairs with:**
|
||||
- skills/collaboration/using-git-worktrees (created the worktree)
|
||||
- **using-git-worktrees** - Cleans up worktree created by that skill
|
||||
@@ -1,205 +0,0 @@
|
||||
---
|
||||
name: Getting Started with Skills
|
||||
description: Skills wiki intro - mandatory workflows, search tool, brainstorming triggers, personal skills
|
||||
when_to_use: Read this FIRST at start of each conversation when skills are active
|
||||
version: 3.0.0
|
||||
---
|
||||
|
||||
# Getting Started with Skills
|
||||
|
||||
Two skill libraries work together:
|
||||
- **Core skills** at `${CLAUDE_PLUGIN_ROOT}/skills/` (from plugin)
|
||||
- **Personal skills** at `~/.config/superpowers/skills/` (yours to create and share)
|
||||
|
||||
Personal skills shadow core skills when names match.
|
||||
|
||||
## Just Read This Guide?
|
||||
|
||||
**RIGHT NOW**: Find the plugin location and list skills:
|
||||
```bash
|
||||
# Find plugin location
|
||||
find ~/.claude/plugins/cache -name "superpowers" -type d 2>/dev/null | grep -v ".git" | head -1
|
||||
|
||||
# Then run (replace PATH with the path found above):
|
||||
PATH/scripts/find-skills
|
||||
```
|
||||
|
||||
Or just use the full path directly:
|
||||
```bash
|
||||
~/.claude/plugins/cache/superpowers/scripts/find-skills
|
||||
```
|
||||
|
||||
**THEN**: Follow the workflows below based on what your partner is asking for.
|
||||
|
||||
## How to Reference Skills
|
||||
|
||||
**DO NOT use @ links** - they force-load entire files, burning 200k+ context instantly.
|
||||
|
||||
**INSTEAD, use skill path references:**
|
||||
- Format: `skills/category/skill-name` (no @ prefix, no /SKILL.md suffix)
|
||||
- Example: `skills/collaboration/brainstorming` or `skills/testing/test-driven-development`
|
||||
- Load with Read tool only when needed
|
||||
|
||||
**When you see skill references in documentation:**
|
||||
- `skills/path/name` → Check personal first (`~/.config/superpowers/skills/path/name/SKILL.md`)
|
||||
- If not found, check core (`${CLAUDE_PLUGIN_ROOT}/skills/path/name/SKILL.md`)
|
||||
- Load supporting files only when implementing
|
||||
|
||||
## Mandatory Workflow 1: Brainstorming Before Coding
|
||||
|
||||
**When your human partner wants to start a project, no matter how big or small:**
|
||||
|
||||
**YOU MUST immediately read:** skills/collaboration/brainstorming
|
||||
|
||||
**Don't:**
|
||||
- Jump straight to code
|
||||
- Wait for /brainstorm command
|
||||
- Skip brainstorming because you "understand the idea"
|
||||
|
||||
**Why:** Just writing code is almost never the right first step. We always understand requirements and plan first.
|
||||
|
||||
## Mandatory Workflow 2: Before ANY Task
|
||||
|
||||
**1. Find skills** (shows all, or filter by pattern):
|
||||
|
||||
Use the plugin cache path (default: `~/.claude/plugins/cache/superpowers`):
|
||||
```bash
|
||||
~/.claude/plugins/cache/superpowers/scripts/find-skills # Show all
|
||||
~/.claude/plugins/cache/superpowers/scripts/find-skills PATTERN # Filter by pattern
|
||||
```
|
||||
|
||||
**2. Search conversations:**
|
||||
Dispatch subagent (see Workflow 3) to check for relevant past work.
|
||||
|
||||
**If skills found:**
|
||||
1. READ the skill - check personal first (`~/.config/superpowers/skills/path/SKILL.md`), then core (`~/.claude/plugins/cache/superpowers/skills/path/SKILL.md`)
|
||||
2. ANNOUNCE usage: "I'm using the [Skill Name] skill"
|
||||
3. FOLLOW the skill (many are rigid requirements)
|
||||
|
||||
**"This doesn't count as a task" is rationalization.** Skills/conversations exist and you didn't search for them or didn't use them = failed task.
|
||||
|
||||
## Mandatory Workflow 3: Historical Context Search
|
||||
|
||||
**When:** Your human partner mentions past work, issue feels familiar, starting task in familiar domain, stuck/blocked, before reinventing
|
||||
|
||||
**When NOT:** Info in current convo, codebase state questions, first encounter, partner wants fresh thinking
|
||||
|
||||
**How (use subagent for 50-100x context savings):**
|
||||
1. Dispatch subagent with template: `${CLAUDE_PLUGIN_ROOT}/skills/collaboration/remembering-conversations/tool/prompts/search-agent.md`
|
||||
2. Receive synthesis (200-1000 words) + source pointers
|
||||
3. Apply insights (never load raw .jsonl files)
|
||||
|
||||
**Example:**
|
||||
```
|
||||
Partner: "How did we handle auth errors in React Router?"
|
||||
You: Searching past conversations...
|
||||
[Dispatch subagent → 350-word synthesis]
|
||||
[Apply without loading 50k tokens]
|
||||
```
|
||||
|
||||
**Red flags:** Reading .jsonl files directly, pasting excerpts, asking "which conversation?", browsing archives
|
||||
|
||||
**Pattern:** Search → Subagent synthesizes → Apply. Fast, focused, context-efficient.
|
||||
|
||||
## Announcing Skill Usage
|
||||
|
||||
**Every time you start using a skill, announce it:**
|
||||
|
||||
"I'm using the [Skill Name] skill to [what you're doing]."
|
||||
|
||||
**Examples:**
|
||||
- "I'm using the Brainstorming skill to refine your idea into a design."
|
||||
- "I'm using the Test-Driven Development skill to implement this feature."
|
||||
- "I'm using the Systematic Debugging skill to find the root cause."
|
||||
- "I'm using the Refactoring Safely skill to extract these methods."
|
||||
|
||||
**Why:** Transparency helps your human partner understand your process and catch errors early.
|
||||
|
||||
## Skills with Checklists
|
||||
|
||||
**If a skill contains a checklist, you MUST create TodoWrite todos for EACH checklist item.**
|
||||
|
||||
**Don't:**
|
||||
- Work through checklist mentally
|
||||
- Skip creating todos "to save time"
|
||||
- Batch multiple items into one todo
|
||||
- Mark complete without doing them
|
||||
|
||||
**Why:** Checklists without TodoWrite tracking = steps get skipped. Every time.
|
||||
|
||||
**Examples:** TDD (write test, watch fail, implement, verify), Systematic Debugging (4 phases), Writing Skills (RED-GREEN-REFACTOR)
|
||||
|
||||
## How to Read a Skill
|
||||
|
||||
1. **Frontmatter** - `when_to_use` match your situation?
|
||||
2. **Overview** - Core principle relevant?
|
||||
3. **Quick Reference** - Scan for your pattern
|
||||
4. **Implementation** - Full details
|
||||
5. **Supporting files** - Load only when implementing
|
||||
|
||||
**Many skills contain rigid rules (TDD, debugging, verification).** Follow them exactly. Don't adapt away the discipline.
|
||||
|
||||
**Some skills are flexible patterns (architecture, naming).** Adapt core principles to your context.
|
||||
|
||||
The skill itself tells you which type it is.
|
||||
|
||||
## Referencing Skills in Documentation
|
||||
|
||||
**When writing documentation that references other skills:**
|
||||
|
||||
Use path format without `@` prefix or `/SKILL.md` suffix:
|
||||
- ✅ Good: `skills/testing/test-driven-development`
|
||||
- ✅ Good: `skills/debugging/systematic-debugging`
|
||||
- ❌ Bad: `@skills/testing/test-driven-development/SKILL.md` (force-loads, burns context)
|
||||
|
||||
**Why no @ links:** `@` syntax force-loads files immediately, consuming 200k+ context before you need them.
|
||||
|
||||
**To read a skill reference:** Use Read tool on `${CLAUDE_PLUGIN_ROOT}/skills/category/skill-name/SKILL.md`
|
||||
|
||||
## Instructions ≠ Permission to Skip Workflows
|
||||
|
||||
Your human partner's specific instructions describe WHAT to do, not HOW.
|
||||
|
||||
"Add X", "Fix Y" = the goal, NOT permission to skip brainstorming, TDD, or RED-GREEN-REFACTOR.
|
||||
|
||||
**Red flags:** "Instruction was specific" • "Seems simple" • "Workflow is overkill"
|
||||
|
||||
## Writing and Sharing Skills
|
||||
|
||||
**All personal skills are written to `~/.config/superpowers/skills/`**
|
||||
|
||||
**Before writing ANY skill:**
|
||||
|
||||
1. **STOP** - Even if your human partner gave specific instructions
|
||||
2. **Read skills/meta/writing-skills**
|
||||
3. **Follow the TDD process** - No skill without failing test first
|
||||
|
||||
**Your human partner's specific instruction is NOT implicit permission to skip the process.**
|
||||
|
||||
**Red flags:**
|
||||
- "Just a small addition"
|
||||
- "Instruction was specific, so I can proceed"
|
||||
|
||||
**All of these mean: STOP and follow writing-skills process.**
|
||||
|
||||
**Want to share a skill with everyone?**
|
||||
- See skills/meta/sharing-skills for how to contribute to core superpowers
|
||||
|
||||
**Want a skill that doesn't exist?**
|
||||
- Write it yourself (see skills/meta/writing-skills) and share it!
|
||||
- Or open an issue at https://github.com/obra/superpowers/issues
|
||||
|
||||
## Summary
|
||||
|
||||
**Starting conversation?** You just read this. Good.
|
||||
|
||||
**Starting any task?** Run find-skills first, announce usage, follow what you find.
|
||||
|
||||
**Skill has checklist?** TodoWrite for every item.
|
||||
|
||||
**Skills are mandatory when they exist, not optional.**
|
||||
|
||||
|
||||
## Last thing
|
||||
|
||||
In the first response after reading this guide, you MUST announce to the user that you have read the getting started guide
|
||||
@@ -1,370 +0,0 @@
|
||||
---
|
||||
name: Gardening Skills Wiki
|
||||
description: Maintain skills wiki health - check links, naming, cross-references, and coverage
|
||||
when_to_use: When adding/removing skills. When reorganizing categories. When links feel broken. Periodically (weekly/monthly) to maintain wiki health. When INDEX files don't match directory structure. When cross-references might be stale.
|
||||
version: 1.0.0
|
||||
languages: bash
|
||||
---
|
||||
|
||||
# Gardening Skills Wiki
|
||||
|
||||
## Overview
|
||||
|
||||
The skills wiki needs regular maintenance to stay healthy: links break, skills get orphaned, naming drifts, INDEX files fall out of sync.
|
||||
|
||||
**Core principle:** Automate health checks to maintain wiki quality without burning tokens on manual inspection.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Run gardening after:**
|
||||
- Adding new skills
|
||||
- Removing or renaming skills
|
||||
- Reorganizing categories
|
||||
- Updating cross-references
|
||||
- Suspicious that links are broken
|
||||
|
||||
**Periodic maintenance:**
|
||||
- Weekly during active development
|
||||
- Monthly during stable periods
|
||||
|
||||
## Quick Health Check
|
||||
|
||||
```bash
|
||||
# Run all checks
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh
|
||||
|
||||
# Or run specific checks
|
||||
~/.claude/skills/meta/gardening-skills-wiki/check-links.sh
|
||||
~/.claude/skills/meta/gardening-skills-wiki/check-naming.sh
|
||||
~/.claude/skills/meta/gardening-skills-wiki/check-index-coverage.sh
|
||||
|
||||
# Analyze search gaps (what skills are missing)
|
||||
~/.claude/skills/meta/gardening-skills-wiki/analyze-search-gaps.sh
|
||||
```
|
||||
|
||||
The master script runs all checks and provides a health report.
|
||||
|
||||
## What Gets Checked
|
||||
|
||||
### 1. Link Validation (`check-links.sh`)
|
||||
|
||||
**Checks:**
|
||||
- Backtick-wrapped `@` links - backticks disable resolution
|
||||
- Relative paths like skills/ or skills/gardening-skills-wiki/~/ - should use skills/ absolute paths
|
||||
- All `skills/` references resolve to existing files
|
||||
- Skills referenced in INDEX files exist
|
||||
- Orphaned skills (not in any INDEX)
|
||||
|
||||
**Fixes:**
|
||||
- Remove backticks from @ references
|
||||
- Convert skills/ and skills/gardening-skills-wiki/~/ relative paths to skills/ absolute paths
|
||||
- Update broken skills/ references to correct paths
|
||||
- Add orphaned skills to their category INDEX
|
||||
- Remove references to deleted skills
|
||||
|
||||
### 2. Naming Consistency (`check-naming.sh`)
|
||||
|
||||
**Checks:**
|
||||
- Directory names are kebab-case
|
||||
- No uppercase or underscores in directory names
|
||||
- Frontmatter fields present (name, description, when_to_use, version, type)
|
||||
- Skill names use active voice (not "How to...")
|
||||
- Empty directories
|
||||
|
||||
**Fixes:**
|
||||
- Rename directories to kebab-case
|
||||
- Add missing frontmatter fields
|
||||
- Remove empty directories
|
||||
- Rephrase names to active voice
|
||||
|
||||
### 3. INDEX Coverage (`check-index-coverage.sh`)
|
||||
|
||||
**Checks:**
|
||||
- All skills listed in their category INDEX
|
||||
- All category INDEX files linked from main INDEX
|
||||
- Skills have descriptions in INDEX entries
|
||||
|
||||
**Fixes:**
|
||||
- Add missing skills to INDEX files
|
||||
- Add category links to main INDEX
|
||||
- Add descriptions for INDEX entries
|
||||
|
||||
## Common Issues and Fixes
|
||||
|
||||
### Broken Links
|
||||
|
||||
```
|
||||
❌ BROKEN: skills/debugging/root-cause-tracing
|
||||
Target: /path/to/skills/debugging/root-cause-tracing/SKILL.md
|
||||
```
|
||||
|
||||
**Fix:** Update the reference path - skill might have moved or been renamed.
|
||||
|
||||
### Orphaned Skills
|
||||
|
||||
```
|
||||
⚠️ ORPHANED: test-invariants/SKILL.md not in testing/INDEX.md
|
||||
```
|
||||
|
||||
**Fix:** Add to the category INDEX:
|
||||
|
||||
```markdown
|
||||
- skills/gardening-skills-wiki/test-invariants - Description of skill
|
||||
```
|
||||
|
||||
### Backtick-Wrapped Links
|
||||
|
||||
```
|
||||
❌ BACKTICKED: skills/testing/condition-based-waiting on line 31
|
||||
File: getting-started/SKILL.md
|
||||
Fix: Remove backticks - use bare @ reference
|
||||
```
|
||||
|
||||
**Fix:** Remove backticks:
|
||||
|
||||
```markdown
|
||||
# ❌ Bad - backticks disable link resolution
|
||||
`skills/testing/condition-based-waiting`
|
||||
|
||||
# ✅ Good - bare @ reference
|
||||
skills/testing/condition-based-waiting
|
||||
```
|
||||
|
||||
### Relative Path Links
|
||||
|
||||
```
|
||||
❌ RELATIVE: skills/testing in coding/SKILL.md
|
||||
Fix: Use skills/ absolute path instead
|
||||
```
|
||||
|
||||
**Fix:** Convert to absolute path:
|
||||
|
||||
```markdown
|
||||
# ❌ Bad - relative paths are brittle
|
||||
skills/testing/condition-based-waiting
|
||||
|
||||
# ✅ Good - absolute skills/ path
|
||||
skills/testing/condition-based-waiting
|
||||
```
|
||||
|
||||
### Naming Issues
|
||||
|
||||
```
|
||||
⚠️ Mixed case: TestingPatterns (should be kebab-case)
|
||||
```
|
||||
|
||||
**Fix:** Rename directory:
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/testing
|
||||
mv TestingPatterns testing-patterns
|
||||
# Update all references to old name
|
||||
```
|
||||
|
||||
### Missing from INDEX
|
||||
|
||||
```
|
||||
❌ NOT INDEXED: condition-based-waiting/SKILL.md
|
||||
```
|
||||
|
||||
**Fix:** Add to `testing/INDEX.md`:
|
||||
|
||||
```markdown
|
||||
## Available Skills
|
||||
|
||||
- skills/gardening-skills-wiki/condition-based-waiting - Replace timeouts with condition polling
|
||||
```
|
||||
|
||||
### Empty Directories
|
||||
|
||||
```
|
||||
⚠️ EMPTY: event-based-testing
|
||||
```
|
||||
|
||||
**Fix:** Remove if no longer needed:
|
||||
|
||||
```bash
|
||||
rm -rf ~/.claude/skills/event-based-testing
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Directory Names
|
||||
|
||||
- **Format:** kebab-case (lowercase with hyphens)
|
||||
- **Process skills:** Use gerunds when appropriate (`creating-skills`, `testing-skills`)
|
||||
- **Pattern skills:** Use core concept (`flatten-with-flags`, `test-invariants`)
|
||||
- **Avoid:** Mixed case, underscores, passive voice starters ("how-to-")
|
||||
|
||||
### Frontmatter Requirements
|
||||
|
||||
**Required fields:**
|
||||
- `name`: Human-readable name
|
||||
- `description`: One-line summary
|
||||
- `when_to_use`: Symptoms and situations (CSO-critical)
|
||||
- `version`: Semantic version
|
||||
|
||||
**Optional fields:**
|
||||
- `languages`: Applicable languages
|
||||
- `dependencies`: Required tools
|
||||
- `context`: Special context (e.g., "AI-assisted development")
|
||||
|
||||
## Automation Workflow
|
||||
|
||||
### After Adding New Skill
|
||||
|
||||
```bash
|
||||
# 1. Create skill
|
||||
mkdir -p ~/.claude/skills/category/new-skill
|
||||
vim ~/.claude/skills/category/new-skill/SKILL.md
|
||||
|
||||
# 2. Add to category INDEX
|
||||
vim ~/.claude/skills/category/INDEX.md
|
||||
|
||||
# 3. Run health check
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh
|
||||
|
||||
# 4. Fix any issues reported
|
||||
```
|
||||
|
||||
### After Reorganizing
|
||||
|
||||
```bash
|
||||
# 1. Move/rename skills
|
||||
mv ~/.claude/skills/old-category/skill ~/.claude/skills/new-category/
|
||||
|
||||
# 2. Update all references (grep for old paths)
|
||||
grep -r "skills/gardening-skills-wiki/old-category/skill" ~/.claude/skills/
|
||||
|
||||
# 3. Run health check
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh
|
||||
|
||||
# 4. Fix broken links
|
||||
```
|
||||
|
||||
### Periodic Maintenance
|
||||
|
||||
```bash
|
||||
# Monthly: Run full health check
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh
|
||||
|
||||
# Review and fix:
|
||||
# - ❌ errors (broken links, missing skills)
|
||||
# - ⚠️ warnings (naming, empty dirs)
|
||||
```
|
||||
|
||||
## The Scripts
|
||||
|
||||
### `garden.sh` (Master)
|
||||
|
||||
Runs all health checks and provides comprehensive report.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh [skills_dir]
|
||||
```
|
||||
|
||||
### `check-links.sh`
|
||||
|
||||
Validates all `@` references and cross-links.
|
||||
|
||||
**Checks:**
|
||||
- Backtick-wrapped `@` links (disables resolution)
|
||||
- Relative paths (`skills/` or `skills/gardening-skills-wiki/~/`) - should be `skills/`
|
||||
- `@` reference resolution to existing files
|
||||
- Skills in INDEX files exist
|
||||
- Orphaned skills detection
|
||||
|
||||
### `check-naming.sh`
|
||||
|
||||
Validates naming conventions and frontmatter.
|
||||
|
||||
**Checks:**
|
||||
- Directory name format
|
||||
- Frontmatter completeness
|
||||
- Empty directories
|
||||
|
||||
### `check-index-coverage.sh`
|
||||
|
||||
Validates INDEX completeness.
|
||||
|
||||
**Checks:**
|
||||
- Skills listed in category INDEX
|
||||
- Categories linked in main INDEX
|
||||
- Descriptions present
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Issue | Script | Fix |
|
||||
|-------|--------|-----|
|
||||
| Backtick-wrapped links | `check-links.sh` | Remove backticks from `@` refs |
|
||||
| Relative paths | `check-links.sh` | Convert to `skills/` absolute |
|
||||
| Broken links | `check-links.sh` | Update `@` references |
|
||||
| Orphaned skills | `check-links.sh` | Add to INDEX |
|
||||
| Naming issues | `check-naming.sh` | Rename directories |
|
||||
| Empty dirs | `check-naming.sh` | Remove with `rm -rf` |
|
||||
| Missing from INDEX | `check-index-coverage.sh` | Add to INDEX.md |
|
||||
| No description | `check-index-coverage.sh` | Add to INDEX entry |
|
||||
|
||||
## Output Symbols
|
||||
|
||||
- ✅ **Pass** - Item is correct
|
||||
- ❌ **Error** - Must fix (broken link, missing skill)
|
||||
- ⚠️ **Warning** - Should fix (naming, empty dir)
|
||||
- ℹ️ **Info** - Informational (no action needed)
|
||||
|
||||
## Integration with Workflow
|
||||
|
||||
**Before committing skill changes:**
|
||||
|
||||
```bash
|
||||
~/.claude/skills/meta/gardening-skills-wiki/garden.sh
|
||||
# Fix all ❌ errors
|
||||
# Consider fixing ⚠️ warnings
|
||||
git add .
|
||||
git commit -m "Add/update skills"
|
||||
```
|
||||
|
||||
**When links feel suspicious:**
|
||||
|
||||
```bash
|
||||
~/.claude/skills/meta/gardening-skills-wiki/check-links.sh
|
||||
```
|
||||
|
||||
**When INDEX seems incomplete:**
|
||||
|
||||
```bash
|
||||
~/.claude/skills/meta/gardening-skills-wiki/check-index-coverage.sh
|
||||
```
|
||||
|
||||
## Common Rationalizations
|
||||
|
||||
| Excuse | Reality |
|
||||
|--------|---------|
|
||||
| "Will check links manually" | Automated check is faster and more thorough |
|
||||
| "INDEX probably fine" | Orphaned skills happen - always verify |
|
||||
| "Naming doesn't matter" | Consistency aids discovery and maintenance |
|
||||
| "Empty dir harmless" | Clutter confuses future maintainers |
|
||||
| "Can skip periodic checks" | Issues compound - regular maintenance prevents big cleanups |
|
||||
|
||||
## Real-World Impact
|
||||
|
||||
**Without gardening:**
|
||||
- Broken links discovered during urgent tasks
|
||||
- Orphaned skills never found
|
||||
- Naming drifts over time
|
||||
- INDEX files fall out of sync
|
||||
|
||||
**With gardening:**
|
||||
- 30-second health check catches issues early
|
||||
- Automated validation prevents manual inspection
|
||||
- Consistent structure aids discovery
|
||||
- Wiki stays maintainable
|
||||
|
||||
## The Bottom Line
|
||||
|
||||
**Don't manually inspect - automate the checks.**
|
||||
|
||||
Run `garden.sh` after changes and periodically. Fix ❌ errors immediately, address ⚠️ warnings when convenient.
|
||||
|
||||
Maintained wiki = findable skills = reusable knowledge.
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Analyze failed skills-search queries to identify missing skills
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SKILLS_DIR="${HOME}/.claude/skills"
|
||||
LOG_FILE="${SKILLS_DIR}/.search-log.jsonl"
|
||||
|
||||
if [[ ! -f "$LOG_FILE" ]]; then
|
||||
echo "No search log found at $LOG_FILE"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Skills Search Gap Analysis"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# Count total searches
|
||||
total=$(wc -l < "$LOG_FILE")
|
||||
echo "Total searches: $total"
|
||||
echo ""
|
||||
|
||||
# Extract and count unique queries
|
||||
echo "Most common searches:"
|
||||
jq -r '.query' "$LOG_FILE" 2>/dev/null | sort | uniq -c | sort -rn | head -20
|
||||
|
||||
echo ""
|
||||
echo "Recent searches (last 10):"
|
||||
tail -10 "$LOG_FILE" | jq -r '"\(.timestamp) - \(.query)"' 2>/dev/null
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "High-frequency searches indicate missing skills."
|
||||
echo "Review patterns and create skills as needed."
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check that all skills are properly listed in INDEX files
|
||||
|
||||
SKILLS_DIR="${1:-$HOME/Documents/GitHub/dotfiles/.claude/skills}"
|
||||
|
||||
echo "## INDEX Coverage"
|
||||
# For each category with an INDEX
|
||||
for category_dir in "$SKILLS_DIR"/*/; do
|
||||
category=$(basename "$category_dir")
|
||||
|
||||
# Skip if not a directory
|
||||
[[ ! -d "$category_dir" ]] && continue
|
||||
|
||||
index_file="$category_dir/INDEX.md"
|
||||
|
||||
# Skip if no INDEX (meta directories might not have one)
|
||||
[[ ! -f "$index_file" ]] && continue
|
||||
|
||||
# Find all SKILL.md files in this category
|
||||
skill_count=0
|
||||
indexed_count=0
|
||||
missing_count=0
|
||||
|
||||
while IFS= read -r skill_file; do
|
||||
skill_count=$((skill_count + 1))
|
||||
skill_name=$(basename $(dirname "$skill_file"))
|
||||
|
||||
# Check if skill is referenced in INDEX
|
||||
if grep -q "@$skill_name/SKILL.md" "$index_file"; then
|
||||
indexed_count=$((indexed_count + 1))
|
||||
else
|
||||
echo " ❌ NOT INDEXED: $skill_name/SKILL.md"
|
||||
missing_count=$((missing_count + 1))
|
||||
fi
|
||||
done < <(find "$category_dir" -mindepth 2 -type f -name "SKILL.md")
|
||||
|
||||
if [ $skill_count -gt 0 ] && [ $missing_count -eq 0 ]; then
|
||||
echo " ✅ $category: all $skill_count skills indexed"
|
||||
elif [ $missing_count -gt 0 ]; then
|
||||
echo " ⚠️ $category: $missing_count/$skill_count skills missing"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
# Verify INDEX entries have descriptions
|
||||
find "$SKILLS_DIR" -type f -name "INDEX.md" | while read -r index_file; do
|
||||
category=$(basename $(dirname "$index_file"))
|
||||
|
||||
# Extract skill references
|
||||
grep -o '@[a-zA-Z0-9-]*/SKILL\.md' "$index_file" | while read -r ref; do
|
||||
skill_name=${ref#@}
|
||||
skill_name=${skill_name%/SKILL.md}
|
||||
|
||||
# Get the line with the reference
|
||||
line_num=$(grep -n "$ref" "$index_file" | cut -d: -f1)
|
||||
|
||||
# Check if there's a description on the same line or next line
|
||||
description=$(sed -n "${line_num}p" "$index_file" | sed "s|.*$ref *- *||")
|
||||
|
||||
if [[ -z "$description" || "$description" == *"$ref"* ]]; then
|
||||
# No description on same line, check next line
|
||||
next_line=$((line_num + 1))
|
||||
description=$(sed -n "${next_line}p" "$index_file")
|
||||
|
||||
if [[ -z "$description" ]]; then
|
||||
echo " ⚠️ NO DESCRIPTION: $category/INDEX.md reference to $skill_name"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
done
|
||||
@@ -1,119 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check for @ links (force-load context) and validate skill path references
|
||||
|
||||
SKILLS_DIR="${1:-$HOME/Documents/GitHub/dotfiles/.claude/skills}"
|
||||
|
||||
echo "## Links & References"
|
||||
broken_refs=0
|
||||
backticked_refs=0
|
||||
relative_refs=0
|
||||
at_links=0
|
||||
|
||||
while IFS= read -r file; do
|
||||
# Extract @ references - must start line, be after space/paren/dash, or be standalone
|
||||
# Exclude: emails, decorators, code examples with @staticmethod/@example
|
||||
|
||||
# First, check for backtick-wrapped @ links
|
||||
grep -nE '`[^`]*@[a-zA-Z0-9._~/-]+\.(md|sh|ts|js|py)[^`]*`' "$file" | while IFS=: read -r line_num match; do
|
||||
# Get actual line to check if in code block
|
||||
actual_line=$(sed -n "${line_num}p" "$file")
|
||||
|
||||
# Skip if line is indented (code block) or in fenced code
|
||||
if [[ "$actual_line" =~ ^[[:space:]]{4,} ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
code_block_count=$(sed -n "1,${line_num}p" "$file" | grep -c '^```')
|
||||
if [ $((code_block_count % 2)) -eq 1 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
ref=$(echo "$match" | grep -o '@[a-zA-Z0-9._~/-]*\.[a-zA-Z0-9]*')
|
||||
echo " ❌ BACKTICKED: $ref on line $line_num"
|
||||
echo " File: $(basename $(dirname "$file"))/$(basename "$file")"
|
||||
echo " Fix: Remove backticks - use bare @ reference"
|
||||
backticked_refs=$((backticked_refs + 1))
|
||||
done
|
||||
|
||||
# Check for ANY @ links to .md/.sh/.ts/.js/.py files (force-loads, burns context)
|
||||
grep -nE '(^|[ \(>-])@[a-zA-Z0-9._/-]+\.(md|sh|ts|js|py)' "$file" | \
|
||||
grep -v '@[a-zA-Z0-9._%+-]*@' | \
|
||||
grep -v 'email.*@' | \
|
||||
grep -v '`.*@.*`' | while IFS=: read -r line_num match; do
|
||||
|
||||
ref=$(echo "$match" | grep -o '@[a-zA-Z0-9._/-]+\.(md|sh|ts|js|py)')
|
||||
ref_path="${ref#@}"
|
||||
|
||||
# Skip if in fenced code block
|
||||
actual_line=$(sed -n "${line_num}p" "$file")
|
||||
if [[ "$actual_line" =~ ^[[:space:]]{4,} ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
code_block_count=$(sed -n "1,${line_num}p" "$file" | grep -c '^```')
|
||||
if [ $((code_block_count % 2)) -eq 1 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Any @ link is wrong - should use skills/path format
|
||||
echo " ❌ @ LINK: $ref on line $line_num"
|
||||
echo " File: $(basename $(dirname "$file"))/$(basename "$file")"
|
||||
|
||||
# Suggest correct format
|
||||
if [[ "$ref_path" == skills/* ]]; then
|
||||
# @skills/category/name/SKILL.md → skills/category/name
|
||||
corrected="${ref_path#skills/}"
|
||||
corrected="${corrected%/SKILL.md}"
|
||||
echo " Fix: $ref → skills/$corrected"
|
||||
elif [[ "$ref_path" == ../* ]]; then
|
||||
echo " Fix: Convert to skills/category/skill-name format"
|
||||
else
|
||||
echo " Fix: Convert to skills/category/skill-name format"
|
||||
fi
|
||||
|
||||
at_links=$((at_links + 1))
|
||||
done
|
||||
done < <(find "$SKILLS_DIR" -type f -name "*.md")
|
||||
|
||||
# Summary
|
||||
total_issues=$((backticked_refs + at_links))
|
||||
if [ $total_issues -eq 0 ]; then
|
||||
echo " ✅ All skill references OK"
|
||||
else
|
||||
[ $backticked_refs -gt 0 ] && echo " ❌ $backticked_refs backticked references"
|
||||
[ $at_links -gt 0 ] && echo " ❌ $at_links @ links (force-load context)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Correct format: skills/category/skill-name"
|
||||
echo " ❌ Bad: @skills/path/SKILL.md (force-loads) or @../path (brittle)"
|
||||
echo " ✅ Good: skills/category/skill-name (load with Read tool when needed)"
|
||||
|
||||
echo ""
|
||||
# Verify all skills mentioned in INDEX files exist
|
||||
find "$SKILLS_DIR" -type f -name "INDEX.md" | while read -r index_file; do
|
||||
index_dir=$(dirname "$index_file")
|
||||
|
||||
# Extract skill references (format: @skill-name/SKILL.md)
|
||||
grep -o '@[a-zA-Z0-9-]*/SKILL\.md' "$index_file" | while read -r skill_ref; do
|
||||
skill_path="$index_dir/${skill_ref#@}"
|
||||
|
||||
if [[ ! -f "$skill_path" ]]; then
|
||||
echo " ❌ BROKEN: $skill_ref in $(basename "$index_dir")/INDEX.md"
|
||||
echo " Expected: $skill_path"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
find "$SKILLS_DIR" -type f -path "*/*/SKILL.md" | while read -r skill_file; do
|
||||
skill_dir=$(basename $(dirname "$skill_file"))
|
||||
category_dir=$(dirname $(dirname "$skill_file"))
|
||||
index_file="$category_dir/INDEX.md"
|
||||
|
||||
if [[ -f "$index_file" ]]; then
|
||||
if ! grep -q "@$skill_dir/SKILL.md" "$index_file"; then
|
||||
echo " ⚠️ ORPHANED: $skill_dir/SKILL.md not in $(basename "$category_dir")/INDEX.md"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Check naming consistency in skills wiki
|
||||
|
||||
SKILLS_DIR="${1:-$HOME/Documents/GitHub/dotfiles/.claude/skills}"
|
||||
|
||||
echo "## Naming & Structure"
|
||||
issues=0
|
||||
|
||||
find "$SKILLS_DIR" -type d -mindepth 2 -maxdepth 2 | while read -r dir; do
|
||||
dir_name=$(basename "$dir")
|
||||
|
||||
# Skip if it's an INDEX or top-level category
|
||||
if [[ "$dir_name" == "INDEX.md" ]] || [[ $(dirname "$dir") == "$SKILLS_DIR" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check for naming issues
|
||||
if [[ "$dir_name" =~ [A-Z] ]]; then
|
||||
echo " ⚠️ Mixed case: $dir_name (should be kebab-case)"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
if [[ "$dir_name" =~ _ ]]; then
|
||||
echo " ⚠️ Underscore: $dir_name (should use hyphens)"
|
||||
issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
# Check if name follows gerund pattern for process skills
|
||||
if [[ -f "$dir/SKILL.md" ]]; then
|
||||
type=$(grep "^type:" "$dir/SKILL.md" | head -1 | cut -d: -f2 | xargs)
|
||||
|
||||
if [[ "$type" == "technique" ]] && [[ ! "$dir_name" =~ ing$ ]] && [[ ! "$dir_name" =~ -with- ]] && [[ ! "$dir_name" =~ ^test- ]]; then
|
||||
# Techniques might want -ing but not required
|
||||
:
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
[ $issues -eq 0 ] && echo " ✅ Directory names OK" || echo " ⚠️ $issues naming issues"
|
||||
|
||||
echo ""
|
||||
find "$SKILLS_DIR" -type d -empty | while read -r empty_dir; do
|
||||
echo " ⚠️ EMPTY: $(realpath --relative-to="$SKILLS_DIR" "$empty_dir" 2>/dev/null || echo "$empty_dir")"
|
||||
done
|
||||
|
||||
echo ""
|
||||
find "$SKILLS_DIR" -type f -name "SKILL.md" | while read -r skill_file; do
|
||||
skill_name=$(basename $(dirname "$skill_file"))
|
||||
|
||||
# Check for required fields
|
||||
if ! grep -q "^name:" "$skill_file"; then
|
||||
echo " ❌ MISSING 'name': $skill_name/SKILL.md"
|
||||
fi
|
||||
|
||||
if ! grep -q "^description:" "$skill_file"; then
|
||||
echo " ❌ MISSING 'description': $skill_name/SKILL.md"
|
||||
fi
|
||||
|
||||
if ! grep -q "^when_to_use:" "$skill_file"; then
|
||||
echo " ❌ MISSING 'when_to_use': $skill_name/SKILL.md"
|
||||
fi
|
||||
|
||||
if ! grep -q "^version:" "$skill_file"; then
|
||||
echo " ⚠️ MISSING 'version': $skill_name/SKILL.md"
|
||||
fi
|
||||
|
||||
# Check for active voice in name (should not start with "How to")
|
||||
name_value=$(grep "^name:" "$skill_file" | head -1 | cut -d: -f2- | xargs)
|
||||
if [[ "$name_value" =~ ^How\ to ]]; then
|
||||
echo " ⚠️ Passive name: $skill_name has 'How to' prefix (prefer active voice)"
|
||||
fi
|
||||
done
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Master gardening script for skills wiki maintenance
|
||||
|
||||
SKILLS_DIR="${1:-$HOME/Documents/GitHub/dotfiles/.claude/skills}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "=== Skills Wiki Health Check ==="
|
||||
echo ""
|
||||
|
||||
# Make scripts executable if not already
|
||||
chmod +x "$SCRIPT_DIR"/*.sh 2>/dev/null
|
||||
|
||||
# Run all checks
|
||||
bash "$SCRIPT_DIR/check-naming.sh" "$SKILLS_DIR"
|
||||
echo ""
|
||||
|
||||
bash "$SCRIPT_DIR/check-links.sh" "$SKILLS_DIR"
|
||||
echo ""
|
||||
|
||||
bash "$SCRIPT_DIR/check-index-coverage.sh" "$SKILLS_DIR"
|
||||
|
||||
echo ""
|
||||
echo "=== Health Check Complete ==="
|
||||
echo ""
|
||||
echo "Fix: ❌ errors (broken/missing) | Consider: ⚠️ warnings | ✅ = correct"
|
||||
@@ -1,162 +0,0 @@
|
||||
---
|
||||
name: Setting Up Personal Superpowers
|
||||
description: Automatic setup of ~/.config/superpowers/ for personal skills, optional GitHub repo creation
|
||||
when_to_use: Runs automatically on first session. Reference when helping users with personal skills setup.
|
||||
version: 1.0.0
|
||||
languages: bash
|
||||
---
|
||||
|
||||
# Setting Up Personal Superpowers
|
||||
|
||||
## Overview
|
||||
|
||||
Personal superpowers directory is automatically set up on your first session. It provides a place to create and manage your own skills alongside the core superpowers library.
|
||||
|
||||
**Default location:** `~/.config/superpowers/`
|
||||
|
||||
**Customizable via:**
|
||||
- `PERSONAL_SUPERPOWERS_DIR` environment variable (highest priority)
|
||||
- `XDG_CONFIG_HOME` environment variable (if set, uses `$XDG_CONFIG_HOME/superpowers`)
|
||||
- Falls back to `~/.config/superpowers`
|
||||
|
||||
**Structure:**
|
||||
```
|
||||
~/.config/superpowers/
|
||||
├── .git/ # Git repository
|
||||
├── .gitignore # Ignores logs and indexes
|
||||
├── README.md # About your personal superpowers
|
||||
├── skills/ # Your personal skills
|
||||
│ └── your-skill/
|
||||
│ └── SKILL.md
|
||||
├── search-log.jsonl # Skill search history (not tracked)
|
||||
└── conversation-index/ # Conversation search index (not tracked)
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The SessionStart hook runs `hooks/setup-personal-superpowers.sh` which:
|
||||
|
||||
1. Checks if `~/.config/superpowers/.git/` and `~/.config/superpowers/skills/` exist
|
||||
2. If not, creates directory structure
|
||||
3. Initializes git repository
|
||||
4. Creates `.gitignore`, `README.md`
|
||||
5. Makes initial commit
|
||||
6. Checks for `gh` CLI availability
|
||||
|
||||
If GitHub CLI is available and no remote exists, you'll see a recommendation to create a public GitHub repo.
|
||||
|
||||
## Creating GitHub Repository
|
||||
|
||||
When prompted, you can create a public `personal-superpowers` repo:
|
||||
|
||||
```bash
|
||||
cd ~/.config/superpowers
|
||||
gh repo create personal-superpowers --public --source=. --push
|
||||
gh repo edit --add-topic superpowers
|
||||
```
|
||||
|
||||
**Why public?** Superpowers are best when everyone can learn from them!
|
||||
|
||||
**Privacy:** If you prefer private or local-only:
|
||||
- **Private:** Use `--private` instead of `--public`
|
||||
- **Local-only:** Just use the local git repo without pushing to GitHub
|
||||
|
||||
## What Gets Tracked
|
||||
|
||||
**.gitignore includes:**
|
||||
- `search-log.jsonl` - Your skill search history
|
||||
- `conversation-index/` - Conversation search index
|
||||
- `conversation-archive/` - Archived conversations
|
||||
|
||||
**Everything else is tracked**, including:
|
||||
- Your personal skills in `skills/`
|
||||
- README.md
|
||||
- Any additional documentation you add
|
||||
|
||||
## Personal vs Core Skills
|
||||
|
||||
**Search order:**
|
||||
1. `~/.config/superpowers/skills/` (personal)
|
||||
2. `${CLAUDE_PLUGIN_ROOT}/skills/` (core)
|
||||
|
||||
**Personal skills shadow core skills** - if you have `~/.config/superpowers/skills/testing/test-driven-development/SKILL.md`, it will be used instead of the core version.
|
||||
|
||||
The `find-skills` tool automatically searches both locations with deduplication.
|
||||
|
||||
## Writing Skills
|
||||
|
||||
See skills/meta/writing-skills for how to create new skills.
|
||||
|
||||
All personal skills are written to `~/.config/superpowers/skills/`.
|
||||
|
||||
## Sharing Skills
|
||||
|
||||
See skills/meta/sharing-skills for how to contribute skills back to the core superpowers repository.
|
||||
|
||||
## Custom Location
|
||||
|
||||
To use a different location for your personal superpowers:
|
||||
|
||||
```bash
|
||||
# In your shell rc file (.bashrc, .zshrc, etc)
|
||||
export PERSONAL_SUPERPOWERS_DIR="$HOME/my-superpowers"
|
||||
|
||||
# Or use XDG_CONFIG_HOME
|
||||
export XDG_CONFIG_HOME="$HOME/.local/config" # Will use $HOME/.local/config/superpowers
|
||||
```
|
||||
|
||||
## Manual Setup
|
||||
|
||||
If auto-setup fails or you need to set up manually:
|
||||
|
||||
```bash
|
||||
# Use your preferred location
|
||||
SUPERPOWERS_DIR="${PERSONAL_SUPERPOWERS_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/superpowers}"
|
||||
mkdir -p "$SUPERPOWERS_DIR/skills"
|
||||
cd "$SUPERPOWERS_DIR"
|
||||
git init
|
||||
cat > .gitignore <<'EOF'
|
||||
search-log.jsonl
|
||||
conversation-index/
|
||||
conversation-archive/
|
||||
EOF
|
||||
|
||||
cat > README.md <<'EOF'
|
||||
# My Personal Superpowers
|
||||
|
||||
Personal skills and techniques for Claude Code.
|
||||
|
||||
Learn more about Superpowers: https://github.com/obra/superpowers
|
||||
EOF
|
||||
|
||||
git add .gitignore README.md
|
||||
git commit -m "Initial commit: Personal superpowers setup"
|
||||
|
||||
# Optional: Create GitHub repo
|
||||
gh repo create personal-superpowers --public --source=. --push
|
||||
gh repo edit --add-topic superpowers
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Setup failed during SessionStart:**
|
||||
File a bug at https://github.com/obra/superpowers/issues
|
||||
|
||||
**Personal skills not being found:**
|
||||
- Check `~/.config/superpowers/skills/` exists
|
||||
- Verify skill has `SKILL.md` file
|
||||
- Run `${CLAUDE_PLUGIN_ROOT}/scripts/find-skills` to see if it appears
|
||||
|
||||
**GitHub push failed:**
|
||||
- Check `gh auth status`
|
||||
- Verify repo was created: `gh repo view personal-superpowers`
|
||||
- Try manual push: `cd ~/.config/superpowers && git push -u origin main`
|
||||
|
||||
## Multi-CLI Support
|
||||
|
||||
The personal superpowers directory is CLI-agnostic. It works with:
|
||||
- Claude Code (current)
|
||||
- OpenAI Codex CLI (future)
|
||||
- Gemini CLI (future)
|
||||
|
||||
Each CLI installs its own base superpowers, but they all read from the same `~/.config/superpowers/skills/` for personal skills.
|
||||
@@ -1,240 +0,0 @@
|
||||
---
|
||||
name: Sharing Skills
|
||||
description: Contribute personal skills back to core superpowers via fork, branch, and PR
|
||||
when_to_use: When you have a personal skill that would benefit others and want to contribute it to the core superpowers library
|
||||
version: 1.0.0
|
||||
languages: bash
|
||||
---
|
||||
|
||||
# Sharing Skills
|
||||
|
||||
## Overview
|
||||
|
||||
Contribute personal skills from `~/.config/superpowers/skills/` back to the core superpowers repository.
|
||||
|
||||
**Workflow:** Fork → Clone to temp → Sync → Branch → Copy skill → Commit → Push → PR
|
||||
|
||||
## When to Share
|
||||
|
||||
**Share when:**
|
||||
- Skill applies broadly (not project-specific)
|
||||
- Pattern/technique others would benefit from
|
||||
- Well-tested and documented
|
||||
- Follows skills/meta/writing-skills guidelines
|
||||
|
||||
**Keep personal when:**
|
||||
- Project-specific or organization-specific
|
||||
- Experimental or unstable
|
||||
- Contains sensitive information
|
||||
- Too narrow/niche for general use
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `gh` CLI installed and authenticated
|
||||
- Personal skill exists in `~/.config/superpowers/skills/your-skill/`
|
||||
- Skill has been tested (see skills/meta/writing-skills for TDD process)
|
||||
|
||||
## Sharing Workflow
|
||||
|
||||
### 1. Fork Core Repository
|
||||
|
||||
```bash
|
||||
# Check if you already have a fork
|
||||
gh repo view YOUR_USERNAME/superpowers 2>/dev/null || gh repo fork obra/superpowers
|
||||
```
|
||||
|
||||
### 2. Clone to Temporary Directory
|
||||
|
||||
```bash
|
||||
# Create temp directory for contribution
|
||||
temp_dir=$(mktemp -d)
|
||||
cd "$temp_dir"
|
||||
git clone git@github.com:YOUR_USERNAME/superpowers.git
|
||||
cd superpowers
|
||||
```
|
||||
|
||||
### 3. Sync with Upstream
|
||||
|
||||
```bash
|
||||
# Add upstream if not already added
|
||||
git remote add upstream https://github.com/obra/superpowers 2>/dev/null || true
|
||||
|
||||
# Fetch and merge latest from upstream
|
||||
git fetch upstream
|
||||
git checkout main
|
||||
git merge upstream/main
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 4. Create Feature Branch
|
||||
|
||||
```bash
|
||||
# Branch name: add-skillname-skill
|
||||
skill_name="your-skill-name"
|
||||
git checkout -b "add-${skill_name}-skill"
|
||||
```
|
||||
|
||||
### 5. Copy Skill from Personal Repo
|
||||
|
||||
```bash
|
||||
# Copy skill directory from personal superpowers
|
||||
cp -r ~/.config/superpowers/skills/your-skill-name/ superpowers/skills/
|
||||
|
||||
# Verify it copied correctly
|
||||
ls -la superpowers/skills/your-skill-name/
|
||||
```
|
||||
|
||||
### 6. Commit Changes
|
||||
|
||||
```bash
|
||||
# Add and commit
|
||||
git add superpowers/skills/your-skill-name/
|
||||
git commit -m "Add ${skill_name} skill
|
||||
|
||||
$(cat <<'EOF'
|
||||
Brief description of what this skill does and why it's useful.
|
||||
|
||||
Tested with: [describe testing approach]
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 7. Push to Your Fork
|
||||
|
||||
```bash
|
||||
git push -u origin "add-${skill_name}-skill"
|
||||
```
|
||||
|
||||
### 8. Create Pull Request
|
||||
|
||||
```bash
|
||||
# Create PR using gh CLI
|
||||
gh pr create \
|
||||
--repo obra/superpowers \
|
||||
--title "Add ${skill_name} skill" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
Brief description of the skill and what problem it solves.
|
||||
|
||||
## Testing
|
||||
Describe how you tested this skill (pressure scenarios, baseline tests, etc.).
|
||||
|
||||
## Context
|
||||
Any additional context about why this skill is needed and how it should be used.
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 9. Cleanup
|
||||
|
||||
```bash
|
||||
# Remove temp directory after PR is created
|
||||
cd ~
|
||||
rm -rf "$temp_dir"
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of sharing a personal skill called "async-patterns":
|
||||
|
||||
```bash
|
||||
# 1. Fork if needed
|
||||
gh repo view $(gh api user --jq .login)/superpowers 2>/dev/null || gh repo fork obra/superpowers
|
||||
|
||||
# 2-3. Clone and sync
|
||||
temp_dir=$(mktemp -d) && cd "$temp_dir"
|
||||
gh repo clone $(gh api user --jq .login)/superpowers
|
||||
cd superpowers
|
||||
git remote add upstream https://github.com/obra/superpowers 2>/dev/null || true
|
||||
git fetch upstream
|
||||
git checkout main
|
||||
git merge upstream/main
|
||||
git push origin main
|
||||
|
||||
# 4. Create branch
|
||||
git checkout -b "add-async-patterns-skill"
|
||||
|
||||
# 5. Copy skill
|
||||
cp -r ~/.config/superpowers/skills/async-patterns/ superpowers/skills/
|
||||
|
||||
# 6. Commit
|
||||
git add superpowers/skills/async-patterns/
|
||||
git commit -m "Add async-patterns skill
|
||||
|
||||
Patterns for handling asynchronous operations in tests and application code.
|
||||
|
||||
Tested with: Multiple pressure scenarios testing agent compliance."
|
||||
|
||||
# 7. Push
|
||||
git push -u origin "add-async-patterns-skill"
|
||||
|
||||
# 8. Create PR
|
||||
gh pr create \
|
||||
--repo obra/superpowers \
|
||||
--title "Add async-patterns skill" \
|
||||
--body "## Summary
|
||||
Patterns for handling asynchronous operations correctly in tests and application code.
|
||||
|
||||
## Testing
|
||||
Tested with multiple application scenarios. Agents successfully apply patterns to new code.
|
||||
|
||||
## Context
|
||||
Addresses common async pitfalls like race conditions, improper error handling, and timing issues."
|
||||
|
||||
# 9. Cleanup
|
||||
cd ~ && rm -rf "$temp_dir"
|
||||
```
|
||||
|
||||
## After PR is Merged
|
||||
|
||||
Once your PR is merged:
|
||||
|
||||
**Option 1: Keep personal version**
|
||||
- Useful if you want to continue iterating locally
|
||||
- Your personal version will shadow the core version
|
||||
- Can later delete personal version to use core
|
||||
|
||||
**Option 2: Delete personal version**
|
||||
```bash
|
||||
# Remove from personal repo to use core version
|
||||
rm -rf ~/.config/superpowers/skills/your-skill-name/
|
||||
cd ~/.config/superpowers
|
||||
git add skills/your-skill-name/
|
||||
git commit -m "Remove your-skill-name (now in core)"
|
||||
git push
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"gh: command not found"**
|
||||
- Install GitHub CLI: https://cli.github.com/
|
||||
- Authenticate: `gh auth login`
|
||||
|
||||
**"Permission denied (publickey)"**
|
||||
- Check SSH keys: `gh auth status`
|
||||
- Set up SSH: https://docs.github.com/en/authentication
|
||||
|
||||
**"Skill already exists in core"**
|
||||
- You're creating a modified version
|
||||
- Consider different skill name or shadow the core version in personal repo
|
||||
|
||||
**PR merge conflicts**
|
||||
- Rebase on latest upstream: `git fetch upstream && git rebase upstream/main`
|
||||
- Resolve conflicts
|
||||
- Force push: `git push -f origin your-branch`
|
||||
|
||||
## Multi-Skill Contributions
|
||||
|
||||
**Do NOT batch multiple skills in one PR.**
|
||||
|
||||
Each skill should:
|
||||
- Have its own feature branch
|
||||
- Have its own PR
|
||||
- Be independently reviewable
|
||||
|
||||
**Why?** Individual skills can be reviewed, iterated, and merged independently.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **skills/meta/writing-skills** - How to create well-tested skills
|
||||
- **skills/meta/setting-up-personal-superpowers** - Initial setup of personal repo
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Code Review Reception
|
||||
description: Receive and act on code review feedback with technical rigor, not performative agreement or blind implementation
|
||||
when_to_use: When receiving code review feedback from your human partner or external reviewers. Before implementing review suggestions. When PR comments arrive. When feedback seems wrong or unclear.
|
||||
version: 1.0.0
|
||||
name: receiving-code-review
|
||||
description: Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation
|
||||
---
|
||||
|
||||
# Code Review Reception
|
||||
@@ -1,13 +1,11 @@
|
||||
---
|
||||
name: Requesting Code Review
|
||||
description: Dispatch code-reviewer subagent to review implementation against plan or requirements before proceeding
|
||||
when_to_use: After completing a task. After major feature implementation. Before merging. When executing plans (after each task). When stuck and need fresh perspective.
|
||||
version: 1.0.0
|
||||
name: requesting-code-review
|
||||
description: Use when completing tasks, implementing major features, or before merging to verify work meets requirements - dispatches superpowers:code-reviewer subagent to review implementation against plan or requirements before proceeding
|
||||
---
|
||||
|
||||
# Requesting Code Review
|
||||
|
||||
Dispatch code-reviewer subagent to catch issues before they cascade.
|
||||
Dispatch superpowers:code-reviewer subagent to catch issues before they cascade.
|
||||
|
||||
**Core principle:** Review early, review often.
|
||||
|
||||
@@ -33,7 +31,7 @@ HEAD_SHA=$(git rev-parse HEAD)
|
||||
|
||||
**2. Dispatch code-reviewer subagent:**
|
||||
|
||||
Use Task tool with code-reviewer type, fill template at `code-reviewer.md`
|
||||
Use Task tool with superpowers:code-reviewer type, fill template at `code-reviewer.md`
|
||||
|
||||
**Placeholders:**
|
||||
- `{WHAT_WAS_IMPLEMENTED}` - What you just built
|
||||
@@ -58,7 +56,7 @@ You: Let me request code review before proceeding.
|
||||
BASE_SHA=$(git log --oneline | grep "Task 1" | head -1 | awk '{print $1}')
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
|
||||
[Dispatch code-reviewer subagent]
|
||||
[Dispatch superpowers:code-reviewer subagent]
|
||||
WHAT_WAS_IMPLEMENTED: Verification and repair functions for conversation index
|
||||
PLAN_OR_REQUIREMENTS: Task 2 from docs/plans/deployment-plan.md
|
||||
BASE_SHA: a7981ec
|
||||
@@ -104,4 +102,4 @@ You: [Fix progress indicators]
|
||||
- Show code/tests that prove it works
|
||||
- Request clarification
|
||||
|
||||
See template at: skills/collaboration/requesting-code-review/code-reviewer.md
|
||||
See template at: requesting-code-review/code-reviewer.md
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Root Cause Tracing
|
||||
description: Systematically trace bugs backward through call stack to find original trigger
|
||||
when_to_use: Bug appears deep in call stack but you need to find where it originates
|
||||
version: 1.0.0
|
||||
languages: all
|
||||
name: root-cause-tracing
|
||||
description: Use when errors occur deep in execution and you need to trace back to find the original trigger - systematically traces bugs backward through call stack, adding instrumentation when needed, to identify source of invalid data or incorrect behavior
|
||||
---
|
||||
|
||||
# Root Cause Tracing
|
||||
194
skills/sharing-skills/SKILL.md
Normal file
194
skills/sharing-skills/SKILL.md
Normal file
@@ -0,0 +1,194 @@
|
||||
---
|
||||
name: sharing-skills
|
||||
description: Use when you've developed a broadly useful skill and want to contribute it upstream via pull request - guides process of branching, committing, pushing, and creating PR to contribute skills back to upstream repository
|
||||
---
|
||||
|
||||
# Sharing Skills
|
||||
|
||||
## Overview
|
||||
|
||||
Contribute skills from your local branch back to the upstream repository.
|
||||
|
||||
**Workflow:** Branch → Edit/Create skill → Commit → Push → PR
|
||||
|
||||
## When to Share
|
||||
|
||||
**Share when:**
|
||||
- Skill applies broadly (not project-specific)
|
||||
- Pattern/technique others would benefit from
|
||||
- Well-tested and documented
|
||||
- Follows writing-skills guidelines
|
||||
|
||||
**Keep personal when:**
|
||||
- Project-specific or organization-specific
|
||||
- Experimental or unstable
|
||||
- Contains sensitive information
|
||||
- Too narrow/niche for general use
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `gh` CLI installed and authenticated
|
||||
- Working directory is `~/.config/superpowers/skills/` (your local clone)
|
||||
- **REQUIRED:** Skill has been tested using writing-skills TDD process
|
||||
|
||||
## Sharing Workflow
|
||||
|
||||
### 1. Ensure You're on Main and Synced
|
||||
|
||||
```bash
|
||||
cd ~/.config/superpowers/skills/
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
git push origin main # Push to your fork
|
||||
```
|
||||
|
||||
### 2. Create Feature Branch
|
||||
|
||||
```bash
|
||||
# Branch name: add-skillname-skill
|
||||
skill_name="your-skill-name"
|
||||
git checkout -b "add-${skill_name}-skill"
|
||||
```
|
||||
|
||||
### 3. Create or Edit Skill
|
||||
|
||||
```bash
|
||||
# Work on your skill in skills/
|
||||
# Create new skill or edit existing one
|
||||
# Skill should be in skills/category/skill-name/SKILL.md
|
||||
```
|
||||
|
||||
### 4. Commit Changes
|
||||
|
||||
```bash
|
||||
# Add and commit
|
||||
git add skills/your-skill-name/
|
||||
git commit -m "Add ${skill_name} skill
|
||||
|
||||
$(cat <<'EOF'
|
||||
Brief description of what this skill does and why it's useful.
|
||||
|
||||
Tested with: [describe testing approach]
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
### 5. Push to Your Fork
|
||||
|
||||
```bash
|
||||
git push -u origin "add-${skill_name}-skill"
|
||||
```
|
||||
|
||||
### 6. Create Pull Request
|
||||
|
||||
```bash
|
||||
# Create PR to upstream using gh CLI
|
||||
gh pr create \
|
||||
--repo upstream-org/upstream-repo \
|
||||
--title "Add ${skill_name} skill" \
|
||||
--body "$(cat <<'EOF'
|
||||
## Summary
|
||||
Brief description of the skill and what problem it solves.
|
||||
|
||||
## Testing
|
||||
Describe how you tested this skill (pressure scenarios, baseline tests, etc.).
|
||||
|
||||
## Context
|
||||
Any additional context about why this skill is needed and how it should be used.
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
Here's a complete example of sharing a skill called "async-patterns":
|
||||
|
||||
```bash
|
||||
# 1. Sync with upstream
|
||||
cd ~/.config/superpowers/skills/
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
git push origin main
|
||||
|
||||
# 2. Create branch
|
||||
git checkout -b "add-async-patterns-skill"
|
||||
|
||||
# 3. Create/edit the skill
|
||||
# (Work on skills/async-patterns/SKILL.md)
|
||||
|
||||
# 4. Commit
|
||||
git add skills/async-patterns/
|
||||
git commit -m "Add async-patterns skill
|
||||
|
||||
Patterns for handling asynchronous operations in tests and application code.
|
||||
|
||||
Tested with: Multiple pressure scenarios testing agent compliance."
|
||||
|
||||
# 5. Push
|
||||
git push -u origin "add-async-patterns-skill"
|
||||
|
||||
# 6. Create PR
|
||||
gh pr create \
|
||||
--repo upstream-org/upstream-repo \
|
||||
--title "Add async-patterns skill" \
|
||||
--body "## Summary
|
||||
Patterns for handling asynchronous operations correctly in tests and application code.
|
||||
|
||||
## Testing
|
||||
Tested with multiple application scenarios. Agents successfully apply patterns to new code.
|
||||
|
||||
## Context
|
||||
Addresses common async pitfalls like race conditions, improper error handling, and timing issues."
|
||||
```
|
||||
|
||||
## After PR is Merged
|
||||
|
||||
Once your PR is merged:
|
||||
|
||||
1. Sync your local main branch:
|
||||
```bash
|
||||
cd ~/.config/superpowers/skills/
|
||||
git checkout main
|
||||
git pull upstream main
|
||||
git push origin main
|
||||
```
|
||||
|
||||
2. Delete the feature branch:
|
||||
```bash
|
||||
git branch -d "add-${skill_name}-skill"
|
||||
git push origin --delete "add-${skill_name}-skill"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"gh: command not found"**
|
||||
- Install GitHub CLI: https://cli.github.com/
|
||||
- Authenticate: `gh auth login`
|
||||
|
||||
**"Permission denied (publickey)"**
|
||||
- Check SSH keys: `gh auth status`
|
||||
- Set up SSH: https://docs.github.com/en/authentication
|
||||
|
||||
**"Skill already exists"**
|
||||
- You're creating a modified version
|
||||
- Consider different skill name or coordinate with the skill's maintainer
|
||||
|
||||
**PR merge conflicts**
|
||||
- Rebase on latest upstream: `git fetch upstream && git rebase upstream/main`
|
||||
- Resolve conflicts
|
||||
- Force push: `git push -f origin your-branch`
|
||||
|
||||
## Multi-Skill Contributions
|
||||
|
||||
**Do NOT batch multiple skills in one PR.**
|
||||
|
||||
Each skill should:
|
||||
- Have its own feature branch
|
||||
- Have its own PR
|
||||
- Be independently reviewable
|
||||
|
||||
**Why?** Individual skills can be reviewed, iterated, and merged independently.
|
||||
|
||||
## Related Skills
|
||||
|
||||
- **writing-skills** - REQUIRED: How to create well-tested skills before sharing
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Subagent-Driven Development
|
||||
description: Execute implementation plan by dispatching fresh subagent for each task, with code review between tasks
|
||||
when_to_use: Alternative to executing-plans when staying in same session. When tasks are independent. When want fast iteration with review checkpoints. After writing implementation plan.
|
||||
version: 1.0.0
|
||||
name: subagent-driven-development
|
||||
description: Use when executing implementation plans with independent tasks in the current session - dispatches fresh subagent for each task with code review between tasks, enabling fast iteration with quality gates
|
||||
---
|
||||
|
||||
# Subagent-Driven Development
|
||||
@@ -64,8 +62,8 @@ Task tool (general-purpose):
|
||||
|
||||
**Dispatch code-reviewer subagent:**
|
||||
```
|
||||
Task tool (code-reviewer):
|
||||
Use template at skills/collaboration/requesting-code-review/code-reviewer.md
|
||||
Task tool (superpowers:code-reviewer):
|
||||
Use template at requesting-code-review/code-reviewer.md
|
||||
|
||||
WHAT_WAS_IMPLEMENTED: [from subagent's report]
|
||||
PLAN_OR_REQUIREMENTS: Task N from [plan-file]
|
||||
@@ -104,8 +102,8 @@ After all tasks complete, dispatch final code-reviewer:
|
||||
### 7. Complete Development
|
||||
|
||||
After final review passes:
|
||||
- Announce: "I'm using the Finishing a Development Branch skill to complete this work."
|
||||
- Switch to skills/collaboration/finishing-a-development-branch
|
||||
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch
|
||||
- Follow that skill to verify tests, present options, execute choice
|
||||
|
||||
## Example Workflow
|
||||
@@ -177,12 +175,15 @@ Done!
|
||||
|
||||
## Integration
|
||||
|
||||
**Pairs with:**
|
||||
- skills/collaboration/writing-plans (creates the plan)
|
||||
- skills/collaboration/requesting-code-review (review template)
|
||||
- skills/testing/test-driven-development (subagents follow this)
|
||||
**Required workflow skills:**
|
||||
- **writing-plans** - REQUIRED: Creates the plan that this skill executes
|
||||
- **requesting-code-review** - REQUIRED: Review after each task (see Step 3)
|
||||
- **finishing-a-development-branch** - REQUIRED: Complete development after all tasks (see Step 7)
|
||||
|
||||
**Alternative to:**
|
||||
- skills/collaboration/executing-plans (parallel session)
|
||||
**Subagents must use:**
|
||||
- **test-driven-development** - Subagents follow TDD for each task
|
||||
|
||||
See code-reviewer template: skills/collaboration/requesting-code-review/code-reviewer.md
|
||||
**Alternative workflow:**
|
||||
- **executing-plans** - Use for parallel session instead of same-session execution
|
||||
|
||||
See code-reviewer template: requesting-code-review/code-reviewer.md
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Systematic Debugging
|
||||
description: Four-phase debugging framework that ensures root cause investigation before attempting fixes. Never jump to solutions.
|
||||
when_to_use: When encountering any technical issue, bug, test failure, or unexpected behavior. When tempted to quick-fix symptoms. When debugging feels chaotic or circular. When fixes don't stick. Before proposing any fix. When you notice yourself jumping to solutions.
|
||||
version: 2.0.0
|
||||
languages: all
|
||||
name: systematic-debugging
|
||||
description: Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes - four-phase framework (root cause investigation, pattern analysis, hypothesis testing, implementation) that ensures understanding before attempting solutions
|
||||
---
|
||||
|
||||
# Systematic Debugging
|
||||
@@ -114,7 +111,7 @@ You MUST complete each phase before proceeding to the next.
|
||||
|
||||
**WHEN error is deep in call stack:**
|
||||
|
||||
See skills/root-cause-tracing for backward tracing technique
|
||||
**REQUIRED SUB-SKILL:** Use superpowers:root-cause-tracing for backward tracing technique
|
||||
|
||||
**Quick version:**
|
||||
- Where does bad value originate?
|
||||
@@ -179,7 +176,7 @@ You MUST complete each phase before proceeding to the next.
|
||||
- Automated test if possible
|
||||
- One-off test script if no framework
|
||||
- MUST have before fixing
|
||||
- See skills/testing/test-driven-development for writing proper failing tests
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:test-driven-development for writing proper failing tests
|
||||
|
||||
2. **Implement Single Fix**
|
||||
- Address the root cause identified
|
||||
@@ -280,11 +277,14 @@ If systematic investigation reveals issue is truly environmental, timing-depende
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
This skill works with:
|
||||
- skills/root-cause-tracing - How to trace back through call stack
|
||||
- skills/defense-in-depth - Add validation after finding root cause
|
||||
- skills/testing/condition-based-waiting - Replace timeouts identified in Phase 2
|
||||
- skills/verification-before-completion - Verify fix worked before claiming success
|
||||
**This skill requires using:**
|
||||
- **root-cause-tracing** - REQUIRED when error is deep in call stack (see Phase 1, Step 5)
|
||||
- **test-driven-development** - REQUIRED for creating failing test case (see Phase 4, Step 1)
|
||||
|
||||
**Complementary skills:**
|
||||
- **defense-in-depth** - Add validation at multiple layers after finding root cause
|
||||
- **condition-based-waiting** - Replace arbitrary timeouts identified in Phase 2
|
||||
- **verification-before-completion** - Verify fix worked before claiming success
|
||||
|
||||
## Real-World Impact
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Test-Driven Development (TDD)
|
||||
description: Write the test first, watch it fail, write minimal code to pass
|
||||
when_to_use: Every feature and bugfix. No exceptions. Test first, always. When you wrote code before tests. When you're tempted to test after. When manually testing seems faster. When you already spent hours on code without tests.
|
||||
version: 2.0.0
|
||||
languages: all
|
||||
name: test-driven-development
|
||||
description: Use when implementing any feature or bugfix, before writing implementation code - write the test first, watch it fail, write minimal code to pass; ensures tests actually verify behavior by requiring failure first
|
||||
---
|
||||
|
||||
# Test-Driven Development (TDD)
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Testing Anti-Patterns
|
||||
description: Never test mock behavior. Never add test-only methods to production classes. Understand dependencies before mocking.
|
||||
when_to_use: When writing tests. When adding mocks. When fixing failing tests. When tempted to add cleanup methods to production code. Before asserting on mock elements.
|
||||
version: 1.0.0
|
||||
name: testing-anti-patterns
|
||||
description: Use when writing or changing tests, adding mocks, or tempted to add test-only methods to production code - prevents testing mock behavior, production pollution with test-only methods, and mocking without understanding dependencies
|
||||
---
|
||||
|
||||
# Testing Anti-Patterns
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Testing Skills With Subagents
|
||||
description: RED-GREEN-REFACTOR for process documentation - baseline without skill, write addressing failures, iterate closing loopholes
|
||||
when_to_use: When creating any skill (especially discipline-enforcing). Before deploying skills. When skill needs to resist rationalization under pressure.
|
||||
version: 2.0.0
|
||||
name: testing-skills-with-subagents
|
||||
description: Use when creating or editing skills, before deployment, to verify they work under pressure and resist rationalization - applies RED-GREEN-REFACTOR cycle to process documentation by running baseline without skill, writing to address failures, iterating to close loopholes
|
||||
---
|
||||
|
||||
# Testing Skills With Subagents
|
||||
@@ -15,7 +13,7 @@ You run scenarios without the skill (RED - watch agent fail), write skill addres
|
||||
|
||||
**Core principle:** If you didn't watch an agent fail without the skill, you don't know if the skill prevents the right failures.
|
||||
|
||||
See skills/testing/test-driven-development for the fundamental cycle. This skill provides skill-specific test formats (pressure scenarios, rationalization tables).
|
||||
**REQUIRED BACKGROUND:** You MUST understand superpowers:test-driven-development before using this skill. That skill defines the fundamental RED-GREEN-REFACTOR cycle. This skill provides skill-specific test formats (pressure scenarios, rationalization tables).
|
||||
|
||||
**Complete worked example:** See examples/CLAUDE_MD_TESTING.md for a full test campaign testing CLAUDE.md documentation variants.
|
||||
|
||||
@@ -144,7 +142,7 @@ Forces explicit choice.
|
||||
|
||||
**Best tests combine 3+ pressures.**
|
||||
|
||||
**Why this works:** See skills/meta/creating-skills/persuasion-principles.md for research on how authority, scarcity, and commitment principles increase compliance pressure.
|
||||
**Why this works:** See persuasion-principles.md (in writing-skills directory) for research on how authority, scarcity, and commitment principles increase compliance pressure.
|
||||
|
||||
### Key Elements of Good Scenarios
|
||||
|
||||
@@ -160,7 +158,7 @@ Forces explicit choice.
|
||||
IMPORTANT: This is a real scenario. You must choose and act.
|
||||
Don't ask hypothetical questions - make the actual decision.
|
||||
|
||||
You have access to: skills/testing-skills-with-subagents/path/to/skill.md
|
||||
You have access to: [skill-being-tested]
|
||||
```
|
||||
|
||||
Make agent believe it's real work, not a quiz.
|
||||
@@ -221,11 +219,10 @@ Write code before test? Delete it. Start over.
|
||||
- "I'm following the spirit not the letter"
|
||||
```
|
||||
|
||||
### 4. Update when_to_use
|
||||
### 4. Update description
|
||||
|
||||
```yaml
|
||||
when_to_use: When you wrote code before tests. When tempted to
|
||||
test after. When manually testing seems faster.
|
||||
description: Use when you wrote code before tests, when tempted to test after, or when manually testing seems faster.
|
||||
```
|
||||
|
||||
Add symptoms of ABOUT to violate.
|
||||
@@ -330,7 +327,7 @@ Before deploying skill, verify you followed RED-GREEN-REFACTOR:
|
||||
- [ ] Added explicit counters for each loophole
|
||||
- [ ] Updated rationalization table
|
||||
- [ ] Updated red flags list
|
||||
- [ ] Updated when_to_use with violation symptoms
|
||||
- [ ] Updated description ith violation symptoms
|
||||
- [ ] Re-tested - agent still complies
|
||||
- [ ] Meta-tested to verify clarity
|
||||
- [ ] Agent follows rule under maximum pressure
|
||||
@@ -94,7 +94,7 @@ is at `~/.claude/skills/`.
|
||||
Browse categories: `ls ~/.claude/skills/`
|
||||
Search: `grep -r "keyword" ~/.claude/skills/ --include="SKILL.md"`
|
||||
|
||||
Instructions: `skills/getting-started`
|
||||
Instructions: `skills/using-skills`
|
||||
</available_skills>
|
||||
|
||||
<important_info_about_skills>
|
||||
@@ -129,7 +129,7 @@ Your workflow for every task:
|
||||
The skills library prevents you from repeating common mistakes.
|
||||
Not checking before you start is choosing to repeat those mistakes.
|
||||
|
||||
Start here: `skills/getting-started`
|
||||
Start here: `skills/using-skills`
|
||||
```
|
||||
|
||||
## Testing Protocol
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Using Git Worktrees
|
||||
description: Create isolated git worktrees with smart directory selection and safety verification
|
||||
when_to_use: When starting feature implementation in isolation. When brainstorming transitions to code. When need separate workspace without branch switching. Before executing implementation plans.
|
||||
version: 1.0.0
|
||||
name: using-git-worktrees
|
||||
description: Use when starting feature work that needs isolation from current workspace or before executing implementation plans - creates isolated git worktrees with smart directory selection and safety verification
|
||||
---
|
||||
|
||||
# Using Git Worktrees
|
||||
@@ -13,7 +11,7 @@ Git worktrees create isolated workspaces sharing the same repository, allowing w
|
||||
|
||||
**Core principle:** Systematic directory selection + safety verification = reliable isolation.
|
||||
|
||||
**Announce at start:** "I'm using the Using Git Worktrees skill to set up an isolated workspace."
|
||||
**Announce at start:** "I'm using the using-git-worktrees skill to set up an isolated workspace."
|
||||
|
||||
## Directory Selection Process
|
||||
|
||||
@@ -45,7 +43,7 @@ If no directory exists and no CLAUDE.md preference:
|
||||
No worktree directory found. Where should I create worktrees?
|
||||
|
||||
1. .worktrees/ (project-local, hidden)
|
||||
2. ~/.clank-worktrees/<project-name>/ (global location)
|
||||
2. ~/.config/superpowers/worktrees/<project-name>/ (global location)
|
||||
|
||||
Which would you prefer?
|
||||
```
|
||||
@@ -70,7 +68,7 @@ Per Jesse's rule "Fix broken things immediately":
|
||||
|
||||
**Why critical:** Prevents accidentally committing worktree contents to repository.
|
||||
|
||||
### For Global Directory (~/.clank-worktrees)
|
||||
### For Global Directory (~/.config/superpowers/worktrees)
|
||||
|
||||
No .gitignore verification needed - outside project entirely.
|
||||
|
||||
@@ -90,8 +88,8 @@ case $LOCATION in
|
||||
.worktrees|worktrees)
|
||||
path="$LOCATION/$BRANCH_NAME"
|
||||
;;
|
||||
~/.clank-worktrees/*)
|
||||
path="~/.clank-worktrees/$project/$BRANCH_NAME"
|
||||
~/.config/superpowers/worktrees/*)
|
||||
path="~/.config/superpowers/worktrees/$project/$BRANCH_NAME"
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -176,7 +174,7 @@ Ready to implement <feature-name>
|
||||
## Example Workflow
|
||||
|
||||
```
|
||||
You: I'm using the Using Git Worktrees skill to set up an isolated workspace.
|
||||
You: I'm using the using-git-worktrees skill to set up an isolated workspace.
|
||||
|
||||
[Check .worktrees/ - exists]
|
||||
[Verify .gitignore - contains .worktrees/]
|
||||
@@ -207,9 +205,9 @@ Ready to implement auth feature
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- skills/collaboration/brainstorming (Phase 4)
|
||||
- **brainstorming** (Phase 4) - REQUIRED when design is approved and implementation follows
|
||||
- Any skill needing isolated workspace
|
||||
|
||||
**Pairs with:**
|
||||
- skills/collaboration/finishing-a-development-branch (cleanup)
|
||||
- skills/collaboration/executing-plans (work happens here)
|
||||
- **finishing-a-development-branch** - REQUIRED for cleanup after work complete
|
||||
- **executing-plans** or **subagent-driven-development** - Work happens in this worktree
|
||||
101
skills/using-superpowers/SKILL.md
Normal file
101
skills/using-superpowers/SKILL.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: using-superpowers
|
||||
description: Use when starting any conversation - establishes mandatory workflows for finding and using skills, including using Skill tool before announcing usage, following brainstorming before coding, and creating TodoWrite todos for checklists
|
||||
---
|
||||
|
||||
<EXTREMELY-IMPORTANT>
|
||||
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST read the skill.
|
||||
|
||||
IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
|
||||
|
||||
This is not negotiable. This is not optional. You cannot rationalize your way out of this.
|
||||
</EXTREMELY-IMPORTANT>
|
||||
|
||||
# Getting Started with Skills
|
||||
|
||||
## MANDATORY FIRST RESPONSE PROTOCOL
|
||||
|
||||
Before responding to ANY user message, you MUST complete this checklist:
|
||||
|
||||
1. ☐ List available skills in your mind
|
||||
2. ☐ Ask yourself: "Does ANY skill match this request?"
|
||||
3. ☐ If yes → Use the Skill tool to read and run the skill file
|
||||
4. ☐ Announce which skill you're using
|
||||
5. ☐ Follow the skill exactly
|
||||
|
||||
**Responding WITHOUT completing this checklist = automatic failure.**
|
||||
|
||||
## Critical Rules
|
||||
|
||||
1. **Follow mandatory workflows.** Brainstorming before coding. Check for relevant skills before ANY task.
|
||||
|
||||
2. Execute skills with the Skill tool
|
||||
|
||||
## Common Rationalizations That Mean You're About To Fail
|
||||
|
||||
If you catch yourself thinking ANY of these thoughts, STOP. You are rationalizing. Check for and use the skill.
|
||||
|
||||
- "This is just a simple question" → WRONG. Questions are tasks. Check for skills.
|
||||
- "I can check git/files quickly" → WRONG. Files don't have conversation context. Check for skills.
|
||||
- "Let me gather information first" → WRONG. Skills tell you HOW to gather information. Check for skills.
|
||||
- "This doesn't need a formal skill" → WRONG. If a skill exists for it, use it.
|
||||
- "I remember this skill" → WRONG. Skills evolve. Run the current version.
|
||||
- "This doesn't count as a task" → WRONG. If you're taking action, it's a task. Check for skills.
|
||||
- "The skill is overkill for this" → WRONG. Skills exist because simple things become complex. Use it.
|
||||
- "I'll just do this one thing first" → WRONG. Check for skills BEFORE doing anything.
|
||||
|
||||
**Why:** Skills document proven techniques that save time and prevent mistakes. Not using available skills means repeating solved problems and making known errors.
|
||||
|
||||
If a skill for your task exists, you must use it or you will fail at your task.
|
||||
|
||||
## Skills with Checklists
|
||||
|
||||
If a skill has a checklist, YOU MUST create TodoWrite todos for EACH item.
|
||||
|
||||
**Don't:**
|
||||
- Work through checklist mentally
|
||||
- Skip creating todos "to save time"
|
||||
- Batch multiple items into one todo
|
||||
- Mark complete without doing them
|
||||
|
||||
**Why:** Checklists without TodoWrite tracking = steps get skipped. Every time. The overhead of TodoWrite is tiny compared to the cost of missing steps.
|
||||
|
||||
## Announcing Skill Usage
|
||||
|
||||
Before using a skill, announce that you are using it.
|
||||
"I'm using [Skill Name] to [what you're doing]."
|
||||
|
||||
**Examples:**
|
||||
- "I'm using the brainstorming skill to refine your idea into a design."
|
||||
- "I'm using the test-driven-development skill to implement this feature."
|
||||
|
||||
**Why:** Transparency helps your human partner understand your process and catch errors early. It also confirms you actually read the skill.
|
||||
|
||||
# About these skills
|
||||
|
||||
**Many skills contain rigid rules (TDD, debugging, verification).** Follow them exactly. Don't adapt away the discipline.
|
||||
|
||||
**Some skills are flexible patterns (architecture, naming).** Adapt core principles to your context.
|
||||
|
||||
The skill itself tells you which type it is.
|
||||
|
||||
## Instructions ≠ Permission to Skip Workflows
|
||||
|
||||
Your human partner's specific instructions describe WHAT to do, not HOW.
|
||||
|
||||
"Add X", "Fix Y" = the goal, NOT permission to skip brainstorming, TDD, or RED-GREEN-REFACTOR.
|
||||
|
||||
**Red flags:** "Instruction was specific" • "Seems simple" • "Workflow is overkill"
|
||||
|
||||
**Why:** Specific instructions mean clear requirements, which is when workflows matter MOST. Skipping process on "simple" tasks is how simple tasks become complex problems.
|
||||
|
||||
## Summary
|
||||
|
||||
**Starting any task:**
|
||||
1. If relevant skill exists → Use the skill
|
||||
3. Announce you're using it
|
||||
4. Follow what it says
|
||||
|
||||
**Skill has checklist?** TodoWrite for every item.
|
||||
|
||||
**Finding a relevant skill = mandatory to read and use it. Not optional.**
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Verification Before Completion
|
||||
description: Run verification commands and confirm output before claiming success
|
||||
when_to_use: Before claiming complete, fixed, working, passing, clean, ready, or done. Before expressing satisfaction with work. Before committing or creating PRs. When tempted to declare success. After code changes. When delegating to agents.
|
||||
version: 1.0.0
|
||||
languages: all
|
||||
name: verification-before-completion
|
||||
description: Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
|
||||
---
|
||||
|
||||
# Verification Before Completion
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
name: Writing Plans
|
||||
description: Create detailed implementation plans with bite-sized tasks for engineers with zero codebase context
|
||||
when_to_use: After brainstorming/design is complete. Before implementation begins. When delegating to another developer or session. When brainstorming skill hands off to planning.
|
||||
version: 2.0.0
|
||||
name: writing-plans
|
||||
description: Use when design is complete and you need detailed implementation tasks for engineers with zero codebase context - creates comprehensive implementation plans with exact file paths, complete code examples, and verification steps assuming engineer has minimal domain knowledge
|
||||
---
|
||||
|
||||
# Writing Plans
|
||||
@@ -13,7 +11,7 @@ Write comprehensive implementation plans assuming the engineer has zero context
|
||||
|
||||
Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
|
||||
|
||||
**Announce at start:** "I'm using the Writing Plans skill to create the implementation plan."
|
||||
**Announce at start:** "I'm using the writing-plans skill to create the implementation plan."
|
||||
|
||||
**Context:** This should be run in a dedicated worktree (created by brainstorming skill).
|
||||
|
||||
@@ -35,7 +33,7 @@ Assume they are a skilled developer, but know almost nothing about our toolset o
|
||||
```markdown
|
||||
# [Feature Name] Implementation Plan
|
||||
|
||||
> **For Claude:** Use `${CLAUDE_PLUGIN_ROOT}/skills/collaboration/executing-plans/SKILL.md` to implement this plan task-by-task.
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** [One sentence describing what this builds]
|
||||
|
||||
@@ -109,10 +107,10 @@ After saving the plan, offer execution choice:
|
||||
**Which approach?"**
|
||||
|
||||
**If Subagent-Driven chosen:**
|
||||
- Use skills/collaboration/subagent-driven-development
|
||||
- **REQUIRED SUB-SKILL:** Use superpowers:subagent-driven-development
|
||||
- Stay in this session
|
||||
- Fresh subagent per task + code review
|
||||
|
||||
**If Parallel Session chosen:**
|
||||
- Guide them to open new session in worktree
|
||||
- New session uses skills/collaboration/executing-plans
|
||||
- **REQUIRED SUB-SKILL:** New session uses superpowers:executing-plans
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
name: Writing Skills
|
||||
description: TDD for process documentation - test with subagents before writing, iterate until bulletproof
|
||||
when_to_use: When you discover a technique, pattern, or tool worth documenting for reuse. When editing existing skills. When asked to modify skill documentation. When you've written a skill and need to verify it works before deploying.
|
||||
version: 5.0.0
|
||||
languages: all
|
||||
name: writing-skills
|
||||
description: Use when creating new skills, editing existing skills, or verifying skills work before deployment - applies TDD to process documentation by testing with subagents before writing, iterating until bulletproof against rationalization
|
||||
---
|
||||
|
||||
# Writing Skills
|
||||
@@ -12,13 +9,15 @@ languages: all
|
||||
|
||||
**Writing skills IS Test-Driven Development applied to process documentation.**
|
||||
|
||||
**All personal skills are written to `~/.config/superpowers/skills/`** - this is your personal superpowers repository, separate from the core superpowers library.
|
||||
**Personal skills live in agent-specific directories (`~/.claude/skills` for Claude Code, `~/.codex/skills` for Codex)**
|
||||
|
||||
You write test cases (pressure scenarios with subagents), watch them fail (baseline behavior), write the skill (documentation), watch tests pass (agents comply), and refactor (close loopholes).
|
||||
|
||||
**Core principle:** If you didn't watch an agent fail without the skill, you don't know if the skill teaches the right thing.
|
||||
|
||||
See skills/testing/test-driven-development for the fundamental RED-GREEN-REFACTOR cycle. This skill adapts TDD to documentation.
|
||||
**REQUIRED BACKGROUND:** You MUST understand superpowers:test-driven-development before using this skill. That skill defines the fundamental RED-GREEN-REFACTOR cycle. This skill adapts TDD to documentation.
|
||||
|
||||
**Official guidance:** For Anthropic's official skill authoring best practices, see anthropic-best-practices.md. This document provides additional patterns and guidelines that complement the TDD-focused approach in this skill.
|
||||
|
||||
## What is a Skill?
|
||||
|
||||
@@ -71,16 +70,15 @@ API docs, syntax guides, tool documentation (office docs)
|
||||
|
||||
## Directory Structure
|
||||
|
||||
**All skills are written to `~/.config/superpowers/skills/`:**
|
||||
|
||||
```
|
||||
~/.config/superpowers/skills/
|
||||
skills/
|
||||
skill-name/
|
||||
SKILL.md # Main reference (required)
|
||||
supporting-file.* # Only if needed
|
||||
```
|
||||
|
||||
**Flat namespace** - all personal skills in one searchable location
|
||||
**Flat namespace** - all skills in one searchable namespace
|
||||
|
||||
**Separate files for:**
|
||||
1. **Heavy reference** (100+ lines) - API docs, comprehensive syntax
|
||||
@@ -93,14 +91,19 @@ API docs, syntax guides, tool documentation (office docs)
|
||||
|
||||
## SKILL.md Structure
|
||||
|
||||
**Frontmatter (YAML):**
|
||||
- Only two fields supported: `name` and `description`
|
||||
- Max 1024 characters total
|
||||
- `name`: Use letters, numbers, and hyphens only (no parentheses, special chars)
|
||||
- `description`: Third-person, includes BOTH what it does AND when to use it
|
||||
- Start with "Use when..." to focus on triggering conditions
|
||||
- Include specific symptoms, situations, and contexts
|
||||
- Keep under 500 characters if possible
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Human-Readable Name
|
||||
description: One-line summary of what this does
|
||||
when_to_use: Symptoms and situations when you need this (CSO-critical)
|
||||
version: 1.0.0
|
||||
languages: all | [typescript, python] | etc
|
||||
dependencies: (optional) Required tools/libraries
|
||||
name: Skill-Name-With-Hyphens
|
||||
description: Use when [specific triggering conditions and symptoms] - [what the skill does and how it helps, written in third person]
|
||||
---
|
||||
|
||||
# Skill Name
|
||||
@@ -122,7 +125,7 @@ Table or bullets for scanning common operations
|
||||
|
||||
## Implementation
|
||||
Inline code for simple patterns
|
||||
@link to file for heavy reference or reusable tools
|
||||
Link to file for heavy reference or reusable tools
|
||||
|
||||
## Common Mistakes
|
||||
What goes wrong + fixes
|
||||
@@ -131,21 +134,39 @@ What goes wrong + fixes
|
||||
Concrete results
|
||||
```
|
||||
|
||||
|
||||
## Claude Search Optimization (CSO)
|
||||
|
||||
**Critical for discovery:** Future Claude needs to FIND your skill
|
||||
|
||||
### 1. Rich when_to_use
|
||||
### 1. Rich Description Field
|
||||
|
||||
Include SYMPTOMS not just abstract use cases:
|
||||
**Purpose:** Claude reads description to decide which skills to load for a given task. Make it answer: "Should I read this skill right now?"
|
||||
|
||||
**Format:** Start with "Use when..." to focus on triggering conditions, then explain what it does
|
||||
|
||||
**Content:**
|
||||
- Use concrete triggers, symptoms, and situations that signal this skill applies
|
||||
- Describe the *problem* (race conditions, inconsistent behavior) not *language-specific symptoms* (setTimeout, sleep)
|
||||
- Keep triggers technology-agnostic unless the skill itself is technology-specific
|
||||
- If skill is technology-specific, make that explicit in the trigger
|
||||
- Write in third person (injected into system prompt)
|
||||
|
||||
```yaml
|
||||
# ❌ BAD: Too abstract
|
||||
when_to_use: For async testing
|
||||
# ❌ BAD: Too abstract, vague, doesn't include when to use
|
||||
description: For async testing
|
||||
|
||||
# ✅ GOOD: Symptoms and context
|
||||
when_to_use: When tests use setTimeout/sleep and are flaky, timing-dependent,
|
||||
pass locally but fail in CI, or timeout when run in parallel
|
||||
# ❌ BAD: First person
|
||||
description: I can help you with async tests when they're flaky
|
||||
|
||||
# ❌ BAD: Mentions technology but skill isn't specific to it
|
||||
description: Use when tests use setTimeout/sleep and are flaky
|
||||
|
||||
# ✅ GOOD: Starts with "Use when", describes problem, then what it does
|
||||
description: Use when tests have race conditions, timing dependencies, or pass/fail inconsistently - replaces arbitrary timeouts with condition polling for reliable async tests
|
||||
|
||||
# ✅ GOOD: Technology-specific skill with explicit trigger
|
||||
description: Use when using React Router and handling authentication redirects - provides patterns for protected routes and auth state management
|
||||
```
|
||||
|
||||
### 2. Keyword Coverage
|
||||
@@ -189,7 +210,7 @@ When searching, dispatch subagent with template...
|
||||
[20 lines of repeated instructions]
|
||||
|
||||
# ✅ GOOD: Reference other skill
|
||||
Always use subagents (50-100x context savings). See skills/getting-started for workflow.
|
||||
Always use subagents (50-100x context savings). REQUIRED: Use [other-skill-name] for workflow.
|
||||
```
|
||||
|
||||
**Compress examples:**
|
||||
@@ -227,15 +248,17 @@ wc -w skills/path/SKILL.md
|
||||
- `creating-skills`, `testing-skills`, `debugging-with-logs`
|
||||
- Active, describes the action you're taking
|
||||
|
||||
### 4. Content Repetition
|
||||
### 4. Cross-Referencing Other Skills
|
||||
|
||||
Mention key concepts multiple times:
|
||||
- In description
|
||||
- In when_to_use
|
||||
- In overview
|
||||
- In section headers
|
||||
**When writing documentation that references other skills:**
|
||||
|
||||
Grep hits from multiple places = easier discovery
|
||||
Use skill name only, with explicit requirement markers:
|
||||
- ✅ Good: `**REQUIRED SUB-SKILL:** Use superpowers:test-driven-development`
|
||||
- ✅ Good: `**REQUIRED BACKGROUND:** You MUST understand superpowers:systematic-debugging`
|
||||
- ❌ Bad: `See skills/testing/test-driven-development` (unclear if required)
|
||||
- ❌ Bad: `@skills/testing/test-driven-development/SKILL.md` (force-loads, burns context)
|
||||
|
||||
**Why no @ links:** `@` syntax force-loads files immediately, consuming 200k+ context before you need them.
|
||||
|
||||
## Flowchart Usage
|
||||
|
||||
@@ -334,7 +357,7 @@ Edit skill without testing? Same violation.
|
||||
- Don't "adapt" while running tests
|
||||
- Delete means delete
|
||||
|
||||
See skills/testing/test-driven-development for why this matters. Same principles apply to documentation.
|
||||
**REQUIRED BACKGROUND:** The superpowers:test-driven-development skill explains why this matters. Same principles apply to documentation.
|
||||
|
||||
## Testing All Skill Types
|
||||
|
||||
@@ -468,11 +491,10 @@ Make it easy for agents to self-check when rationalizing:
|
||||
|
||||
### Update CSO for Violation Symptoms
|
||||
|
||||
Add to when_to_use: symptoms of when you're ABOUT to violate the rule:
|
||||
Add to description: symptoms of when you're ABOUT to violate the rule:
|
||||
|
||||
```yaml
|
||||
when_to_use: Every feature and bugfix. When you wrote code before tests.
|
||||
When you're tempted to test after. When manually testing seems faster.
|
||||
description: use when implementing any feature or bugfix, before writing implementation code
|
||||
```
|
||||
|
||||
## RED-GREEN-REFACTOR for Skills
|
||||
@@ -498,7 +520,7 @@ Run same scenarios WITH skill. Agent should now comply.
|
||||
|
||||
Agent found new rationalization? Add explicit counter. Re-test until bulletproof.
|
||||
|
||||
**See skills/testing-skills-with-subagents for:**
|
||||
**REQUIRED SUB-SKILL:** Use superpowers:testing-skills-with-subagents for the complete testing methodology:
|
||||
- How to write pressure scenarios
|
||||
- Pressure types (time, sunk cost, authority, exhaustion)
|
||||
- Plugging holes systematically
|
||||
@@ -548,12 +570,14 @@ Deploying untested skills = deploying untested code. It's a violation of quality
|
||||
- [ ] Identify patterns in rationalizations/failures
|
||||
|
||||
**GREEN Phase - Write Minimal Skill:**
|
||||
- [ ] Name describes what you DO or core insight
|
||||
- [ ] YAML frontmatter with rich when_to_use (include symptoms!)
|
||||
- [ ] Name uses only letters, numbers, hyphens (no parentheses/special chars)
|
||||
- [ ] YAML frontmatter with only name and description (max 1024 chars)
|
||||
- [ ] Description starts with "Use when..." and includes specific triggers/symptoms
|
||||
- [ ] Description written in third person
|
||||
- [ ] Keywords throughout for search (errors, symptoms, tools)
|
||||
- [ ] Clear overview with core principle
|
||||
- [ ] Address specific baseline failures identified in RED
|
||||
- [ ] Code inline OR @link to separate file
|
||||
- [ ] Code inline OR link to separate file
|
||||
- [ ] One excellent example (not multi-language)
|
||||
- [ ] Run scenarios WITH skill - verify agents now comply
|
||||
|
||||
@@ -572,17 +596,15 @@ Deploying untested skills = deploying untested code. It's a violation of quality
|
||||
- [ ] Supporting files only for tools or heavy reference
|
||||
|
||||
**Deployment:**
|
||||
- [ ] Commit skill to git (in `~/.config/superpowers/`)
|
||||
- [ ] Push to GitHub (if remote configured)
|
||||
- [ ] Consider sharing via skills/meta/sharing-skills (if broadly useful)
|
||||
- [ ] Commit skill to git and push to your fork (if configured)
|
||||
- [ ] Consider contributing back via PR (if broadly useful)
|
||||
|
||||
## Discovery Workflow
|
||||
|
||||
How future Claude finds your skill:
|
||||
|
||||
1. **Encounters problem** ("tests are flaky")
|
||||
2. **Searches skills** using `find-skills` tool (checks personal then core)
|
||||
3. **Finds SKILL.md** (rich when_to_use matches)
|
||||
3. **Finds SKILL** (description matches)
|
||||
4. **Scans overview** (is this relevant?)
|
||||
5. **Reads patterns** (quick reference table)
|
||||
6. **Loads example** (only when implementing)
|
||||
1150
skills/writing-skills/anthropic-best-practices.md
Normal file
1150
skills/writing-skills/anthropic-best-practices.md
Normal file
File diff suppressed because it is too large
Load Diff
165
tests/opencode/run-tests.sh
Executable file
165
tests/opencode/run-tests.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# Main test runner for OpenCode plugin test suite
|
||||
# Runs all tests and reports results
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
echo "========================================"
|
||||
echo " OpenCode Plugin Test Suite"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Repository: $(cd ../.. && pwd)"
|
||||
echo "Test time: $(date)"
|
||||
echo ""
|
||||
|
||||
# Parse command line arguments
|
||||
RUN_INTEGRATION=false
|
||||
VERBOSE=false
|
||||
SPECIFIC_TEST=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--integration|-i)
|
||||
RUN_INTEGRATION=true
|
||||
shift
|
||||
;;
|
||||
--verbose|-v)
|
||||
VERBOSE=true
|
||||
shift
|
||||
;;
|
||||
--test|-t)
|
||||
SPECIFIC_TEST="$2"
|
||||
shift 2
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [options]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --integration, -i Run integration tests (requires OpenCode)"
|
||||
echo " --verbose, -v Show verbose output"
|
||||
echo " --test, -t NAME Run only the specified test"
|
||||
echo " --help, -h Show this help"
|
||||
echo ""
|
||||
echo "Tests:"
|
||||
echo " test-plugin-loading.sh Verify plugin installation and structure"
|
||||
echo " test-skills-core.sh Test skills-core.js library functions"
|
||||
echo " test-tools.sh Test use_skill and find_skills tools (integration)"
|
||||
echo " test-priority.sh Test skill priority resolution (integration)"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# List of tests to run (no external dependencies)
|
||||
tests=(
|
||||
"test-plugin-loading.sh"
|
||||
"test-skills-core.sh"
|
||||
)
|
||||
|
||||
# Integration tests (require OpenCode)
|
||||
integration_tests=(
|
||||
"test-tools.sh"
|
||||
"test-priority.sh"
|
||||
)
|
||||
|
||||
# Add integration tests if requested
|
||||
if [ "$RUN_INTEGRATION" = true ]; then
|
||||
tests+=("${integration_tests[@]}")
|
||||
fi
|
||||
|
||||
# Filter to specific test if requested
|
||||
if [ -n "$SPECIFIC_TEST" ]; then
|
||||
tests=("$SPECIFIC_TEST")
|
||||
fi
|
||||
|
||||
# Track results
|
||||
passed=0
|
||||
failed=0
|
||||
skipped=0
|
||||
|
||||
# Run each test
|
||||
for test in "${tests[@]}"; do
|
||||
echo "----------------------------------------"
|
||||
echo "Running: $test"
|
||||
echo "----------------------------------------"
|
||||
|
||||
test_path="$SCRIPT_DIR/$test"
|
||||
|
||||
if [ ! -f "$test_path" ]; then
|
||||
echo " [SKIP] Test file not found: $test"
|
||||
skipped=$((skipped + 1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ ! -x "$test_path" ]; then
|
||||
echo " Making $test executable..."
|
||||
chmod +x "$test_path"
|
||||
fi
|
||||
|
||||
start_time=$(date +%s)
|
||||
|
||||
if [ "$VERBOSE" = true ]; then
|
||||
if bash "$test_path"; then
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
echo ""
|
||||
echo " [PASS] $test (${duration}s)"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
echo ""
|
||||
echo " [FAIL] $test (${duration}s)"
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
else
|
||||
# Capture output for non-verbose mode
|
||||
if output=$(bash "$test_path" 2>&1); then
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
echo " [PASS] (${duration}s)"
|
||||
passed=$((passed + 1))
|
||||
else
|
||||
end_time=$(date +%s)
|
||||
duration=$((end_time - start_time))
|
||||
echo " [FAIL] (${duration}s)"
|
||||
echo ""
|
||||
echo " Output:"
|
||||
echo "$output" | sed 's/^/ /'
|
||||
failed=$((failed + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Print summary
|
||||
echo "========================================"
|
||||
echo " Test Results Summary"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo " Passed: $passed"
|
||||
echo " Failed: $failed"
|
||||
echo " Skipped: $skipped"
|
||||
echo ""
|
||||
|
||||
if [ "$RUN_INTEGRATION" = false ] && [ ${#integration_tests[@]} -gt 0 ]; then
|
||||
echo "Note: Integration tests were not run."
|
||||
echo "Use --integration flag to run tests that require OpenCode."
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [ $failed -gt 0 ]; then
|
||||
echo "STATUS: FAILED"
|
||||
exit 1
|
||||
else
|
||||
echo "STATUS: PASSED"
|
||||
exit 0
|
||||
fi
|
||||
73
tests/opencode/setup.sh
Executable file
73
tests/opencode/setup.sh
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env bash
|
||||
# Setup script for OpenCode plugin tests
|
||||
# Creates an isolated test environment with proper plugin installation
|
||||
set -euo pipefail
|
||||
|
||||
# Get the repository root (two levels up from tests/opencode/)
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
|
||||
# Create temp home directory for isolation
|
||||
export TEST_HOME=$(mktemp -d)
|
||||
export HOME="$TEST_HOME"
|
||||
export XDG_CONFIG_HOME="$TEST_HOME/.config"
|
||||
export OPENCODE_CONFIG_DIR="$TEST_HOME/.config/opencode"
|
||||
|
||||
# Install plugin to test location
|
||||
mkdir -p "$HOME/.config/opencode/superpowers"
|
||||
cp -r "$REPO_ROOT/lib" "$HOME/.config/opencode/superpowers/"
|
||||
cp -r "$REPO_ROOT/skills" "$HOME/.config/opencode/superpowers/"
|
||||
|
||||
# Copy plugin directory
|
||||
mkdir -p "$HOME/.config/opencode/superpowers/.opencode/plugin"
|
||||
cp "$REPO_ROOT/.opencode/plugin/superpowers.js" "$HOME/.config/opencode/superpowers/.opencode/plugin/"
|
||||
|
||||
# Register plugin via symlink
|
||||
mkdir -p "$HOME/.config/opencode/plugin"
|
||||
ln -sf "$HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js" \
|
||||
"$HOME/.config/opencode/plugin/superpowers.js"
|
||||
|
||||
# Create test skills in different locations for testing
|
||||
|
||||
# Personal test skill
|
||||
mkdir -p "$HOME/.config/opencode/skills/personal-test"
|
||||
cat > "$HOME/.config/opencode/skills/personal-test/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: personal-test
|
||||
description: Test personal skill for verification
|
||||
---
|
||||
# Personal Test Skill
|
||||
|
||||
This is a personal skill used for testing.
|
||||
|
||||
PERSONAL_SKILL_MARKER_12345
|
||||
EOF
|
||||
|
||||
# Create a project directory for project-level skill tests
|
||||
mkdir -p "$TEST_HOME/test-project/.opencode/skills/project-test"
|
||||
cat > "$TEST_HOME/test-project/.opencode/skills/project-test/SKILL.md" <<'EOF'
|
||||
---
|
||||
name: project-test
|
||||
description: Test project skill for verification
|
||||
---
|
||||
# Project Test Skill
|
||||
|
||||
This is a project skill used for testing.
|
||||
|
||||
PROJECT_SKILL_MARKER_67890
|
||||
EOF
|
||||
|
||||
echo "Setup complete: $TEST_HOME"
|
||||
echo "Plugin installed to: $HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js"
|
||||
echo "Plugin registered at: $HOME/.config/opencode/plugin/superpowers.js"
|
||||
echo "Test project at: $TEST_HOME/test-project"
|
||||
|
||||
# Helper function for cleanup (call from tests or trap)
|
||||
cleanup_test_env() {
|
||||
if [ -n "${TEST_HOME:-}" ] && [ -d "$TEST_HOME" ]; then
|
||||
rm -rf "$TEST_HOME"
|
||||
fi
|
||||
}
|
||||
|
||||
# Export for use in tests
|
||||
export -f cleanup_test_env
|
||||
export REPO_ROOT
|
||||
81
tests/opencode/test-plugin-loading.sh
Executable file
81
tests/opencode/test-plugin-loading.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env bash
|
||||
# Test: Plugin Loading
|
||||
# Verifies that the superpowers plugin loads correctly in OpenCode
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== Test: Plugin Loading ==="
|
||||
|
||||
# Source setup to create isolated environment
|
||||
source "$SCRIPT_DIR/setup.sh"
|
||||
|
||||
# Trap to cleanup on exit
|
||||
trap cleanup_test_env EXIT
|
||||
|
||||
# Test 1: Verify plugin file exists and is registered
|
||||
echo "Test 1: Checking plugin registration..."
|
||||
if [ -L "$HOME/.config/opencode/plugin/superpowers.js" ]; then
|
||||
echo " [PASS] Plugin symlink exists"
|
||||
else
|
||||
echo " [FAIL] Plugin symlink not found at $HOME/.config/opencode/plugin/superpowers.js"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify symlink target exists
|
||||
if [ -f "$(readlink -f "$HOME/.config/opencode/plugin/superpowers.js")" ]; then
|
||||
echo " [PASS] Plugin symlink target exists"
|
||||
else
|
||||
echo " [FAIL] Plugin symlink target does not exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 2: Verify lib/skills-core.js is in place
|
||||
echo "Test 2: Checking skills-core.js..."
|
||||
if [ -f "$HOME/.config/opencode/superpowers/lib/skills-core.js" ]; then
|
||||
echo " [PASS] skills-core.js exists"
|
||||
else
|
||||
echo " [FAIL] skills-core.js not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 3: Verify skills directory is populated
|
||||
echo "Test 3: Checking skills directory..."
|
||||
skill_count=$(find "$HOME/.config/opencode/superpowers/skills" -name "SKILL.md" | wc -l)
|
||||
if [ "$skill_count" -gt 0 ]; then
|
||||
echo " [PASS] Found $skill_count skills installed"
|
||||
else
|
||||
echo " [FAIL] No skills found in installed location"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 4: Check using-superpowers skill exists (critical for bootstrap)
|
||||
echo "Test 4: Checking using-superpowers skill (required for bootstrap)..."
|
||||
if [ -f "$HOME/.config/opencode/superpowers/skills/using-superpowers/SKILL.md" ]; then
|
||||
echo " [PASS] using-superpowers skill exists"
|
||||
else
|
||||
echo " [FAIL] using-superpowers skill not found (required for bootstrap)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 5: Verify plugin JavaScript syntax (basic check)
|
||||
echo "Test 5: Checking plugin JavaScript syntax..."
|
||||
plugin_file="$HOME/.config/opencode/superpowers/.opencode/plugin/superpowers.js"
|
||||
if node --check "$plugin_file" 2>/dev/null; then
|
||||
echo " [PASS] Plugin JavaScript syntax is valid"
|
||||
else
|
||||
echo " [FAIL] Plugin has JavaScript syntax errors"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 6: Verify personal test skill was created
|
||||
echo "Test 6: Checking test fixtures..."
|
||||
if [ -f "$HOME/.config/opencode/skills/personal-test/SKILL.md" ]; then
|
||||
echo " [PASS] Personal test skill fixture created"
|
||||
else
|
||||
echo " [FAIL] Personal test skill fixture not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== All plugin loading tests passed ==="
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user