promptgraph-mcp 1.7.0 → 1.8.1
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 +186 -186
- package/ann.js +61 -61
- package/chunker.js +26 -26
- package/cli.js +115 -115
- package/db.js +86 -86
- package/doctor.js +48 -48
- package/embedder.js +44 -44
- package/github-import.js +52 -52
- package/index.js +118 -9
- package/indexer.js +199 -199
- package/marketplace.js +23 -0
- package/package.json +2 -1
- package/parser.js +45 -45
- package/pg-hook.js +70 -70
- package/platform.js +120 -120
- package/search.js +120 -120
- package/validator.js +13 -5
- package/watcher.js +84 -84
package/README.md
CHANGED
|
@@ -1,186 +1,186 @@
|
|
|
1
|
-
# PromptGraph
|
|
2
|
-
|
|
3
|
-
**Stop burning 50,000 tokens on skills you won't use.**
|
|
4
|
-
|
|
5
|
-
PromptGraph is an MCP server that gives Claude Code a semantic skill index — vector search, skill graph, and a community marketplace. Instead of cramming every `.md` into your system prompt, Claude finds and loads only the one skill it needs.
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/promptgraph-mcp)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Why
|
|
13
|
-
|
|
14
|
-
Claude Code loads skill metadata from `~/.claude/commands/` each session. With 40+ skills that's thousands of tokens in routing overhead before you say a word. More importantly, when Claude loads and executes a full skill file (typically 1,000–5,000 tokens each), the cost multiplies fast.
|
|
15
|
-
|
|
16
|
-
PromptGraph replaces that with one tiny router skill (`~150 tokens`) and a local vector index. Claude calls `pg_search` → gets the right skill path + a content snippet → reads only that file when needed.
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
Before: route across all 40 skills → 40 × read overhead
|
|
20
|
-
After: 1 pg_search call + snippet → read only what you need
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Features
|
|
26
|
-
|
|
27
|
-
- 🔍 **Semantic search** — finds skills by meaning, not just keywords (HNSW, O(log N))
|
|
28
|
-
- 📦 **Marketplace** — browse and install community skills with one command
|
|
29
|
-
- 🧩 **Skill bundles** — install curated packs (e.g. `engineering-essentials`)
|
|
30
|
-
- 🔗 **Dependency graph** — tracks which skills call other skills (`pg_callers`, `pg_impact`)
|
|
31
|
-
- ⚡ **Local embeddings** — `fastembed` BGE-Small-EN, 23 MB, no API key needed
|
|
32
|
-
- 👁️ **File watcher** — auto-reindexes when you add or edit skills
|
|
33
|
-
- 🛡️ **Validator** — blocks malicious/junk skills before they reach your machine
|
|
34
|
-
- 🌐 **MCP-native** — works with Claude Code, Claude Desktop, Cline, OpenCode, Cursor, Windsurf, and any MCP client
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Quick Start
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# one-time global install (recommended — faster than npx every time)
|
|
42
|
-
npm install -g promptgraph-mcp@latest
|
|
43
|
-
pg init
|
|
44
|
-
|
|
45
|
-
# or without installing
|
|
46
|
-
npx promptgraph-mcp init
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`init` downloads the embedding model (~23 MB, once), indexes your skills, and prints the config to paste into `settings.json`.
|
|
50
|
-
|
|
51
|
-
### Add to Claude Code (`~/.claude/settings.json`)
|
|
52
|
-
|
|
53
|
-
```json
|
|
54
|
-
{
|
|
55
|
-
"mcpServers": {
|
|
56
|
-
"promptgraph": {
|
|
57
|
-
"command": "npx",
|
|
58
|
-
"args": ["promptgraph-mcp"]
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Add to OpenCode (`~/.config/opencode/opencode.json`)
|
|
65
|
-
|
|
66
|
-
```json
|
|
67
|
-
{
|
|
68
|
-
"mcp": {
|
|
69
|
-
"promptgraph": {
|
|
70
|
-
"type": "local",
|
|
71
|
-
"command": ["npx", "promptgraph-mcp"],
|
|
72
|
-
"enabled": true
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
> `pg setup` auto-detects OpenCode and writes this config for you.
|
|
79
|
-
|
|
80
|
-
### Move your skills out of `commands/`
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
mkdir -p ~/.claude/skills-store
|
|
84
|
-
mv ~/.claude/commands/*.md ~/.claude/skills-store/
|
|
85
|
-
mv ~/.claude/skills-store/pg.md ~/.claude/commands/ # keep only the router
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## Marketplace
|
|
91
|
-
|
|
92
|
-
Browse and install community skills without leaving your terminal:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
pg marketplace # browse skills
|
|
96
|
-
pg marketplace bundles # browse bundles
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Or ask Claude directly:
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
install pg-a1b2c3 # by code
|
|
103
|
-
install systematic-debugging # by name
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
**Publish your own skill:**
|
|
107
|
-
|
|
108
|
-
```bash
|
|
109
|
-
pg publish ~/.claude/skills-store/my-skill.md
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
> Skills are validated automatically — dangerous patterns, prompt injection, and junk are rejected by CI before they enter the registry.
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## CLI
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
pg init # first-time setup
|
|
120
|
-
pg reindex # re-index all skills
|
|
121
|
-
pg search "deploy" # search from terminal
|
|
122
|
-
pg list # list all indexed skills
|
|
123
|
-
pg marketplace # browse registry
|
|
124
|
-
pg import owner/repo # import any GitHub repo full of .md skills
|
|
125
|
-
pg validate my-skill.md
|
|
126
|
-
pg doctor # clean up orphaned data
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
---
|
|
130
|
-
|
|
131
|
-
## MCP Tools (used by Claude automatically)
|
|
132
|
-
|
|
133
|
-
| Tool | What it does |
|
|
134
|
-
|---|---|
|
|
135
|
-
| `pg_search` | Semantic skill search by task description |
|
|
136
|
-
| `pg_list` | List all indexed skills |
|
|
137
|
-
| `pg_context` | Full skill details + callers/callees |
|
|
138
|
-
| `pg_callers` | Which skills reference this one |
|
|
139
|
-
| `pg_callees` | Which skills this one calls |
|
|
140
|
-
| `pg_impact` | What breaks if this skill changes |
|
|
141
|
-
| `pg_marketplace_browse` | Browse community registry |
|
|
142
|
-
| `pg_marketplace_install` | Install a skill by code, id, or name |
|
|
143
|
-
| `pg_bundle_browse` | Browse skill bundles |
|
|
144
|
-
| `pg_bundle_install` | Install a bundle |
|
|
145
|
-
| `pg_top_rated` | Highest-rated local skills |
|
|
146
|
-
| `pg_rate` | Rate a skill (success/fail) |
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Token Savings
|
|
151
|
-
|
|
152
|
-
| | Before PromptGraph | After PromptGraph |
|
|
153
|
-
|---|---|---|
|
|
154
|
-
| Skills in system prompt | All 40+ every session | 1 router (~150 tokens) |
|
|
155
|
-
| Tokens per session | 20,000 – 50,000 | ~300 + 1 skill on demand |
|
|
156
|
-
| Skills you can have | ~30 before it gets painful | Unlimited |
|
|
157
|
-
|
|
158
|
-
---
|
|
159
|
-
|
|
160
|
-
## How It Works
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
pg_search("refactor without breaking tests")
|
|
164
|
-
→ embed query → HNSW ANN search → rank by cosine + rating boost
|
|
165
|
-
→ return top skill path
|
|
166
|
-
→ Claude reads only that file
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
Embeddings are stored in SQLite. The HNSW index ([vectra](https://github.com/Stevenic/vectra)) keeps search sub-millisecond even at thousands of skills. Skills are re-indexed automatically via `chokidar` file watcher.
|
|
170
|
-
|
|
171
|
-
---
|
|
172
|
-
|
|
173
|
-
## Requirements
|
|
174
|
-
|
|
175
|
-
- Node.js 18+
|
|
176
|
-
- Claude Code or Claude Desktop (any MCP-compatible client)
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Related
|
|
181
|
-
|
|
182
|
-
- 📋 [promptgraph-registry](https://github.com/NeiP4n/promptgraph-registry) — community skill registry
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
*Built with [Claude](https://claude.com/claude-code) by Anthropic.*
|
|
1
|
+
# PromptGraph
|
|
2
|
+
|
|
3
|
+
**Stop burning 50,000 tokens on skills you won't use.**
|
|
4
|
+
|
|
5
|
+
PromptGraph is an MCP server that gives Claude Code a semantic skill index — vector search, skill graph, and a community marketplace. Instead of cramming every `.md` into your system prompt, Claude finds and loads only the one skill it needs.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/promptgraph-mcp)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Claude Code loads skill metadata from `~/.claude/commands/` each session. With 40+ skills that's thousands of tokens in routing overhead before you say a word. More importantly, when Claude loads and executes a full skill file (typically 1,000–5,000 tokens each), the cost multiplies fast.
|
|
15
|
+
|
|
16
|
+
PromptGraph replaces that with one tiny router skill (`~150 tokens`) and a local vector index. Claude calls `pg_search` → gets the right skill path + a content snippet → reads only that file when needed.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
Before: route across all 40 skills → 40 × read overhead
|
|
20
|
+
After: 1 pg_search call + snippet → read only what you need
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- 🔍 **Semantic search** — finds skills by meaning, not just keywords (HNSW, O(log N))
|
|
28
|
+
- 📦 **Marketplace** — browse and install community skills with one command
|
|
29
|
+
- 🧩 **Skill bundles** — install curated packs (e.g. `engineering-essentials`)
|
|
30
|
+
- 🔗 **Dependency graph** — tracks which skills call other skills (`pg_callers`, `pg_impact`)
|
|
31
|
+
- ⚡ **Local embeddings** — `fastembed` BGE-Small-EN, 23 MB, no API key needed
|
|
32
|
+
- 👁️ **File watcher** — auto-reindexes when you add or edit skills
|
|
33
|
+
- 🛡️ **Validator** — blocks malicious/junk skills before they reach your machine
|
|
34
|
+
- 🌐 **MCP-native** — works with Claude Code, Claude Desktop, Cline, OpenCode, Cursor, Windsurf, and any MCP client
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# one-time global install (recommended — faster than npx every time)
|
|
42
|
+
npm install -g promptgraph-mcp@latest
|
|
43
|
+
pg init
|
|
44
|
+
|
|
45
|
+
# or without installing
|
|
46
|
+
npx promptgraph-mcp init
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`init` downloads the embedding model (~23 MB, once), indexes your skills, and prints the config to paste into `settings.json`.
|
|
50
|
+
|
|
51
|
+
### Add to Claude Code (`~/.claude/settings.json`)
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"promptgraph": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["promptgraph-mcp"]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Add to OpenCode (`~/.config/opencode/opencode.json`)
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"mcp": {
|
|
69
|
+
"promptgraph": {
|
|
70
|
+
"type": "local",
|
|
71
|
+
"command": ["npx", "promptgraph-mcp"],
|
|
72
|
+
"enabled": true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
> `pg setup` auto-detects OpenCode and writes this config for you.
|
|
79
|
+
|
|
80
|
+
### Move your skills out of `commands/`
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
mkdir -p ~/.claude/skills-store
|
|
84
|
+
mv ~/.claude/commands/*.md ~/.claude/skills-store/
|
|
85
|
+
mv ~/.claude/skills-store/pg.md ~/.claude/commands/ # keep only the router
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Marketplace
|
|
91
|
+
|
|
92
|
+
Browse and install community skills without leaving your terminal:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pg marketplace # browse skills
|
|
96
|
+
pg marketplace bundles # browse bundles
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Or ask Claude directly:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
install pg-a1b2c3 # by code
|
|
103
|
+
install systematic-debugging # by name
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Publish your own skill:**
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pg publish ~/.claude/skills-store/my-skill.md
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
> Skills are validated automatically — dangerous patterns, prompt injection, and junk are rejected by CI before they enter the registry.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## CLI
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pg init # first-time setup
|
|
120
|
+
pg reindex # re-index all skills
|
|
121
|
+
pg search "deploy" # search from terminal
|
|
122
|
+
pg list # list all indexed skills
|
|
123
|
+
pg marketplace # browse registry
|
|
124
|
+
pg import owner/repo # import any GitHub repo full of .md skills
|
|
125
|
+
pg validate my-skill.md
|
|
126
|
+
pg doctor # clean up orphaned data
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## MCP Tools (used by Claude automatically)
|
|
132
|
+
|
|
133
|
+
| Tool | What it does |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `pg_search` | Semantic skill search by task description |
|
|
136
|
+
| `pg_list` | List all indexed skills |
|
|
137
|
+
| `pg_context` | Full skill details + callers/callees |
|
|
138
|
+
| `pg_callers` | Which skills reference this one |
|
|
139
|
+
| `pg_callees` | Which skills this one calls |
|
|
140
|
+
| `pg_impact` | What breaks if this skill changes |
|
|
141
|
+
| `pg_marketplace_browse` | Browse community registry |
|
|
142
|
+
| `pg_marketplace_install` | Install a skill by code, id, or name |
|
|
143
|
+
| `pg_bundle_browse` | Browse skill bundles |
|
|
144
|
+
| `pg_bundle_install` | Install a bundle |
|
|
145
|
+
| `pg_top_rated` | Highest-rated local skills |
|
|
146
|
+
| `pg_rate` | Rate a skill (success/fail) |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Token Savings
|
|
151
|
+
|
|
152
|
+
| | Before PromptGraph | After PromptGraph |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| Skills in system prompt | All 40+ every session | 1 router (~150 tokens) |
|
|
155
|
+
| Tokens per session | 20,000 – 50,000 | ~300 + 1 skill on demand |
|
|
156
|
+
| Skills you can have | ~30 before it gets painful | Unlimited |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## How It Works
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
pg_search("refactor without breaking tests")
|
|
164
|
+
→ embed query → HNSW ANN search → rank by cosine + rating boost
|
|
165
|
+
→ return top skill path
|
|
166
|
+
→ Claude reads only that file
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Embeddings are stored in SQLite. The HNSW index ([vectra](https://github.com/Stevenic/vectra)) keeps search sub-millisecond even at thousands of skills. Skills are re-indexed automatically via `chokidar` file watcher.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Requirements
|
|
174
|
+
|
|
175
|
+
- Node.js 18+
|
|
176
|
+
- Claude Code or Claude Desktop (any MCP-compatible client)
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Related
|
|
181
|
+
|
|
182
|
+
- 📋 [promptgraph-registry](https://github.com/NeiP4n/promptgraph-registry) — community skill registry
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
*Built with [Claude](https://claude.com/claude-code) by Anthropic.*
|
package/ann.js
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
import { getDb, blobToVec } from './db.js';
|
|
2
|
-
|
|
3
|
-
// In-memory flat index — no external dependency.
|
|
4
|
-
// For typical skill counts (<5000) this is faster than vectra's disk-based HNSW
|
|
5
|
-
// because all data fits in RAM and no I/O is needed per query.
|
|
6
|
-
let _cache = null;
|
|
7
|
-
let _cacheChunkCount = -1;
|
|
8
|
-
|
|
9
|
-
function loadCache(db) {
|
|
10
|
-
const count = db.prepare('SELECT COUNT(*) as n FROM chunks').get().n;
|
|
11
|
-
if (_cache && count === _cacheChunkCount) return _cache;
|
|
12
|
-
const rows = db.prepare('SELECT skill_id, embedding FROM chunks').all();
|
|
13
|
-
_cache = rows.map(r => ({
|
|
14
|
-
skill_id: r.skill_id,
|
|
15
|
-
vec: new Float32Array(blobToVec(r.embedding)),
|
|
16
|
-
}));
|
|
17
|
-
_cacheChunkCount = count;
|
|
18
|
-
return _cache;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function cosineSim(a, b) {
|
|
22
|
-
let dot = 0, na = 0, nb = 0;
|
|
23
|
-
for (let i = 0; i < a.length; i++) {
|
|
24
|
-
dot += a[i] * b[i];
|
|
25
|
-
na += a[i] * a[i];
|
|
26
|
-
nb += b[i] * b[i];
|
|
27
|
-
}
|
|
28
|
-
return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-8);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Called after reindex — invalidate cache so next search reloads
|
|
32
|
-
export async function buildAnnIndex() {
|
|
33
|
-
_cache = null;
|
|
34
|
-
_cacheChunkCount = -1;
|
|
35
|
-
const db = getDb();
|
|
36
|
-
const count = db.prepare('SELECT COUNT(*) as n FROM chunks').get().n;
|
|
37
|
-
console.error(`[PromptGraph] In-memory index ready: ${count} chunks`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function annSearch(queryVec, topK = 20) {
|
|
41
|
-
try {
|
|
42
|
-
const db = getDb();
|
|
43
|
-
const cache = loadCache(db);
|
|
44
|
-
if (!cache.length) return null;
|
|
45
|
-
|
|
46
|
-
const qArr = new Float32Array(queryVec);
|
|
47
|
-
const bestBySkill = new Map();
|
|
48
|
-
for (const entry of cache) {
|
|
49
|
-
const score = cosineSim(qArr, entry.vec);
|
|
50
|
-
const prev = bestBySkill.get(entry.skill_id);
|
|
51
|
-
if (!prev || score > prev) bestBySkill.set(entry.skill_id, score);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return [...bestBySkill.entries()]
|
|
55
|
-
.sort((a, b) => b[1] - a[1])
|
|
56
|
-
.slice(0, topK)
|
|
57
|
-
.map(([skill_id, score]) => ({ skill_id, score }));
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
1
|
+
import { getDb, blobToVec } from './db.js';
|
|
2
|
+
|
|
3
|
+
// In-memory flat index — no external dependency.
|
|
4
|
+
// For typical skill counts (<5000) this is faster than vectra's disk-based HNSW
|
|
5
|
+
// because all data fits in RAM and no I/O is needed per query.
|
|
6
|
+
let _cache = null;
|
|
7
|
+
let _cacheChunkCount = -1;
|
|
8
|
+
|
|
9
|
+
function loadCache(db) {
|
|
10
|
+
const count = db.prepare('SELECT COUNT(*) as n FROM chunks').get().n;
|
|
11
|
+
if (_cache && count === _cacheChunkCount) return _cache;
|
|
12
|
+
const rows = db.prepare('SELECT skill_id, embedding FROM chunks').all();
|
|
13
|
+
_cache = rows.map(r => ({
|
|
14
|
+
skill_id: r.skill_id,
|
|
15
|
+
vec: new Float32Array(blobToVec(r.embedding)),
|
|
16
|
+
}));
|
|
17
|
+
_cacheChunkCount = count;
|
|
18
|
+
return _cache;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function cosineSim(a, b) {
|
|
22
|
+
let dot = 0, na = 0, nb = 0;
|
|
23
|
+
for (let i = 0; i < a.length; i++) {
|
|
24
|
+
dot += a[i] * b[i];
|
|
25
|
+
na += a[i] * a[i];
|
|
26
|
+
nb += b[i] * b[i];
|
|
27
|
+
}
|
|
28
|
+
return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-8);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Called after reindex — invalidate cache so next search reloads
|
|
32
|
+
export async function buildAnnIndex() {
|
|
33
|
+
_cache = null;
|
|
34
|
+
_cacheChunkCount = -1;
|
|
35
|
+
const db = getDb();
|
|
36
|
+
const count = db.prepare('SELECT COUNT(*) as n FROM chunks').get().n;
|
|
37
|
+
console.error(`[PromptGraph] In-memory index ready: ${count} chunks`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function annSearch(queryVec, topK = 20) {
|
|
41
|
+
try {
|
|
42
|
+
const db = getDb();
|
|
43
|
+
const cache = loadCache(db);
|
|
44
|
+
if (!cache.length) return null;
|
|
45
|
+
|
|
46
|
+
const qArr = new Float32Array(queryVec);
|
|
47
|
+
const bestBySkill = new Map();
|
|
48
|
+
for (const entry of cache) {
|
|
49
|
+
const score = cosineSim(qArr, entry.vec);
|
|
50
|
+
const prev = bestBySkill.get(entry.skill_id);
|
|
51
|
+
if (!prev || score > prev) bestBySkill.set(entry.skill_id, score);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [...bestBySkill.entries()]
|
|
55
|
+
.sort((a, b) => b[1] - a[1])
|
|
56
|
+
.slice(0, topK)
|
|
57
|
+
.map(([skill_id, score]) => ({ skill_id, score }));
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
package/chunker.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
const CHUNK_SIZE = 800;
|
|
2
|
-
const CHUNK_OVERLAP = 100;
|
|
3
|
-
|
|
4
|
-
export function chunkText(text) {
|
|
5
|
-
// Split on markdown h1/h2/h3 headers to preserve semantic boundaries
|
|
6
|
-
const sections = text.split(/(?=\n#{1,3} )/);
|
|
7
|
-
const chunks = [];
|
|
8
|
-
|
|
9
|
-
for (const section of sections) {
|
|
10
|
-
const words = section.split(/\s+/).filter(Boolean);
|
|
11
|
-
if (words.length === 0) continue;
|
|
12
|
-
|
|
13
|
-
if (words.length <= CHUNK_SIZE) {
|
|
14
|
-
chunks.push(section.trim());
|
|
15
|
-
} else {
|
|
16
|
-
let i = 0;
|
|
17
|
-
while (i < words.length) {
|
|
18
|
-
chunks.push(words.slice(i, i + CHUNK_SIZE).join(' '));
|
|
19
|
-
if (i + CHUNK_SIZE >= words.length) break;
|
|
20
|
-
i += CHUNK_SIZE - CHUNK_OVERLAP;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return chunks.length > 0 ? chunks : [text];
|
|
26
|
-
}
|
|
1
|
+
const CHUNK_SIZE = 800;
|
|
2
|
+
const CHUNK_OVERLAP = 100;
|
|
3
|
+
|
|
4
|
+
export function chunkText(text) {
|
|
5
|
+
// Split on markdown h1/h2/h3 headers to preserve semantic boundaries
|
|
6
|
+
const sections = text.split(/(?=\n#{1,3} )/);
|
|
7
|
+
const chunks = [];
|
|
8
|
+
|
|
9
|
+
for (const section of sections) {
|
|
10
|
+
const words = section.split(/\s+/).filter(Boolean);
|
|
11
|
+
if (words.length === 0) continue;
|
|
12
|
+
|
|
13
|
+
if (words.length <= CHUNK_SIZE) {
|
|
14
|
+
chunks.push(section.trim());
|
|
15
|
+
} else {
|
|
16
|
+
let i = 0;
|
|
17
|
+
while (i < words.length) {
|
|
18
|
+
chunks.push(words.slice(i, i + CHUNK_SIZE).join(' '));
|
|
19
|
+
if (i + CHUNK_SIZE >= words.length) break;
|
|
20
|
+
i += CHUNK_SIZE - CHUNK_OVERLAP;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return chunks.length > 0 ? chunks : [text];
|
|
26
|
+
}
|