promptgraph-mcp 1.0.2 → 1.0.3

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 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/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,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
- embedding TEXT
22
- );
23
-
24
- CREATE TABLE IF NOT EXISTS edges (
25
- from_skill TEXT NOT NULL,
26
- to_skill TEXT NOT NULL,
27
- PRIMARY KEY (from_skill, to_skill)
28
- );
29
- `);
30
-
31
- return db;
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
+ embedding TEXT
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS edges (
25
+ from_skill TEXT NOT NULL,
26
+ to_skill TEXT NOT NULL,
27
+ PRIMARY KEY (from_skill, to_skill)
28
+ );
29
+ `);
30
+
31
+ return db;
32
+ }
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
+ }