promptgraph-mcp 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -159
- package/chunker.js +17 -0
- package/config.js +51 -51
- package/db.js +40 -32
- package/embedder.js +32 -32
- package/github-import.js +48 -0
- package/index.js +164 -137
- package/indexer.js +31 -32
- package/package.json +42 -42
- package/parser.js +47 -47
- package/pg-hook.js +46 -0
- package/platform.js +98 -0
- package/search.js +21 -13
- package/watcher.js +50 -50
package/README.md
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
# PromptGraph
|
|
2
|
-
|
|
3
|
-
Semantic skill router for Claude Code. Instead of loading all your skills into context on every request, PromptGraph indexes them with vector embeddings and loads only the relevant one on demand.
|
|
4
|
-
|
|
5
|
-
## The Problem
|
|
6
|
-
|
|
7
|
-
Claude Code loads all `.md` files from `~/.claude/commands/` into the system prompt on every session. With 40+ skills, that's **20,000–50,000 tokens wasted per conversation** — before you've even said hello.
|
|
8
|
-
|
|
9
|
-
## The Solution
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
~/.claude/commands/
|
|
13
|
-
pg.md ← one tiny router skill (~150 tokens)
|
|
14
|
-
|
|
15
|
-
~/.claude/skills-store/
|
|
16
|
-
game-audit.md
|
|
17
|
-
chain.md
|
|
18
|
-
hunt-sqli.md
|
|
19
|
-
... ← 40+ skills, NOT loaded into context
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
When you ask Claude a question, it calls `pg_search("your task")` → finds the right skill via vector search → reads only that file. **One skill loaded instead of forty.**
|
|
23
|
-
|
|
24
|
-
## Features
|
|
25
|
-
|
|
26
|
-
- **Vector search** via `fastembed` (`BGE-Small-EN`, 23MB, runs locally, no API needed)
|
|
27
|
-
- **Semantic matching** — Russian query finds English skill, synonyms work
|
|
28
|
-
- **Auto-reindex** via file watcher when skills change
|
|
29
|
-
- **Graph edges** — tracks which skills call other skills
|
|
30
|
-
- **MCP server** — integrates directly into Claude Code and Claude Desktop
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
|
|
34
|
-
### Via npx (recommended)
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
npx promptgraph-mcp init
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### From source
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
git clone https://github.com/NeiP4n/promptgraph
|
|
44
|
-
cd promptgraph
|
|
45
|
-
npm install
|
|
46
|
-
npm link
|
|
47
|
-
promptgraph init
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`init` will:
|
|
51
|
-
1. Ask for extra skill directories (optional)
|
|
52
|
-
2. Download the embedding model (~23MB, one time)
|
|
53
|
-
3. Index all your skills
|
|
54
|
-
4. Print the config snippet to add to `settings.json`
|
|
55
|
-
|
|
56
|
-
## Setup
|
|
57
|
-
|
|
58
|
-
### Claude Code (`~/.claude/settings.json`)
|
|
59
|
-
|
|
60
|
-
```json
|
|
61
|
-
{
|
|
62
|
-
"mcpServers": {
|
|
63
|
-
"promptgraph": {
|
|
64
|
-
"command": "npx",
|
|
65
|
-
"args": ["promptgraph-mcp"]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Claude Desktop
|
|
72
|
-
|
|
73
|
-
Add the same block to `claude_desktop_config.json`.
|
|
74
|
-
|
|
75
|
-
### Router skill (`~/.claude/commands/pg.md`)
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
name: pg
|
|
80
|
-
description: PromptGraph router — finds and loads the right skill for any task
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
# PromptGraph Router
|
|
84
|
-
|
|
85
|
-
You have access to a semantic skill index via the `promptgraph` MCP server.
|
|
86
|
-
|
|
87
|
-
## How to handle any task
|
|
88
|
-
|
|
89
|
-
1. Call `pg_search` with the user's task as query (in English)
|
|
90
|
-
2. Pick the top result with score > 0.6
|
|
91
|
-
3. Read the skill file at the returned `path`
|
|
92
|
-
4. Execute that skill's instructions
|
|
93
|
-
|
|
94
|
-
## If no good match (score < 0.6)
|
|
95
|
-
|
|
96
|
-
Handle the task directly without a skill.
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Move all your other skills from `commands/` to `skills-store/`:
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
mkdir -p ~/.claude/skills-store
|
|
103
|
-
mv ~/.claude/commands/*.md ~/.claude/skills-store/
|
|
104
|
-
mv ~/.claude/skills-store/pg.md ~/.claude/commands/
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Commands
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
promptgraph init # First-time setup (interactive)
|
|
111
|
-
promptgraph reindex # Re-index all skills
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
## MCP Tools
|
|
115
|
-
|
|
116
|
-
| Tool | Description |
|
|
117
|
-
|---|---|
|
|
118
|
-
| `pg_search` | Semantic search by task description |
|
|
119
|
-
| `pg_list` | List all indexed skills |
|
|
120
|
-
| `pg_context` | Full details for a skill |
|
|
121
|
-
| `pg_callers` | Which skills reference this one |
|
|
122
|
-
| `pg_callees` | Which skills this one references |
|
|
123
|
-
| `pg_impact` | What breaks if this skill changes |
|
|
124
|
-
|
|
125
|
-
## Token Savings
|
|
126
|
-
|
|
127
|
-
| | Before | After |
|
|
128
|
-
|---|---|---|
|
|
129
|
-
| Skills in context | All 40+ | 1 (router) |
|
|
130
|
-
| Tokens per session | ~20,000–50,000 | ~150 + 1 skill |
|
|
131
|
-
| Scales to | ~50 skills | 5,000+ skills |
|
|
132
|
-
|
|
133
|
-
## File Structure
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
promptgraph/
|
|
137
|
-
index.js ← MCP server + CLI
|
|
138
|
-
config.js ← Config management
|
|
139
|
-
db.js ← SQLite setup
|
|
140
|
-
embedder.js ← fastembed wrapper
|
|
141
|
-
indexer.js ← Skill indexer
|
|
142
|
-
parser.js ← .md parser
|
|
143
|
-
search.js ← Vector search + graph queries
|
|
144
|
-
watcher.js ← File watcher (auto-reindex)
|
|
145
|
-
|
|
146
|
-
~/.claude/.promptgraph/
|
|
147
|
-
promptgraph.db ← SQLite index
|
|
148
|
-
model-cache/ ← Embedding model cache
|
|
149
|
-
config.json ← Skill directory config
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Requirements
|
|
153
|
-
|
|
154
|
-
- Node.js 18+
|
|
155
|
-
- Claude Code or Claude Desktop
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
*Generated with [Claude](https://claude.ai) by Anthropic*
|
|
1
|
+
# PromptGraph
|
|
2
|
+
|
|
3
|
+
Semantic skill router for Claude Code. Instead of loading all your skills into context on every request, PromptGraph indexes them with vector embeddings and loads only the relevant one on demand.
|
|
4
|
+
|
|
5
|
+
## The Problem
|
|
6
|
+
|
|
7
|
+
Claude Code loads all `.md` files from `~/.claude/commands/` into the system prompt on every session. With 40+ skills, that's **20,000–50,000 tokens wasted per conversation** — before you've even said hello.
|
|
8
|
+
|
|
9
|
+
## The Solution
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
~/.claude/commands/
|
|
13
|
+
pg.md ← one tiny router skill (~150 tokens)
|
|
14
|
+
|
|
15
|
+
~/.claude/skills-store/
|
|
16
|
+
game-audit.md
|
|
17
|
+
chain.md
|
|
18
|
+
hunt-sqli.md
|
|
19
|
+
... ← 40+ skills, NOT loaded into context
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
When you ask Claude a question, it calls `pg_search("your task")` → finds the right skill via vector search → reads only that file. **One skill loaded instead of forty.**
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Vector search** via `fastembed` (`BGE-Small-EN`, 23MB, runs locally, no API needed)
|
|
27
|
+
- **Semantic matching** — Russian query finds English skill, synonyms work
|
|
28
|
+
- **Auto-reindex** via file watcher when skills change
|
|
29
|
+
- **Graph edges** — tracks which skills call other skills
|
|
30
|
+
- **MCP server** — integrates directly into Claude Code and Claude Desktop
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Via npx (recommended)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx promptgraph-mcp init
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### From source
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/NeiP4n/promptgraph
|
|
44
|
+
cd promptgraph
|
|
45
|
+
npm install
|
|
46
|
+
npm link
|
|
47
|
+
promptgraph-mcp init
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`init` will:
|
|
51
|
+
1. Ask for extra skill directories (optional)
|
|
52
|
+
2. Download the embedding model (~23MB, one time)
|
|
53
|
+
3. Index all your skills
|
|
54
|
+
4. Print the config snippet to add to `settings.json`
|
|
55
|
+
|
|
56
|
+
## Setup
|
|
57
|
+
|
|
58
|
+
### Claude Code (`~/.claude/settings.json`)
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"promptgraph": {
|
|
64
|
+
"command": "npx",
|
|
65
|
+
"args": ["promptgraph-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Claude Desktop
|
|
72
|
+
|
|
73
|
+
Add the same block to `claude_desktop_config.json`.
|
|
74
|
+
|
|
75
|
+
### Router skill (`~/.claude/commands/pg.md`)
|
|
76
|
+
|
|
77
|
+
```markdown
|
|
78
|
+
---
|
|
79
|
+
name: pg
|
|
80
|
+
description: PromptGraph router — finds and loads the right skill for any task
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
# PromptGraph Router
|
|
84
|
+
|
|
85
|
+
You have access to a semantic skill index via the `promptgraph` MCP server.
|
|
86
|
+
|
|
87
|
+
## How to handle any task
|
|
88
|
+
|
|
89
|
+
1. Call `pg_search` with the user's task as query (in English)
|
|
90
|
+
2. Pick the top result with score > 0.6
|
|
91
|
+
3. Read the skill file at the returned `path`
|
|
92
|
+
4. Execute that skill's instructions
|
|
93
|
+
|
|
94
|
+
## If no good match (score < 0.6)
|
|
95
|
+
|
|
96
|
+
Handle the task directly without a skill.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Move all your other skills from `commands/` to `skills-store/`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
mkdir -p ~/.claude/skills-store
|
|
103
|
+
mv ~/.claude/commands/*.md ~/.claude/skills-store/
|
|
104
|
+
mv ~/.claude/skills-store/pg.md ~/.claude/commands/
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Commands
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
promptgraph-mcp init # First-time setup (interactive)
|
|
111
|
+
promptgraph-mcp reindex # Re-index all skills
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## MCP Tools
|
|
115
|
+
|
|
116
|
+
| Tool | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `pg_search` | Semantic search by task description |
|
|
119
|
+
| `pg_list` | List all indexed skills |
|
|
120
|
+
| `pg_context` | Full details for a skill |
|
|
121
|
+
| `pg_callers` | Which skills reference this one |
|
|
122
|
+
| `pg_callees` | Which skills this one references |
|
|
123
|
+
| `pg_impact` | What breaks if this skill changes |
|
|
124
|
+
|
|
125
|
+
## Token Savings
|
|
126
|
+
|
|
127
|
+
| | Before | After |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| Skills in context | All 40+ | 1 (router) |
|
|
130
|
+
| Tokens per session | ~20,000–50,000 | ~150 + 1 skill |
|
|
131
|
+
| Scales to | ~50 skills | 5,000+ skills |
|
|
132
|
+
|
|
133
|
+
## File Structure
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
promptgraph/
|
|
137
|
+
index.js ← MCP server + CLI
|
|
138
|
+
config.js ← Config management
|
|
139
|
+
db.js ← SQLite setup
|
|
140
|
+
embedder.js ← fastembed wrapper
|
|
141
|
+
indexer.js ← Skill indexer
|
|
142
|
+
parser.js ← .md parser
|
|
143
|
+
search.js ← Vector search + graph queries
|
|
144
|
+
watcher.js ← File watcher (auto-reindex)
|
|
145
|
+
|
|
146
|
+
~/.claude/.promptgraph/
|
|
147
|
+
promptgraph.db ← SQLite index
|
|
148
|
+
model-cache/ ← Embedding model cache
|
|
149
|
+
config.json ← Skill directory config
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Requirements
|
|
153
|
+
|
|
154
|
+
- Node.js 18+
|
|
155
|
+
- Claude Code or Claude Desktop
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
*Generated with [Claude](https://claude.ai) by Anthropic*
|
package/chunker.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const CHUNK_SIZE = 400;
|
|
2
|
+
const CHUNK_OVERLAP = 80;
|
|
3
|
+
|
|
4
|
+
export function chunkText(text) {
|
|
5
|
+
const words = text.split(/\s+/);
|
|
6
|
+
const chunks = [];
|
|
7
|
+
let i = 0;
|
|
8
|
+
|
|
9
|
+
while (i < words.length) {
|
|
10
|
+
const chunk = words.slice(i, i + CHUNK_SIZE).join(' ');
|
|
11
|
+
chunks.push(chunk);
|
|
12
|
+
if (i + CHUNK_SIZE >= words.length) break;
|
|
13
|
+
i += CHUNK_SIZE - CHUNK_OVERLAP;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return chunks.length > 0 ? chunks : [text];
|
|
17
|
+
}
|
package/config.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import readline from 'readline';
|
|
5
|
-
|
|
6
|
-
const CONFIG_PATH = path.join(os.homedir(), '.claude', '.promptgraph', 'config.json');
|
|
7
|
-
|
|
8
|
-
const DEFAULTS = {
|
|
9
|
-
sources: [
|
|
10
|
-
{ dir: path.join(os.homedir(), '.claude', 'skills-store'), source: 'skills-store' },
|
|
11
|
-
{ dir: path.join(os.homedir(), '.claude', 'skills'), source: 'skills' },
|
|
12
|
-
{ dir: path.join(os.homedir(), '.claude', 'commands'), source: 'commands' },
|
|
13
|
-
],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export function loadConfig() {
|
|
17
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
18
|
-
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
19
|
-
}
|
|
20
|
-
return DEFAULTS;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function saveConfig(config) {
|
|
24
|
-
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
25
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function promptConfig() {
|
|
29
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
-
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
31
|
-
|
|
32
|
-
console.log('\n=== PromptGraph Setup ===\n');
|
|
33
|
-
console.log('Default skill directories:');
|
|
34
|
-
DEFAULTS.sources.forEach((s, i) => console.log(` ${i + 1}. ${s.dir}`));
|
|
35
|
-
|
|
36
|
-
const extra = await ask('\nAdd extra skill directories? (comma-separated paths, or press Enter to skip): ');
|
|
37
|
-
rl.close();
|
|
38
|
-
|
|
39
|
-
const config = { ...DEFAULTS };
|
|
40
|
-
|
|
41
|
-
if (extra.trim()) {
|
|
42
|
-
const extraDirs = extra.split(',').map(d => d.trim()).filter(Boolean);
|
|
43
|
-
for (const dir of extraDirs) {
|
|
44
|
-
config.sources.push({ dir, source: 'custom' });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
saveConfig(config);
|
|
49
|
-
console.log(`\nConfig saved to ${CONFIG_PATH}`);
|
|
50
|
-
return config;
|
|
51
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
|
|
6
|
+
const CONFIG_PATH = path.join(os.homedir(), '.claude', '.promptgraph', 'config.json');
|
|
7
|
+
|
|
8
|
+
const DEFAULTS = {
|
|
9
|
+
sources: [
|
|
10
|
+
{ dir: path.join(os.homedir(), '.claude', 'skills-store'), source: 'skills-store' },
|
|
11
|
+
{ dir: path.join(os.homedir(), '.claude', 'skills'), source: 'skills' },
|
|
12
|
+
{ dir: path.join(os.homedir(), '.claude', 'commands'), source: 'commands' },
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function loadConfig() {
|
|
17
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
18
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
return DEFAULTS;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function saveConfig(config) {
|
|
24
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
25
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function promptConfig() {
|
|
29
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
30
|
+
const ask = (q) => new Promise(r => rl.question(q, r));
|
|
31
|
+
|
|
32
|
+
console.log('\n=== PromptGraph Setup ===\n');
|
|
33
|
+
console.log('Default skill directories:');
|
|
34
|
+
DEFAULTS.sources.forEach((s, i) => console.log(` ${i + 1}. ${s.dir}`));
|
|
35
|
+
|
|
36
|
+
const extra = await ask('\nAdd extra skill directories? (comma-separated paths, or press Enter to skip): ');
|
|
37
|
+
rl.close();
|
|
38
|
+
|
|
39
|
+
const config = { ...DEFAULTS };
|
|
40
|
+
|
|
41
|
+
if (extra.trim()) {
|
|
42
|
+
const extraDirs = extra.split(',').map(d => d.trim()).filter(Boolean);
|
|
43
|
+
for (const dir of extraDirs) {
|
|
44
|
+
config.sources.push({ dir, source: 'custom' });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
saveConfig(config);
|
|
49
|
+
console.log(`\nConfig saved to ${CONFIG_PATH}`);
|
|
50
|
+
return config;
|
|
51
|
+
}
|
package/db.js
CHANGED
|
@@ -1,32 +1,40 @@
|
|
|
1
|
-
import Database from 'better-sqlite3';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
const DB_PATH = path.join(os.homedir(), '.claude', '.promptgraph', 'promptgraph.db');
|
|
7
|
-
|
|
8
|
-
export function getDb() {
|
|
9
|
-
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
10
|
-
const db = new Database(DB_PATH);
|
|
11
|
-
db.pragma('journal_mode = WAL');
|
|
12
|
-
|
|
13
|
-
db.exec(`
|
|
14
|
-
CREATE TABLE IF NOT EXISTS skills (
|
|
15
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
-
name TEXT UNIQUE NOT NULL,
|
|
17
|
-
description TEXT,
|
|
18
|
-
path TEXT NOT NULL,
|
|
19
|
-
source TEXT NOT NULL,
|
|
20
|
-
content TEXT NOT NULL
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
const DB_PATH = path.join(os.homedir(), '.claude', '.promptgraph', 'promptgraph.db');
|
|
7
|
+
|
|
8
|
+
export function getDb() {
|
|
9
|
+
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
10
|
+
const db = new Database(DB_PATH);
|
|
11
|
+
db.pragma('journal_mode = WAL');
|
|
12
|
+
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS skills (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
name TEXT UNIQUE NOT NULL,
|
|
17
|
+
description TEXT,
|
|
18
|
+
path TEXT NOT NULL,
|
|
19
|
+
source TEXT NOT NULL,
|
|
20
|
+
content TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE TABLE IF NOT EXISTS chunks (
|
|
24
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
25
|
+
skill_name TEXT NOT NULL,
|
|
26
|
+
chunk_index INTEGER NOT NULL,
|
|
27
|
+
text TEXT NOT NULL,
|
|
28
|
+
embedding TEXT NOT NULL,
|
|
29
|
+
UNIQUE(skill_name, chunk_index)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
33
|
+
from_skill TEXT NOT NULL,
|
|
34
|
+
to_skill TEXT NOT NULL,
|
|
35
|
+
PRIMARY KEY (from_skill, to_skill)
|
|
36
|
+
);
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
return db;
|
|
40
|
+
}
|
package/embedder.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { EmbeddingModel, FlagEmbedding } from 'fastembed';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
|
-
|
|
5
|
-
const CACHE_DIR = path.join(os.homedir(), '.claude', '.promptgraph', 'model-cache');
|
|
6
|
-
|
|
7
|
-
let model = null;
|
|
8
|
-
|
|
9
|
-
async function getModel() {
|
|
10
|
-
if (!model) {
|
|
11
|
-
model = await FlagEmbedding.init({
|
|
12
|
-
model: EmbeddingModel.BGESmallENV15,
|
|
13
|
-
cacheDir: CACHE_DIR,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return model;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function embed(text) {
|
|
20
|
-
const m = await getModel();
|
|
21
|
-
const results = [];
|
|
22
|
-
for await (const batch of m.embed([text])) {
|
|
23
|
-
results.push(...batch);
|
|
24
|
-
}
|
|
25
|
-
return Array.from(results[0]);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function cosineSimilarity(a, b) {
|
|
29
|
-
let dot = 0;
|
|
30
|
-
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
31
|
-
return dot;
|
|
32
|
-
}
|
|
1
|
+
import { EmbeddingModel, FlagEmbedding } from 'fastembed';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const CACHE_DIR = path.join(os.homedir(), '.claude', '.promptgraph', 'model-cache');
|
|
6
|
+
|
|
7
|
+
let model = null;
|
|
8
|
+
|
|
9
|
+
async function getModel() {
|
|
10
|
+
if (!model) {
|
|
11
|
+
model = await FlagEmbedding.init({
|
|
12
|
+
model: EmbeddingModel.BGESmallENV15,
|
|
13
|
+
cacheDir: CACHE_DIR,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return model;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function embed(text) {
|
|
20
|
+
const m = await getModel();
|
|
21
|
+
const results = [];
|
|
22
|
+
for await (const batch of m.embed([text])) {
|
|
23
|
+
results.push(...batch);
|
|
24
|
+
}
|
|
25
|
+
return Array.from(results[0]);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function cosineSimilarity(a, b) {
|
|
29
|
+
let dot = 0;
|
|
30
|
+
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
31
|
+
return dot;
|
|
32
|
+
}
|
package/github-import.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { indexAll } from './indexer.js';
|
|
6
|
+
import { loadConfig, saveConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
const SKILLS_DIR = path.join(os.homedir(), '.claude', 'skills-store');
|
|
9
|
+
|
|
10
|
+
export async function importFromGitHub(repoUrl) {
|
|
11
|
+
if (!repoUrl) {
|
|
12
|
+
console.error('Usage: promptgraph-mcp import <github-url-or-owner/repo>');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const url = repoUrl.startsWith('http') ? repoUrl : `https://github.com/${repoUrl}`;
|
|
17
|
+
const repoName = url.split('/').slice(-2).join('-').replace('.git', '');
|
|
18
|
+
const dest = path.join(SKILLS_DIR, 'github', repoName);
|
|
19
|
+
|
|
20
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
21
|
+
|
|
22
|
+
if (fs.existsSync(dest)) {
|
|
23
|
+
console.log(`Updating ${repoName}...`);
|
|
24
|
+
execSync(`git -C "${dest}" pull --depth=1`, { stdio: 'inherit' });
|
|
25
|
+
} else {
|
|
26
|
+
console.log(`Cloning ${url}...`);
|
|
27
|
+
execSync(`git clone --depth=1 ${url} "${dest}"`, { stdio: 'inherit' });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mdCount = execSync(`find "${dest}" -name "*.md" | wc -l`).toString().trim();
|
|
31
|
+
console.log(`Found ${mdCount} .md files`);
|
|
32
|
+
|
|
33
|
+
if (parseInt(mdCount) < 2) {
|
|
34
|
+
console.warn('Warning: repo has fewer than 2 .md files — may be empty');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = loadConfig();
|
|
38
|
+
const githubDir = path.join(SKILLS_DIR, 'github');
|
|
39
|
+
if (!config.sources.find(s => s.dir === githubDir)) {
|
|
40
|
+
config.sources.push({ dir: githubDir, source: 'github' });
|
|
41
|
+
saveConfig(config);
|
|
42
|
+
console.log('Added github dir to config');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('\nReindexing...');
|
|
46
|
+
await indexAll();
|
|
47
|
+
console.log(`Done! Imported from ${repoName}`);
|
|
48
|
+
}
|