@vpxa/kb 0.1.1 → 0.1.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.
Files changed (138) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/packages/analyzers/dist/blast-radius-analyzer.js +13 -114
  4. package/packages/analyzers/dist/dependency-analyzer.js +11 -425
  5. package/packages/analyzers/dist/diagram-generator.js +4 -86
  6. package/packages/analyzers/dist/entry-point-analyzer.js +5 -239
  7. package/packages/analyzers/dist/index.js +1 -23
  8. package/packages/analyzers/dist/knowledge-producer.js +24 -113
  9. package/packages/analyzers/dist/pattern-analyzer.js +5 -359
  10. package/packages/analyzers/dist/regex-call-graph.js +1 -428
  11. package/packages/analyzers/dist/structure-analyzer.js +4 -258
  12. package/packages/analyzers/dist/symbol-analyzer.js +13 -442
  13. package/packages/analyzers/dist/ts-call-graph.js +1 -160
  14. package/packages/analyzers/dist/types.js +0 -1
  15. package/packages/chunker/dist/call-graph-extractor.js +1 -90
  16. package/packages/chunker/dist/chunker-factory.js +1 -36
  17. package/packages/chunker/dist/chunker.interface.js +0 -1
  18. package/packages/chunker/dist/code-chunker.js +14 -134
  19. package/packages/chunker/dist/generic-chunker.js +5 -72
  20. package/packages/chunker/dist/index.js +1 -21
  21. package/packages/chunker/dist/markdown-chunker.js +7 -119
  22. package/packages/chunker/dist/treesitter-chunker.js +8 -234
  23. package/packages/cli/dist/commands/analyze.js +3 -112
  24. package/packages/cli/dist/commands/context-cmds.js +1 -155
  25. package/packages/cli/dist/commands/environment.js +2 -204
  26. package/packages/cli/dist/commands/execution.js +1 -137
  27. package/packages/cli/dist/commands/graph.js +7 -81
  28. package/packages/cli/dist/commands/init.js +9 -87
  29. package/packages/cli/dist/commands/knowledge.js +1 -139
  30. package/packages/cli/dist/commands/search.js +8 -267
  31. package/packages/cli/dist/commands/system.js +4 -241
  32. package/packages/cli/dist/commands/workspace.js +2 -388
  33. package/packages/cli/dist/context.js +1 -14
  34. package/packages/cli/dist/helpers.js +3 -458
  35. package/packages/cli/dist/index.d.ts +1 -1
  36. package/packages/cli/dist/index.js +3 -69
  37. package/packages/cli/dist/kb-init.js +1 -82
  38. package/packages/cli/dist/types.js +0 -1
  39. package/packages/core/dist/constants.js +1 -43
  40. package/packages/core/dist/content-detector.js +1 -79
  41. package/packages/core/dist/errors.js +1 -40
  42. package/packages/core/dist/index.js +1 -9
  43. package/packages/core/dist/logger.js +1 -34
  44. package/packages/core/dist/types.js +0 -1
  45. package/packages/embeddings/dist/embedder.interface.js +0 -1
  46. package/packages/embeddings/dist/index.js +1 -5
  47. package/packages/embeddings/dist/onnx-embedder.js +1 -82
  48. package/packages/indexer/dist/file-hasher.js +1 -13
  49. package/packages/indexer/dist/filesystem-crawler.js +1 -125
  50. package/packages/indexer/dist/graph-extractor.js +1 -111
  51. package/packages/indexer/dist/incremental-indexer.js +1 -278
  52. package/packages/indexer/dist/index.js +1 -14
  53. package/packages/server/dist/api.js +1 -9
  54. package/packages/server/dist/config.js +1 -75
  55. package/packages/server/dist/curated-manager.js +9 -356
  56. package/packages/server/dist/index.js +1 -134
  57. package/packages/server/dist/replay-interceptor.js +1 -38
  58. package/packages/server/dist/resources/resources.js +2 -40
  59. package/packages/server/dist/server.js +1 -247
  60. package/packages/server/dist/tools/analyze.tools.js +1 -288
  61. package/packages/server/dist/tools/forge.tools.js +11 -499
  62. package/packages/server/dist/tools/forget.tool.js +3 -39
  63. package/packages/server/dist/tools/graph.tool.js +5 -110
  64. package/packages/server/dist/tools/list.tool.js +5 -53
  65. package/packages/server/dist/tools/lookup.tool.js +8 -51
  66. package/packages/server/dist/tools/onboard.tool.js +2 -112
  67. package/packages/server/dist/tools/produce.tool.js +4 -74
  68. package/packages/server/dist/tools/read.tool.js +4 -47
  69. package/packages/server/dist/tools/reindex.tool.js +2 -70
  70. package/packages/server/dist/tools/remember.tool.js +3 -42
  71. package/packages/server/dist/tools/replay.tool.js +6 -88
  72. package/packages/server/dist/tools/search.tool.js +17 -327
  73. package/packages/server/dist/tools/status.tool.js +3 -68
  74. package/packages/server/dist/tools/toolkit.tools.js +20 -1673
  75. package/packages/server/dist/tools/update.tool.js +3 -39
  76. package/packages/server/dist/tools/utility.tools.js +19 -456
  77. package/packages/store/dist/graph-store.interface.js +0 -1
  78. package/packages/store/dist/index.js +1 -9
  79. package/packages/store/dist/lance-store.js +1 -258
  80. package/packages/store/dist/sqlite-graph-store.js +8 -309
  81. package/packages/store/dist/store-factory.js +1 -14
  82. package/packages/store/dist/store.interface.js +0 -1
  83. package/packages/tools/dist/batch.js +1 -45
  84. package/packages/tools/dist/changelog.js +2 -112
  85. package/packages/tools/dist/check.js +2 -59
  86. package/packages/tools/dist/checkpoint.js +2 -43
  87. package/packages/tools/dist/codemod.js +2 -69
  88. package/packages/tools/dist/compact.js +3 -60
  89. package/packages/tools/dist/data-transform.js +1 -124
  90. package/packages/tools/dist/dead-symbols.js +2 -71
  91. package/packages/tools/dist/delegate.js +3 -128
  92. package/packages/tools/dist/diff-parse.js +3 -153
  93. package/packages/tools/dist/digest.js +7 -242
  94. package/packages/tools/dist/encode.js +1 -46
  95. package/packages/tools/dist/env-info.js +1 -58
  96. package/packages/tools/dist/eval.js +3 -79
  97. package/packages/tools/dist/evidence-map.js +3 -203
  98. package/packages/tools/dist/file-summary.js +2 -106
  99. package/packages/tools/dist/file-walk.js +1 -75
  100. package/packages/tools/dist/find-examples.js +3 -48
  101. package/packages/tools/dist/find.js +1 -120
  102. package/packages/tools/dist/forge-classify.js +2 -319
  103. package/packages/tools/dist/forge-ground.js +1 -184
  104. package/packages/tools/dist/git-context.js +3 -46
  105. package/packages/tools/dist/graph-query.js +1 -194
  106. package/packages/tools/dist/health.js +1 -118
  107. package/packages/tools/dist/http-request.js +1 -58
  108. package/packages/tools/dist/index.js +1 -273
  109. package/packages/tools/dist/lane.js +7 -227
  110. package/packages/tools/dist/measure.js +2 -119
  111. package/packages/tools/dist/onboard.js +42 -1136
  112. package/packages/tools/dist/parse-output.js +2 -158
  113. package/packages/tools/dist/process-manager.js +1 -69
  114. package/packages/tools/dist/queue.js +2 -126
  115. package/packages/tools/dist/regex-test.js +1 -39
  116. package/packages/tools/dist/rename.js +2 -70
  117. package/packages/tools/dist/replay.js +6 -108
  118. package/packages/tools/dist/schema-validate.js +1 -141
  119. package/packages/tools/dist/scope-map.js +1 -72
  120. package/packages/tools/dist/snippet.js +1 -80
  121. package/packages/tools/dist/stash.js +2 -60
  122. package/packages/tools/dist/stratum-card.js +5 -238
  123. package/packages/tools/dist/symbol.js +3 -87
  124. package/packages/tools/dist/test-run.js +2 -55
  125. package/packages/tools/dist/text-utils.js +2 -31
  126. package/packages/tools/dist/time-utils.js +1 -135
  127. package/packages/tools/dist/trace.js +2 -114
  128. package/packages/tools/dist/truncation.js +10 -41
  129. package/packages/tools/dist/watch.js +1 -61
  130. package/packages/tools/dist/web-fetch.js +9 -244
  131. package/packages/tools/dist/web-search.js +1 -46
  132. package/packages/tools/dist/workset.js +2 -77
  133. package/packages/tui/dist/App.js +260 -52468
  134. package/packages/tui/dist/index.js +286 -54551
  135. package/packages/tui/dist/panels/CuratedPanel.js +211 -34291
  136. package/packages/tui/dist/panels/LogPanel.js +259 -51703
  137. package/packages/tui/dist/panels/SearchPanel.js +212 -34824
  138. package/packages/tui/dist/panels/StatusPanel.js +211 -34304
@@ -1,357 +1,10 @@
1
- import { createHash } from "node:crypto";
2
- import { mkdir, readdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
3
- import { dirname, isAbsolute, join } from "node:path";
4
- const MAX_CONTENT_SIZE = 50 * 1024;
5
- class CuratedKnowledgeManager {
6
- constructor(curatedDir, store, embedder) {
7
- this.curatedDir = curatedDir;
8
- this.store = store;
9
- this.embedder = embedder;
10
- }
11
- async remember(title, content, category, tags = []) {
12
- this.validateCategoryName(category);
13
- this.validateContentSize(content);
14
- const slug = this.slugify(title);
15
- const relativePath = await this.uniqueRelativePath(category, slug);
16
- const filePath = join(this.curatedDir, relativePath);
17
- const now = (/* @__PURE__ */ new Date()).toISOString();
18
- const frontmatter = {
19
- title,
20
- category,
21
- tags,
22
- created: now,
23
- updated: now,
24
- version: 1,
25
- origin: "curated",
26
- changelog: [{ version: 1, date: now, reason: "Initial creation" }]
27
- };
28
- const fileContent = this.serializeFile(content, frontmatter);
29
- await mkdir(dirname(filePath), { recursive: true });
30
- await writeFile(filePath, fileContent, "utf-8");
31
- await this.indexCuratedFile(relativePath, content, frontmatter);
32
- return { path: relativePath };
33
- }
34
- async update(relativePath, newContent, reason) {
35
- this.guardPath(relativePath);
36
- this.validateContentSize(newContent);
37
- const filePath = join(this.curatedDir, relativePath);
38
- const raw = await readFile(filePath, "utf-8");
39
- const { frontmatter } = this.parseFile(raw);
40
- const newVersion = (frontmatter.version ?? 1) + 1;
41
- const now = (/* @__PURE__ */ new Date()).toISOString();
42
- frontmatter.version = newVersion;
43
- frontmatter.updated = now;
44
- frontmatter.changelog = [
45
- ...frontmatter.changelog ?? [],
46
- { version: newVersion, date: now, reason }
47
- ];
48
- const fileContent = this.serializeFile(newContent, frontmatter);
49
- await writeFile(filePath, fileContent, "utf-8");
50
- await this.indexCuratedFile(relativePath, newContent, frontmatter);
51
- return { path: relativePath, version: newVersion };
52
- }
53
- async forget(relativePath, _reason) {
54
- this.guardPath(relativePath);
55
- const filePath = join(this.curatedDir, relativePath);
56
- const storePath = `curated/${relativePath}`;
57
- await this.store.deleteBySourcePath(storePath);
58
- await unlink(filePath);
59
- return { path: relativePath };
60
- }
61
- async read(relativePath) {
62
- this.guardPath(relativePath);
63
- const filePath = join(this.curatedDir, relativePath);
64
- const raw = await readFile(filePath, "utf-8");
65
- const { frontmatter, content } = this.parseFile(raw);
66
- const category = relativePath.split("/")[0];
67
- return {
68
- path: relativePath,
69
- title: frontmatter.title ?? relativePath,
70
- category,
71
- tags: frontmatter.tags ?? [],
72
- version: frontmatter.version ?? 1,
73
- created: frontmatter.created ?? "",
74
- updated: frontmatter.updated ?? "",
75
- contentPreview: content.slice(0, 200),
76
- content
77
- };
78
- }
79
- async list(filters) {
80
- const entries = [];
81
- const categories = filters?.category ? [filters.category] : await this.discoverCategories();
82
- for (const cat of categories) {
83
- const catDir = join(this.curatedDir, cat);
84
- try {
85
- const files = await readdir(catDir);
86
- for (const file of files) {
87
- if (!file.endsWith(".md")) continue;
88
- const filePath = join(catDir, file);
89
- const raw = await readFile(filePath, "utf-8");
90
- const { frontmatter, content } = this.parseFile(raw);
91
- if (filters?.tag && !(frontmatter.tags ?? []).includes(filters.tag)) continue;
92
- entries.push({
93
- path: `${cat}/${file}`,
94
- title: frontmatter.title ?? file,
95
- category: cat,
96
- tags: frontmatter.tags ?? [],
97
- version: frontmatter.version ?? 1,
98
- created: frontmatter.created ?? "",
99
- updated: frontmatter.updated ?? "",
100
- contentPreview: content.slice(0, 200)
101
- });
102
- }
103
- } catch {
104
- }
105
- }
106
- return entries;
107
- }
108
- /**
109
- * Re-index all curated files into the vector store.
110
- * Call this after the indexer has run to restore curated vectors
111
- * that may have been lost, or to bulk-sync disk state to vectors.
112
- */
113
- async reindexAll() {
114
- const categories = await this.discoverCategories();
115
- const errors = [];
116
- const pending = [];
117
- for (const cat of categories) {
118
- const catDir = join(this.curatedDir, cat);
119
- let files;
120
- try {
121
- files = (await readdir(catDir)).filter((f) => f.endsWith(".md"));
122
- } catch {
123
- continue;
124
- }
125
- for (const file of files) {
126
- const relativePath = `${cat}/${file}`;
127
- const filePath = join(catDir, file);
128
- try {
129
- const raw = await readFile(filePath, "utf-8");
130
- const { frontmatter, content } = this.parseFile(raw);
131
- pending.push({ relativePath, content, frontmatter });
132
- } catch (err) {
133
- errors.push(`${relativePath}: ${err.message}`);
134
- }
135
- }
136
- }
137
- if (pending.length === 0) return { indexed: 0, errors };
138
- const vectors = await this.embedder.embedBatch(pending.map((p) => p.content));
139
- const now = (/* @__PURE__ */ new Date()).toISOString();
140
- const records = pending.map((p) => {
141
- const storePath = `curated/${p.relativePath}`;
142
- return {
143
- id: this.hashId(storePath, 0),
144
- content: p.content,
145
- sourcePath: storePath,
146
- contentType: "curated-knowledge",
147
- headingPath: p.frontmatter.title,
148
- chunkIndex: 0,
149
- totalChunks: 1,
150
- startLine: 1,
151
- endLine: p.content.split("\n").length,
152
- fileHash: this.hash(p.content),
153
- indexedAt: now,
154
- origin: "curated",
155
- tags: p.frontmatter.tags,
156
- category: p.frontmatter.category,
157
- version: p.frontmatter.version
158
- };
159
- });
160
- await this.store.upsert(records, vectors);
161
- return { indexed: pending.length, errors };
162
- }
163
- // --- Private helpers ---
164
- async indexCuratedFile(relativePath, content, frontmatter) {
165
- const vector = await this.embedder.embed(content);
166
- const storePath = `curated/${relativePath}`;
167
- const now = (/* @__PURE__ */ new Date()).toISOString();
168
- const record = {
169
- id: this.hashId(storePath, 0),
170
- content,
171
- sourcePath: storePath,
172
- contentType: "curated-knowledge",
173
- headingPath: frontmatter.title,
174
- chunkIndex: 0,
175
- totalChunks: 1,
176
- startLine: 1,
177
- endLine: content.split("\n").length,
178
- fileHash: this.hash(content),
179
- indexedAt: now,
180
- origin: "curated",
181
- tags: frontmatter.tags,
182
- category: frontmatter.category,
183
- version: frontmatter.version
184
- };
185
- await this.store.upsert([record], [vector]);
186
- }
187
- async discoverCategories() {
188
- try {
189
- const entries = await readdir(this.curatedDir, { withFileTypes: true });
190
- return entries.filter((e) => e.isDirectory() && /^[a-z][a-z0-9-]*$/.test(e.name)).map((e) => e.name);
191
- } catch {
192
- return [];
193
- }
194
- }
195
- guardPath(relativePath) {
196
- if (relativePath.includes("..") || isAbsolute(relativePath)) {
197
- throw new Error(`Invalid path: ${relativePath}. Must be relative within curated/ directory.`);
198
- }
199
- const category = relativePath.split("/")[0];
200
- this.validateCategoryName(category);
201
- }
202
- validateCategoryName(category) {
203
- if (!/^[a-z][a-z0-9-]*$/.test(category)) {
204
- throw new Error(
205
- `Invalid category name: "${category}". Must be lowercase kebab-case (e.g., "decisions", "api-contracts").`
206
- );
207
- }
208
- }
209
- validateContentSize(content) {
210
- if (Buffer.byteLength(content, "utf-8") > MAX_CONTENT_SIZE) {
211
- throw new Error(`Content exceeds maximum size of ${MAX_CONTENT_SIZE / 1024}KB`);
212
- }
213
- }
214
- slugify(title) {
215
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
216
- }
217
- /** Return a unique `category/slug.md` path, appending `-2`, `-3`, … on collision. */
218
- async uniqueRelativePath(category, slug) {
219
- const base = `${category}/${slug}.md`;
220
- const basePath = join(this.curatedDir, base);
221
- try {
222
- await stat(basePath);
223
- } catch {
224
- return base;
225
- }
226
- for (let i = 2; i <= 100; i++) {
227
- const candidate = `${category}/${slug}-${i}.md`;
228
- try {
229
- await stat(join(this.curatedDir, candidate));
230
- } catch {
231
- return candidate;
232
- }
233
- }
234
- throw new Error(`Too many entries with slug "${slug}" in category "${category}"`);
235
- }
236
- hash(content) {
237
- return createHash("sha256").update(content).digest("hex").slice(0, 16);
238
- }
239
- hashId(sourcePath, chunkIndex) {
240
- return this.hash(`${sourcePath}::${chunkIndex}`);
241
- }
242
- /**
243
- * Simple YAML frontmatter serializer (no gray-matter dependency).
244
- */
245
- serializeFile(content, frontmatter) {
246
- const yaml = [
247
- "---",
248
- `title: "${frontmatter.title.replace(/"/g, '\\"')}"`,
249
- `category: ${frontmatter.category}`,
250
- `tags: [${frontmatter.tags.map((t) => `"${t}"`).join(", ")}]`,
251
- `created: ${frontmatter.created}`,
252
- `updated: ${frontmatter.updated}`,
253
- `version: ${frontmatter.version}`,
254
- `origin: ${frontmatter.origin}`,
255
- "changelog:",
256
- ...frontmatter.changelog.map(
257
- (c) => ` - version: ${c.version}
258
- date: ${c.date}
259
- reason: "${c.reason.replace(/"/g, '\\"')}"`
260
- ),
261
- "---"
262
- ].join("\n");
263
- return `${yaml}
1
+ import{createHash as C}from"node:crypto";import{mkdir as b,readdir as f,readFile as m,stat as y,unlink as P,writeFile as v}from"node:fs/promises";import{dirname as x,isAbsolute as F,join as u}from"node:path";const w=50*1024;class k{constructor(t,e,n){this.curatedDir=t;this.store=e;this.embedder=n}async remember(t,e,n,r=[]){this.validateCategoryName(n),this.validateContentSize(e);const a=this.slugify(t),s=await this.uniqueRelativePath(n,a),i=u(this.curatedDir,s),g=new Date().toISOString(),c={title:t,category:n,tags:r,created:g,updated:g,version:1,origin:"curated",changelog:[{version:1,date:g,reason:"Initial creation"}]},d=this.serializeFile(e,c);return await b(x(i),{recursive:!0}),await v(i,d,"utf-8"),await this.indexCuratedFile(s,e,c),{path:s}}async update(t,e,n){this.guardPath(t),this.validateContentSize(e);const r=u(this.curatedDir,t),a=await m(r,"utf-8"),{frontmatter:s}=this.parseFile(a),i=(s.version??1)+1,g=new Date().toISOString();s.version=i,s.updated=g,s.changelog=[...s.changelog??[],{version:i,date:g,reason:n}];const c=this.serializeFile(e,s);return await v(r,c,"utf-8"),await this.indexCuratedFile(t,e,s),{path:t,version:i}}async forget(t,e){this.guardPath(t);const n=u(this.curatedDir,t),r=`curated/${t}`;return await this.store.deleteBySourcePath(r),await P(n),{path:t}}async read(t){this.guardPath(t);const e=u(this.curatedDir,t),n=await m(e,"utf-8"),{frontmatter:r,content:a}=this.parseFile(n),s=t.split("/")[0];return{path:t,title:r.title??t,category:s,tags:r.tags??[],version:r.version??1,created:r.created??"",updated:r.updated??"",contentPreview:a.slice(0,200),content:a}}async list(t){const e=[],n=t?.category?[t.category]:await this.discoverCategories();for(const r of n){const a=u(this.curatedDir,r);try{const s=await f(a);for(const i of s){if(!i.endsWith(".md"))continue;const g=u(a,i),c=await m(g,"utf-8"),{frontmatter:d,content:h}=this.parseFile(c);t?.tag&&!(d.tags??[]).includes(t.tag)||e.push({path:`${r}/${i}`,title:d.title??i,category:r,tags:d.tags??[],version:d.version??1,created:d.created??"",updated:d.updated??"",contentPreview:h.slice(0,200)})}}catch{}}return e}async reindexAll(){const t=await this.discoverCategories(),e=[],n=[];for(const i of t){const g=u(this.curatedDir,i);let c;try{c=(await f(g)).filter(d=>d.endsWith(".md"))}catch{continue}for(const d of c){const h=`${i}/${d}`,p=u(g,d);try{const o=await m(p,"utf-8"),{frontmatter:l,content:$}=this.parseFile(o);n.push({relativePath:h,content:$,frontmatter:l})}catch(o){e.push(`${h}: ${o.message}`)}}}if(n.length===0)return{indexed:0,errors:e};const r=await this.embedder.embedBatch(n.map(i=>i.content)),a=new Date().toISOString(),s=n.map(i=>{const g=`curated/${i.relativePath}`;return{id:this.hashId(g,0),content:i.content,sourcePath:g,contentType:"curated-knowledge",headingPath:i.frontmatter.title,chunkIndex:0,totalChunks:1,startLine:1,endLine:i.content.split(`
2
+ `).length,fileHash:this.hash(i.content),indexedAt:a,origin:"curated",tags:i.frontmatter.tags,category:i.frontmatter.category,version:i.frontmatter.version}});return await this.store.upsert(s,r),{indexed:n.length,errors:e}}async indexCuratedFile(t,e,n){const r=await this.embedder.embed(e),a=`curated/${t}`,s=new Date().toISOString(),i={id:this.hashId(a,0),content:e,sourcePath:a,contentType:"curated-knowledge",headingPath:n.title,chunkIndex:0,totalChunks:1,startLine:1,endLine:e.split(`
3
+ `).length,fileHash:this.hash(e),indexedAt:s,origin:"curated",tags:n.tags,category:n.category,version:n.version};await this.store.upsert([i],[r])}async discoverCategories(){try{return(await f(this.curatedDir,{withFileTypes:!0})).filter(e=>e.isDirectory()&&/^[a-z][a-z0-9-]*$/.test(e.name)).map(e=>e.name)}catch{return[]}}guardPath(t){if(t.includes("..")||F(t))throw new Error(`Invalid path: ${t}. Must be relative within curated/ directory.`);const e=t.split("/")[0];this.validateCategoryName(e)}validateCategoryName(t){if(!/^[a-z][a-z0-9-]*$/.test(t))throw new Error(`Invalid category name: "${t}". Must be lowercase kebab-case (e.g., "decisions", "api-contracts").`)}validateContentSize(t){if(Buffer.byteLength(t,"utf-8")>w)throw new Error(`Content exceeds maximum size of ${w/1024}KB`)}slugify(t){return t.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,80)}async uniqueRelativePath(t,e){const n=`${t}/${e}.md`,r=u(this.curatedDir,n);try{await y(r)}catch{return n}for(let a=2;a<=100;a++){const s=`${t}/${e}-${a}.md`;try{await y(u(this.curatedDir,s))}catch{return s}}throw new Error(`Too many entries with slug "${e}" in category "${t}"`)}hash(t){return C("sha256").update(t).digest("hex").slice(0,16)}hashId(t,e){return this.hash(`${t}::${e}`)}serializeFile(t,e){return`${["---",`title: "${e.title.replace(/"/g,'\\"')}"`,`category: ${e.category}`,`tags: [${e.tags.map(r=>`"${r}"`).join(", ")}]`,`created: ${e.created}`,`updated: ${e.updated}`,`version: ${e.version}`,`origin: ${e.origin}`,"changelog:",...e.changelog.map(r=>` - version: ${r.version}
4
+ date: ${r.date}
5
+ reason: "${r.reason.replace(/"/g,'\\"')}"`),"---"].join(`
6
+ `)}
264
7
 
265
- ${content}
266
- `;
267
- }
268
- /**
269
- * Simple YAML frontmatter parser (no gray-matter dependency).
270
- */
271
- parseFile(raw) {
272
- const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
273
- if (!fmMatch) {
274
- return {
275
- frontmatter: {
276
- title: "Untitled",
277
- category: "notes",
278
- tags: [],
279
- created: "",
280
- updated: "",
281
- version: 1,
282
- origin: "curated",
283
- changelog: []
284
- },
285
- content: raw
286
- };
287
- }
288
- const yamlStr = fmMatch[1];
289
- const content = fmMatch[2].trim();
290
- const fm = {};
291
- const changelog = [];
292
- const lines = yamlStr.split("\n");
293
- let inChangelog = false;
294
- let currentEntry = {};
295
- for (const line of lines) {
296
- if (/^changelog:\s*$/.test(line)) {
297
- inChangelog = true;
298
- continue;
299
- }
300
- if (inChangelog) {
301
- const itemMatch = line.match(/^\s+-\s+version:\s*(\d+)$/);
302
- if (itemMatch) {
303
- if (currentEntry.version != null) changelog.push(currentEntry);
304
- currentEntry = { version: parseInt(itemMatch[1], 10) };
305
- continue;
306
- }
307
- const dateMatch = line.match(/^\s+date:\s*(.+)$/);
308
- if (dateMatch) {
309
- currentEntry.date = dateMatch[1].trim();
310
- continue;
311
- }
312
- const reasonMatch = line.match(/^\s+reason:\s*"?(.*?)"?\s*$/);
313
- if (reasonMatch) {
314
- currentEntry.reason = reasonMatch[1];
315
- continue;
316
- }
317
- if (/^\w/.test(line)) {
318
- inChangelog = false;
319
- if (currentEntry.version != null) changelog.push(currentEntry);
320
- currentEntry = {};
321
- }
322
- continue;
323
- }
324
- const kvMatch = line.match(/^(\w+):\s*(.*)$/);
325
- if (kvMatch) {
326
- const key = kvMatch[1];
327
- let value = kvMatch[2];
328
- if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
329
- value = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^"|"$/g, "")).filter((s) => s.length > 0);
330
- } else if (typeof value === "string" && /^\d+$/.test(value)) {
331
- value = parseInt(value, 10);
332
- } else if (typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
333
- value = value.slice(1, -1);
334
- }
335
- fm[key] = value;
336
- }
337
- }
338
- if (currentEntry.version != null) changelog.push(currentEntry);
339
- return {
340
- frontmatter: {
341
- title: fm.title ?? "Untitled",
342
- category: fm.category ?? "notes",
343
- tags: fm.tags ?? [],
344
- created: fm.created ?? "",
345
- updated: fm.updated ?? "",
346
- version: fm.version ?? 1,
347
- origin: "curated",
348
- changelog
349
- },
350
- content
351
- };
352
- }
353
- }
354
- export {
355
- CuratedKnowledgeManager
356
- };
357
- //# sourceMappingURL=curated-manager.js.map
8
+ ${t}
9
+ `}parseFile(t){const e=t.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);if(!e)return{frontmatter:{title:"Untitled",category:"notes",tags:[],created:"",updated:"",version:1,origin:"curated",changelog:[]},content:t};const n=e[1],r=e[2].trim(),a={},s=[],i=n.split(`
10
+ `);let g=!1,c={};for(const d of i){if(/^changelog:\s*$/.test(d)){g=!0;continue}if(g){const p=d.match(/^\s+-\s+version:\s*(\d+)$/);if(p){c.version!=null&&s.push(c),c={version:parseInt(p[1],10)};continue}const o=d.match(/^\s+date:\s*(.+)$/);if(o){c.date=o[1].trim();continue}const l=d.match(/^\s+reason:\s*"?(.*?)"?\s*$/);if(l){c.reason=l[1];continue}/^\w/.test(d)&&(g=!1,c.version!=null&&s.push(c),c={});continue}const h=d.match(/^(\w+):\s*(.*)$/);if(h){const p=h[1];let o=h[2];typeof o=="string"&&o.startsWith("[")&&o.endsWith("]")?o=o.slice(1,-1).split(",").map(l=>l.trim().replace(/^"|"$/g,"")).filter(l=>l.length>0):typeof o=="string"&&/^\d+$/.test(o)?o=parseInt(o,10):typeof o=="string"&&o.startsWith('"')&&o.endsWith('"')&&(o=o.slice(1,-1)),a[p]=o}}return c.version!=null&&s.push(c),{frontmatter:{title:a.title??"Untitled",category:a.category??"notes",tags:a.tags??[],created:a.created??"",updated:a.updated??"",version:a.version??1,origin:"curated",changelog:s},content:r}}}export{k as CuratedKnowledgeManager};
@@ -1,134 +1 @@
1
- import { parseArgs } from "node:util";
2
- import { loadConfig } from "./config.js";
3
- import { createMcpServer, createServer, initializeKnowledgeBase } from "./server.js";
4
- const { values } = parseArgs({
5
- options: {
6
- transport: { type: "string", default: process.env.KB_TRANSPORT ?? "stdio" },
7
- port: { type: "string", default: process.env.KB_PORT ?? "3210" }
8
- }
9
- });
10
- async function main() {
11
- console.error("[KB] Starting MCP Knowledge Base server...");
12
- const config = loadConfig();
13
- console.error(
14
- `[KB] Config loaded: ${config.sources.length} source(s), store at ${config.store.path}`
15
- );
16
- if (values.transport === "http") {
17
- const { StreamableHTTPServerTransport } = await import("@modelcontextprotocol/sdk/server/streamableHttp.js");
18
- const express = (await import("express")).default;
19
- const kb = await initializeKnowledgeBase(config);
20
- const mcpServer = createMcpServer(kb, config);
21
- console.error("[KB] MCP server configured with 46 tools and 2 resources");
22
- const app = express();
23
- app.use(express.json());
24
- app.use((_req, res, next) => {
25
- res.setHeader("Access-Control-Allow-Origin", process.env.KB_CORS_ORIGIN ?? "*");
26
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
27
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
28
- if (_req.method === "OPTIONS") {
29
- res.status(204).end();
30
- return;
31
- }
32
- next();
33
- });
34
- app.get("/health", (_req, res) => {
35
- res.json({ status: "ok" });
36
- });
37
- app.post("/mcp", async (req, res) => {
38
- try {
39
- const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
40
- await mcpServer.connect(transport);
41
- await transport.handleRequest(req, res, req.body);
42
- res.on("close", () => {
43
- transport.close();
44
- });
45
- } catch (err) {
46
- console.error("[KB] MCP handler error:", err);
47
- if (!res.headersSent) {
48
- res.status(500).json({
49
- jsonrpc: "2.0",
50
- error: { code: -32603, message: "Internal server error" },
51
- id: null
52
- });
53
- }
54
- }
55
- });
56
- app.get("/mcp", (_req, res) => {
57
- res.writeHead(405).end(
58
- JSON.stringify({
59
- jsonrpc: "2.0",
60
- error: { code: -32e3, message: "Method not allowed." },
61
- id: null
62
- })
63
- );
64
- });
65
- app.delete("/mcp", (_req, res) => {
66
- res.writeHead(405).end(
67
- JSON.stringify({
68
- jsonrpc: "2.0",
69
- error: { code: -32e3, message: "Method not allowed." },
70
- id: null
71
- })
72
- );
73
- });
74
- const port = Number(values.port);
75
- const httpServer = app.listen(port, () => {
76
- console.error(`[KB] MCP server listening on http://0.0.0.0:${port}/mcp`);
77
- const runInitialIndex = async () => {
78
- try {
79
- const sourcePaths = config.sources.map((s) => s.path).join(", ");
80
- console.error(`[KB] Running initial index for sources: ${sourcePaths}`);
81
- const result = await kb.indexer.index(config, (p) => {
82
- if (p.phase === "crawling" || p.phase === "done") return;
83
- if (p.phase === "chunking" && p.currentFile) {
84
- console.error(`[KB] [${p.filesProcessed + 1}/${p.filesTotal}] ${p.currentFile}`);
85
- }
86
- });
87
- console.error(
88
- `[KB] Indexed ${result.filesProcessed} files (${result.filesSkipped} skipped, ${result.chunksCreated} chunks) in ${(result.durationMs / 1e3).toFixed(1)}s`
89
- );
90
- try {
91
- const curatedResult = await kb.curated.reindexAll();
92
- console.error(
93
- `[KB] Curated re-index: ${curatedResult.indexed} entries restored to vector store`
94
- );
95
- } catch (curatedErr) {
96
- console.error("[KB] Curated re-index failed:", curatedErr);
97
- }
98
- } catch (err) {
99
- console.error("[KB] Initial index failed (will retry on kb_reindex):", err);
100
- }
101
- };
102
- runInitialIndex();
103
- });
104
- const shutdown = async (signal) => {
105
- console.error(`[KB] ${signal} received \u2014 shutting down...`);
106
- httpServer.close();
107
- await mcpServer.close();
108
- await kb.store.close();
109
- await kb.embedder.shutdown();
110
- process.exit(0);
111
- };
112
- process.on("SIGINT", () => shutdown("SIGINT"));
113
- process.on("SIGTERM", () => shutdown("SIGTERM"));
114
- } else {
115
- const { server, runInitialIndex } = await createServer(config);
116
- const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
117
- const transport = new StdioServerTransport();
118
- await server.connect(transport);
119
- console.error("[KB] MCP server started (stdio)");
120
- const autoIndex = process.env.KB_AUTO_INDEX !== "false";
121
- if (autoIndex) {
122
- runInitialIndex();
123
- } else {
124
- console.error(
125
- "[KB] Auto-index disabled (KB_AUTO_INDEX=false). Use kb_reindex to index manually."
126
- );
127
- }
128
- }
129
- }
130
- main().catch((err) => {
131
- console.error("[KB] Fatal error:", err);
132
- process.exit(1);
133
- });
134
- //# sourceMappingURL=index.js.map
1
+ import{parseArgs as m}from"node:util";import{loadConfig as f}from"./config.js";import{createMcpServer as w,createServer as x,initializeKnowledgeBase as g}from"./server.js";const{values:u}=m({options:{transport:{type:"string",default:process.env.KB_TRANSPORT??"stdio"},port:{type:"string",default:process.env.KB_PORT??"3210"}}});async function I(){console.error("[KB] Starting MCP Knowledge Base server...");const t=f();if(console.error(`[KB] Config loaded: ${t.sources.length} source(s), store at ${t.store.path}`),u.transport==="http"){const{StreamableHTTPServerTransport:d}=await import("@modelcontextprotocol/sdk/server/streamableHttp.js"),a=(await import("express")).default,i=await g(t),c=w(i,t);console.error("[KB] MCP server configured with 46 tools and 2 resources");const n=a();n.use(a.json()),n.use((s,e,o)=>{if(e.setHeader("Access-Control-Allow-Origin",process.env.KB_CORS_ORIGIN??"*"),e.setHeader("Access-Control-Allow-Methods","GET, POST, DELETE, OPTIONS"),e.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),s.method==="OPTIONS"){e.status(204).end();return}o()}),n.get("/health",(s,e)=>{e.json({status:"ok"})}),n.post("/mcp",async(s,e)=>{try{const o=new d({sessionIdGenerator:void 0});await c.connect(o),await o.handleRequest(s,e,s.body),e.on("close",()=>{o.close()})}catch(o){console.error("[KB] MCP handler error:",o),e.headersSent||e.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:"Internal server error"},id:null})}}),n.get("/mcp",(s,e)=>{e.writeHead(405).end(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed."},id:null}))}),n.delete("/mcp",(s,e)=>{e.writeHead(405).end(JSON.stringify({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed."},id:null}))});const l=Number(u.port),h=n.listen(l,()=>{console.error(`[KB] MCP server listening on http://0.0.0.0:${l}/mcp`),(async()=>{try{const e=t.sources.map(r=>r.path).join(", ");console.error(`[KB] Running initial index for sources: ${e}`);const o=await i.indexer.index(t,r=>{r.phase==="crawling"||r.phase==="done"||r.phase==="chunking"&&r.currentFile&&console.error(`[KB] [${r.filesProcessed+1}/${r.filesTotal}] ${r.currentFile}`)});console.error(`[KB] Indexed ${o.filesProcessed} files (${o.filesSkipped} skipped, ${o.chunksCreated} chunks) in ${(o.durationMs/1e3).toFixed(1)}s`);try{const r=await i.curated.reindexAll();console.error(`[KB] Curated re-index: ${r.indexed} entries restored to vector store`)}catch(r){console.error("[KB] Curated re-index failed:",r)}}catch(e){console.error("[KB] Initial index failed (will retry on kb_reindex):",e)}})()}),p=async s=>{console.error(`[KB] ${s} received \u2014 shutting down...`),h.close(),await c.close(),await i.store.close(),await i.embedder.shutdown(),process.exit(0)};process.on("SIGINT",()=>p("SIGINT")),process.on("SIGTERM",()=>p("SIGTERM"))}else{const{server:d,runInitialIndex:a}=await x(t),{StdioServerTransport:i}=await import("@modelcontextprotocol/sdk/server/stdio.js"),c=new i;await d.connect(c),console.error("[KB] MCP server started (stdio)"),process.env.KB_AUTO_INDEX!=="false"?a():console.error("[KB] Auto-index disabled (KB_AUTO_INDEX=false). Use kb_reindex to index manually.")}}I().catch(t=>{console.error("[KB] Fatal error:",t),process.exit(1)});
@@ -1,38 +1 @@
1
- import { replayAppend } from "@kb/tools";
2
- function installReplayInterceptor(server) {
3
- const original = server.registerTool.bind(server);
4
- server.registerTool = (name, config, handler) => {
5
- const wrappedHandler = async (args, extra) => {
6
- const start = Date.now();
7
- try {
8
- const result = await handler(args, extra);
9
- replayAppend({
10
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11
- source: "mcp",
12
- tool: name,
13
- input: JSON.stringify(args),
14
- durationMs: Date.now() - start,
15
- status: "ok",
16
- output: JSON.stringify(result).slice(0, 200)
17
- });
18
- return result;
19
- } catch (err) {
20
- replayAppend({
21
- ts: (/* @__PURE__ */ new Date()).toISOString(),
22
- source: "mcp",
23
- tool: name,
24
- input: JSON.stringify(args),
25
- durationMs: Date.now() - start,
26
- status: "error",
27
- output: err instanceof Error ? err.message : String(err)
28
- });
29
- throw err;
30
- }
31
- };
32
- return original(name, config, wrappedHandler);
33
- };
34
- }
35
- export {
36
- installReplayInterceptor
37
- };
38
- //# sourceMappingURL=replay-interceptor.js.map
1
+ import{replayAppend as s}from"../../tools/dist/index.js";function w(n){const i=n.registerTool.bind(n);n.registerTool=(o,a,p)=>i(o,a,async(r,c)=>{const e=Date.now();try{const t=await p(r,c);return s({ts:new Date().toISOString(),source:"mcp",tool:o,input:JSON.stringify(r),durationMs:Date.now()-e,status:"ok",output:JSON.stringify(t).slice(0,200)}),t}catch(t){throw s({ts:new Date().toISOString(),source:"mcp",tool:o,input:JSON.stringify(r),durationMs:Date.now()-e,status:"error",output:t instanceof Error?t.message:String(t)}),t}})}export{w as installReplayInterceptor};
@@ -1,40 +1,2 @@
1
- function registerResources(server, store) {
2
- server.resource(
3
- "kb-status",
4
- "kb://status",
5
- { description: "Current knowledge base status and statistics", mimeType: "text/plain" },
6
- async () => {
7
- const stats = await store.getStats();
8
- return {
9
- contents: [
10
- {
11
- uri: "kb://status",
12
- text: `Knowledge Base: ${stats.totalRecords} records from ${stats.totalFiles} files. Last indexed: ${stats.lastIndexedAt ?? "Never"}`,
13
- mimeType: "text/plain"
14
- }
15
- ]
16
- };
17
- }
18
- );
19
- server.resource(
20
- "kb-file-tree",
21
- "kb://file-tree",
22
- { description: "List of all indexed source files", mimeType: "text/plain" },
23
- async () => {
24
- const paths = await store.listSourcePaths();
25
- return {
26
- contents: [
27
- {
28
- uri: "kb://file-tree",
29
- text: paths.sort().join("\n"),
30
- mimeType: "text/plain"
31
- }
32
- ]
33
- };
34
- }
35
- );
36
- }
37
- export {
38
- registerResources
39
- };
40
- //# sourceMappingURL=resources.js.map
1
+ function r(t,s){t.resource("kb-status","kb://status",{description:"Current knowledge base status and statistics",mimeType:"text/plain"},async()=>{const e=await s.getStats();return{contents:[{uri:"kb://status",text:`Knowledge Base: ${e.totalRecords} records from ${e.totalFiles} files. Last indexed: ${e.lastIndexedAt??"Never"}`,mimeType:"text/plain"}]}}),t.resource("kb-file-tree","kb://file-tree",{description:"List of all indexed source files",mimeType:"text/plain"},async()=>({contents:[{uri:"kb://file-tree",text:(await s.listSourcePaths()).sort().join(`
2
+ `),mimeType:"text/plain"}]}))}export{r as registerResources};