docs-hub-mcp 1.0.14

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 (96) hide show
  1. package/README.md +171 -0
  2. package/dhm.js +2 -0
  3. package/dist/cli/commands.d.ts +11 -0
  4. package/dist/cli/commands.d.ts.map +1 -0
  5. package/dist/cli/commands.js +112 -0
  6. package/dist/cli/commands.js.map +1 -0
  7. package/dist/cli/config.d.ts +120 -0
  8. package/dist/cli/config.d.ts.map +1 -0
  9. package/dist/cli/config.js +76 -0
  10. package/dist/cli/config.js.map +1 -0
  11. package/dist/index/builder.d.ts +25 -0
  12. package/dist/index/builder.d.ts.map +1 -0
  13. package/dist/index/builder.js +40 -0
  14. package/dist/index/builder.js.map +1 -0
  15. package/dist/index/parser.d.ts +20 -0
  16. package/dist/index/parser.d.ts.map +1 -0
  17. package/dist/index/parser.js +162 -0
  18. package/dist/index/parser.js.map +1 -0
  19. package/dist/index.d.ts +3 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +92 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/mcp/server.d.ts +3 -0
  24. package/dist/mcp/server.d.ts.map +1 -0
  25. package/dist/mcp/server.js +97 -0
  26. package/dist/mcp/server.js.map +1 -0
  27. package/dist/mcp/tools.d.ts +57 -0
  28. package/dist/mcp/tools.d.ts.map +1 -0
  29. package/dist/mcp/tools.js +70 -0
  30. package/dist/mcp/tools.js.map +1 -0
  31. package/dist/search/cache.d.ts +12 -0
  32. package/dist/search/cache.d.ts.map +1 -0
  33. package/dist/search/cache.js +36 -0
  34. package/dist/search/cache.js.map +1 -0
  35. package/dist/search/exact.d.ts +3 -0
  36. package/dist/search/exact.d.ts.map +1 -0
  37. package/dist/search/exact.js +56 -0
  38. package/dist/search/exact.js.map +1 -0
  39. package/dist/search/graph.d.ts +6 -0
  40. package/dist/search/graph.d.ts.map +1 -0
  41. package/dist/search/graph.js +137 -0
  42. package/dist/search/graph.js.map +1 -0
  43. package/dist/search/pipeline.d.ts +18 -0
  44. package/dist/search/pipeline.d.ts.map +1 -0
  45. package/dist/search/pipeline.js +99 -0
  46. package/dist/search/pipeline.js.map +1 -0
  47. package/dist/search/semantic.d.ts +4 -0
  48. package/dist/search/semantic.d.ts.map +1 -0
  49. package/dist/search/semantic.js +120 -0
  50. package/dist/search/semantic.js.map +1 -0
  51. package/dist/sync/base.d.ts +21 -0
  52. package/dist/sync/base.d.ts.map +1 -0
  53. package/dist/sync/base.js +8 -0
  54. package/dist/sync/base.js.map +1 -0
  55. package/dist/sync/confluence.d.ts +3 -0
  56. package/dist/sync/confluence.d.ts.map +1 -0
  57. package/dist/sync/confluence.js +111 -0
  58. package/dist/sync/confluence.js.map +1 -0
  59. package/dist/sync/git-wiki.d.ts +3 -0
  60. package/dist/sync/git-wiki.d.ts.map +1 -0
  61. package/dist/sync/git-wiki.js +103 -0
  62. package/dist/sync/git-wiki.js.map +1 -0
  63. package/dist/sync/google-docs.d.ts +3 -0
  64. package/dist/sync/google-docs.d.ts.map +1 -0
  65. package/dist/sync/google-docs.js +87 -0
  66. package/dist/sync/google-docs.js.map +1 -0
  67. package/dist/sync/notion.d.ts +3 -0
  68. package/dist/sync/notion.d.ts.map +1 -0
  69. package/dist/sync/notion.js +126 -0
  70. package/dist/sync/notion.js.map +1 -0
  71. package/dist/sync/slack.d.ts +3 -0
  72. package/dist/sync/slack.d.ts.map +1 -0
  73. package/dist/sync/slack.js +92 -0
  74. package/dist/sync/slack.js.map +1 -0
  75. package/dist/sync/state.d.ts +25 -0
  76. package/dist/sync/state.d.ts.map +1 -0
  77. package/dist/sync/state.js +80 -0
  78. package/dist/sync/state.js.map +1 -0
  79. package/dist/utils/fs.d.ts +8 -0
  80. package/dist/utils/fs.d.ts.map +1 -0
  81. package/dist/utils/fs.js +43 -0
  82. package/dist/utils/fs.js.map +1 -0
  83. package/dist/utils/git.d.ts +7 -0
  84. package/dist/utils/git.d.ts.map +1 -0
  85. package/dist/utils/git.js +39 -0
  86. package/dist/utils/git.js.map +1 -0
  87. package/dist/utils/logger.d.ts +16 -0
  88. package/dist/utils/logger.d.ts.map +1 -0
  89. package/dist/utils/logger.js +21 -0
  90. package/dist/utils/logger.js.map +1 -0
  91. package/dist/web/server.d.ts +2 -0
  92. package/dist/web/server.d.ts.map +1 -0
  93. package/dist/web/server.js +82 -0
  94. package/dist/web/server.js.map +1 -0
  95. package/dist/web/ui.html +210 -0
  96. package/package.json +38 -0
@@ -0,0 +1,56 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { relative } from 'node:path';
3
+ export function searchExact(query, kbPath, maxResults = 10) {
4
+ if (!query.trim())
5
+ return [];
6
+ try {
7
+ const safeQuery = query.replace(/"/g, '\\"');
8
+ const output = execSync(`rg --ignore-case --max-count=${maxResults} --line-number --context=1 --no-heading "${safeQuery}" "${kbPath}"`, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 10000 });
9
+ return parseRgOutput(output, kbPath, maxResults);
10
+ }
11
+ catch (err) {
12
+ const status = err.status;
13
+ if (status === 1)
14
+ return [];
15
+ throw err;
16
+ }
17
+ }
18
+ function parseRgOutput(output, kbPath, maxResults) {
19
+ const results = [];
20
+ const seen = new Set();
21
+ for (const block of output.split('--\n')) {
22
+ if (results.length >= maxResults)
23
+ break;
24
+ const lines = block.trim().split('\n');
25
+ if (lines.length === 0)
26
+ continue;
27
+ const mainMatch = lines.find(l => !l.startsWith('--') && /^.+?[-:]\d+[-:]/.test(l));
28
+ if (!mainMatch)
29
+ continue;
30
+ const parsed = parseRgLine(mainMatch);
31
+ if (!parsed || seen.has(parsed.file))
32
+ continue;
33
+ seen.add(parsed.file);
34
+ const relPath = relative(kbPath, parsed.file);
35
+ results.push({
36
+ path: relPath,
37
+ score: 0.95,
38
+ matchType: 'exact',
39
+ tier: 1,
40
+ snippet: block.trim(),
41
+ });
42
+ }
43
+ return results;
44
+ }
45
+ function parseRgLine(line) {
46
+ // Format: "file.md:42:content" or "file.md-42-content"
47
+ const match = line.match(/^(.+?)[:-](\d+)[:-](.+)$/);
48
+ if (!match)
49
+ return null;
50
+ return {
51
+ file: match[1],
52
+ line: parseInt(match[2], 10),
53
+ content: match[3],
54
+ };
55
+ }
56
+ //# sourceMappingURL=exact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact.js","sourceRoot":"","sources":["../../src/search/exact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AASrC,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,MAAc,EAAE,UAAU,GAAG,EAAE;IACxE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,MAAM,GAAG,QAAQ,CACrB,gCAAgC,UAAU,4CAA4C,SAAS,MAAM,MAAM,GAAG,EAC9G,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CACnE,CAAC;QAEF,OAAO,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,MAAM,MAAM,GAAI,GAA2B,CAAC,MAAM,CAAC;QACnD,IAAI,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,MAAc,EAAE,MAAc,EAAE,UAAkB;IACvE,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACzC,IAAI,OAAO,CAAC,MAAM,IAAI,UAAU;YAAE,MAAM;QAExC,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEjC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,IAAI,CAAC,SAAS;YAAE,SAAS;QAEzB,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,SAAS;QAC/C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAEtB,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAE9C,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,IAAI;YACX,SAAS,EAAE,OAAO;YAClB,IAAI,EAAE,CAAC;YACP,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,uDAAuD;IACvD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACd,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;KAClB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { KnowledgeGraph } from '../index/builder.js';
2
+ import type { SearchResult } from '../mcp/tools.js';
3
+ export declare function loadGraph(kbPath: string): KnowledgeGraph | null;
4
+ export declare function clearGraphCache(): void;
5
+ export declare function searchGraph(query: string, kbPath: string, maxResults?: number): SearchResult[];
6
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/search/graph.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAA0B,MAAM,qBAAqB,CAAC;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMpD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAsB/D;AAED,wBAAgB,eAAe,IAAI,IAAI,CAGtC;AAkCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,YAAY,EAAE,CAmF1F"}
@@ -0,0 +1,137 @@
1
+ import { readFileSync, existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import * as yaml from 'js-yaml';
4
+ import { logger } from '../utils/logger.js';
5
+ let cachedGraph = null;
6
+ let cachedGraphPath = null;
7
+ export function loadGraph(kbPath) {
8
+ const indexPath = resolve(kbPath, '.dhm-index.yaml');
9
+ if (cachedGraph && cachedGraphPath === indexPath) {
10
+ return cachedGraph;
11
+ }
12
+ if (!existsSync(indexPath)) {
13
+ logger.debug('No .dhm-index.yaml found, skipping graph search');
14
+ return null;
15
+ }
16
+ try {
17
+ const content = readFileSync(indexPath, 'utf-8');
18
+ const graph = yaml.load(content);
19
+ cachedGraph = graph;
20
+ cachedGraphPath = indexPath;
21
+ return graph;
22
+ }
23
+ catch {
24
+ logger.warn('Failed to load .dhm-index.yaml');
25
+ return null;
26
+ }
27
+ }
28
+ export function clearGraphCache() {
29
+ cachedGraph = null;
30
+ cachedGraphPath = null;
31
+ }
32
+ function tokenize(query) {
33
+ return query
34
+ .toLowerCase()
35
+ .replace(/[?#*_`\[\]()]/g, '')
36
+ .split(/\s+/)
37
+ .filter(t => t.length > 1);
38
+ }
39
+ function matchTags(tokens, tags) {
40
+ let score = 0;
41
+ for (const token of tokens) {
42
+ for (const tag of tags) {
43
+ if (tag.includes(token) || token.includes(tag)) {
44
+ score += 1;
45
+ }
46
+ }
47
+ }
48
+ return score;
49
+ }
50
+ function matchKeywords(tokens, keywords) {
51
+ let score = 0;
52
+ for (const token of tokens) {
53
+ for (const kw of keywords) {
54
+ if (kw.includes(token) || token.includes(kw)) {
55
+ score += 1;
56
+ }
57
+ }
58
+ }
59
+ return score;
60
+ }
61
+ export function searchGraph(query, kbPath, maxResults = 10) {
62
+ const graph = loadGraph(kbPath);
63
+ if (!graph || graph.documents.length === 0)
64
+ return [];
65
+ const tokens = tokenize(query);
66
+ if (tokens.length === 0)
67
+ return [];
68
+ const results = new Map();
69
+ for (const doc of graph.documents) {
70
+ const tagScore = matchTags(tokens, doc.tags);
71
+ const kwScore = matchKeywords(tokens, doc.keywords);
72
+ const totalScore = tagScore * 1.5 + kwScore * 1.2;
73
+ if (totalScore > 0) {
74
+ const normalized = totalScore / (tokens.length * 2);
75
+ results.set(doc.path, {
76
+ path: doc.path,
77
+ score: Math.min(normalized, 0.9),
78
+ matchType: tagScore > 0 ? 'tag' : 'keyword',
79
+ tier: 2,
80
+ snippet: doc.title,
81
+ });
82
+ }
83
+ // Section-level match bonus
84
+ for (const section of doc.sections) {
85
+ const secScore = matchKeywords(tokens, section.keywords);
86
+ if (secScore > 0) {
87
+ const existing = results.get(doc.path);
88
+ if (existing) {
89
+ existing.score = Math.min(existing.score + 0.1, 0.95);
90
+ existing.snippet += ` > ${section.title}`;
91
+ }
92
+ else {
93
+ results.set(doc.path, {
94
+ path: doc.path,
95
+ score: Math.min(secScore / tokens.length, 0.85),
96
+ matchType: 'keyword',
97
+ tier: 2,
98
+ snippet: `${doc.title} > ${section.title}`,
99
+ });
100
+ }
101
+ }
102
+ }
103
+ }
104
+ // Expand related documents from top matches
105
+ const topPaths = [...results.entries()]
106
+ .sort((a, b) => b[1].score - a[1].score)
107
+ .slice(0, 3)
108
+ .map(([path]) => path);
109
+ for (const path of topPaths) {
110
+ const doc = graph.documents.find(d => d.path === path);
111
+ if (!doc)
112
+ continue;
113
+ for (const { path: relPath, relation } of doc.related) {
114
+ if (!results.has(relPath)) {
115
+ // Normalize the related path
116
+ const dir = path.split('/').slice(0, -1).join('/');
117
+ const resolved = dir ? `${dir}/${relPath}` : relPath;
118
+ const clean = resolved.replace(/\.\//g, '');
119
+ // Check if the related doc exists in graph
120
+ const relDoc = graph.documents.find(d => d.path === clean || d.path.endsWith(relPath));
121
+ if (relDoc && !results.has(relDoc.path)) {
122
+ results.set(relDoc.path, {
123
+ path: relDoc.path,
124
+ score: 0.4,
125
+ matchType: 'related',
126
+ tier: 2,
127
+ snippet: `${relation} (related to ${path})`,
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }
133
+ return [...results.values()]
134
+ .sort((a, b) => b.score - a.score)
135
+ .slice(0, maxResults);
136
+ }
137
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/search/graph.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { SearchResponse } from '../mcp/tools.js';
2
+ import type { AppConfig } from '../cli/config.js';
3
+ export interface SearchOptions {
4
+ maxResults?: number;
5
+ minResults?: number;
6
+ tier?: 1 | 2 | 3;
7
+ }
8
+ export declare class SearchPipeline {
9
+ private kbPath;
10
+ private cache;
11
+ private maxResults;
12
+ private minResults;
13
+ constructor(kbPath: string, config: AppConfig);
14
+ search(query: string, opts?: SearchOptions): Promise<SearchResponse>;
15
+ prewarm(queries: string[]): void;
16
+ clearCache(): void;
17
+ }
18
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/search/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAMpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAElD,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAClB;AAID,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,UAAU,CAAS;gBAEf,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS;IAOvC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,cAAc,CAAC;IA6D1E,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAUhC,UAAU,IAAI,IAAI;CAGnB"}
@@ -0,0 +1,99 @@
1
+ import { searchExact } from './exact.js';
2
+ import { searchGraph } from './graph.js';
3
+ import { searchSemantic } from './semantic.js';
4
+ import { SearchCache } from './cache.js';
5
+ import { logger } from '../utils/logger.js';
6
+ const TIER_WEIGHTS = { 1: 1.0, 2: 0.8, 3: 0.5 };
7
+ export class SearchPipeline {
8
+ kbPath;
9
+ cache;
10
+ maxResults;
11
+ minResults;
12
+ constructor(kbPath, config) {
13
+ this.kbPath = kbPath;
14
+ this.maxResults = config.maxResults;
15
+ this.minResults = config.minResults;
16
+ this.cache = new SearchCache(config.cache.maxSize, config.cache.ttl);
17
+ }
18
+ async search(query, opts) {
19
+ const maxR = opts?.maxResults ?? this.maxResults;
20
+ const minR = opts?.minResults ?? this.minResults;
21
+ const startTime = performance.now();
22
+ // Check cache first
23
+ const cached = this.cache.get(query);
24
+ if (cached) {
25
+ logger.debug('cache hit', { query });
26
+ return {
27
+ results: cached.slice(0, maxR),
28
+ totalTime: performance.now() - startTime,
29
+ tierUsed: 0,
30
+ };
31
+ }
32
+ const allResults = [];
33
+ let tierUsed = 0;
34
+ // Tier 1: Exact match
35
+ if (!opts?.tier || opts.tier >= 1) {
36
+ const exactResults = searchExact(query, this.kbPath, maxR);
37
+ allResults.push(...exactResults.map(r => ({ ...r, score: r.score * TIER_WEIGHTS[1] })));
38
+ tierUsed = 1;
39
+ }
40
+ // Tier 2: Knowledge graph
41
+ if ((!opts?.tier || opts.tier >= 2) && (!opts?.tier || opts.tier === 2 || allResults.length < minR)) {
42
+ const graphResults = searchGraph(query, this.kbPath, maxR);
43
+ allResults.push(...graphResults.map(r => ({ ...r, score: r.score * TIER_WEIGHTS[2] })));
44
+ if (graphResults.length > 0)
45
+ tierUsed = Math.max(tierUsed, 2);
46
+ }
47
+ // Tier 3: AI Semantic fallback
48
+ if ((!opts?.tier || opts.tier >= 3) && allResults.length < minR) {
49
+ const semanticResults = searchSemantic(query, this.kbPath, maxR);
50
+ allResults.push(...semanticResults.map(r => ({ ...r, score: r.score * TIER_WEIGHTS[3] })));
51
+ if (semanticResults.length > 0)
52
+ tierUsed = Math.max(tierUsed, 3);
53
+ }
54
+ const final = deduplicateAndRank(allResults).slice(0, maxR);
55
+ // Cache results
56
+ if (final.length > 0) {
57
+ this.cache.set(query, final);
58
+ }
59
+ logger.info('search complete', {
60
+ query,
61
+ totalResults: final.length,
62
+ tierUsed,
63
+ totalTime: Math.round(performance.now() - startTime),
64
+ });
65
+ return {
66
+ results: final,
67
+ totalTime: performance.now() - startTime,
68
+ tierUsed,
69
+ };
70
+ }
71
+ prewarm(queries) {
72
+ for (const query of queries) {
73
+ const results = searchExact(query, this.kbPath, 10);
74
+ if (results.length > 0) {
75
+ this.cache.set(query, results);
76
+ }
77
+ }
78
+ logger.info('prewarm complete', { queries: queries.length, cacheSize: this.cache.size });
79
+ }
80
+ clearCache() {
81
+ this.cache.clear();
82
+ }
83
+ }
84
+ function deduplicateAndRank(results) {
85
+ const merged = new Map();
86
+ for (const r of results) {
87
+ const existing = merged.get(r.path);
88
+ if (existing) {
89
+ existing.score = Math.max(existing.score, r.score);
90
+ if (r.tier < existing.tier)
91
+ existing.tier = r.tier;
92
+ }
93
+ else {
94
+ merged.set(r.path, { ...r });
95
+ }
96
+ }
97
+ return [...merged.values()].sort((a, b) => b.score - a.score);
98
+ }
99
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/search/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { SearchResult } from '../mcp/tools.js';
2
+ export declare function clearSemanticCache(): void;
3
+ export declare function searchSemantic(query: string, kbPath: string, maxResults?: number): SearchResult[];
4
+ //# sourceMappingURL=semantic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../../src/search/semantic.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAmEpD,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AA0BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,YAAY,EAAE,CA6C7F"}
@@ -0,0 +1,120 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { parseAllDocuments } from '../index/parser.js';
4
+ import { logger } from '../utils/logger.js';
5
+ let summariesCache = null;
6
+ let summariesCachePath = null;
7
+ function tokenizeText(text) {
8
+ return text
9
+ .toLowerCase()
10
+ .replace(/[#*_`\[\]()\n\r]/g, ' ')
11
+ .split(/\s+/)
12
+ .filter(t => t.length > 1);
13
+ }
14
+ function buildTokenFreq(tokens) {
15
+ const freq = new Map();
16
+ for (const t of tokens) {
17
+ freq.set(t, (freq.get(t) || 0) + 1);
18
+ }
19
+ return freq;
20
+ }
21
+ function loadSummaries(kbPath) {
22
+ if (summariesCache && summariesCachePath === kbPath) {
23
+ return summariesCache;
24
+ }
25
+ const docs = parseAllDocuments(kbPath);
26
+ const summaries = [];
27
+ for (const doc of docs) {
28
+ // Read first paragraph from the file
29
+ let firstPara = '';
30
+ try {
31
+ const content = readFileSync(resolve(kbPath, doc.path), 'utf-8');
32
+ const lines = content.split('\n');
33
+ const textStart = lines.findIndex(l => l.trim() && !l.startsWith('#'));
34
+ if (textStart >= 0) {
35
+ firstPara = lines[textStart].trim();
36
+ }
37
+ }
38
+ catch {
39
+ // skip
40
+ }
41
+ const text = `${doc.title} ${firstPara} ${doc.keywords.join(' ')}`;
42
+ const tokens = tokenizeText(text);
43
+ summaries.push({
44
+ path: doc.path,
45
+ title: doc.title,
46
+ tokens: buildTokenFreq(tokens),
47
+ });
48
+ }
49
+ summariesCache = summaries;
50
+ summariesCachePath = kbPath;
51
+ logger.debug('Summaries loaded', { count: summaries.length });
52
+ return summaries;
53
+ }
54
+ export function clearSemanticCache() {
55
+ summariesCache = null;
56
+ summariesCachePath = null;
57
+ }
58
+ /**
59
+ * TF-IDF inspired similarity using word overlap with IDF-like weighting.
60
+ * Words that appear in fewer documents get higher weight (simple IDF).
61
+ */
62
+ function computeSimilarity(queryTokens, docTokens, totalDocs, docFreq) {
63
+ let score = 0;
64
+ const queryFreq = buildTokenFreq(queryTokens);
65
+ for (const [token, qf] of queryFreq) {
66
+ const df = docFreq.get(token) || 0;
67
+ if (df === 0)
68
+ continue;
69
+ const idf = Math.log(totalDocs / df);
70
+ const tf = docTokens.get(token) || 0;
71
+ // TF-IDF cosine component
72
+ score += qf * tf * idf;
73
+ }
74
+ // Normalize by query length
75
+ const norm = Math.sqrt(queryTokens.length * Math.log(totalDocs));
76
+ return norm > 0 ? score / norm : 0;
77
+ }
78
+ export function searchSemantic(query, kbPath, maxResults = 10) {
79
+ if (!query.trim())
80
+ return [];
81
+ const summaries = loadSummaries(kbPath);
82
+ if (summaries.length === 0)
83
+ return [];
84
+ const queryTokens = tokenizeText(query);
85
+ if (queryTokens.length === 0)
86
+ return [];
87
+ // Compute document frequencies across all summaries
88
+ const totalDocs = summaries.length;
89
+ const docFreq = new Map();
90
+ for (const s of summaries) {
91
+ for (const token of s.tokens.keys()) {
92
+ docFreq.set(token, (docFreq.get(token) || 0) + 1);
93
+ }
94
+ }
95
+ // Score each document
96
+ const scored = summaries.map(doc => ({
97
+ path: doc.path,
98
+ score: computeSimilarity(queryTokens, doc.tokens, totalDocs, docFreq),
99
+ title: doc.title,
100
+ }));
101
+ // Filter and rank
102
+ const results = scored
103
+ .filter(s => s.score > 0.05)
104
+ .sort((a, b) => b.score - a.score)
105
+ .slice(0, maxResults)
106
+ .map(s => ({
107
+ path: s.path,
108
+ score: Math.min(s.score * 0.7, 0.8), // Scale down — semantic is less precise
109
+ matchType: 'semantic',
110
+ tier: 3,
111
+ snippet: s.title,
112
+ }));
113
+ logger.debug('semantic search', {
114
+ query,
115
+ candidates: scored.length,
116
+ results: results.length,
117
+ });
118
+ return results;
119
+ }
120
+ //# sourceMappingURL=semantic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic.js","sourceRoot":"","sources":["../../src/search/semantic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { SourceConfig } from '../cli/config.js';
2
+ export interface SyncResult {
3
+ sourceName: string;
4
+ sourceType: string;
5
+ filesWritten: number;
6
+ filesUpdated: number;
7
+ filesDeleted: number;
8
+ errors: SyncError[];
9
+ duration: number;
10
+ incremental: boolean;
11
+ }
12
+ export interface SyncError {
13
+ message: string;
14
+ details?: string;
15
+ }
16
+ export interface SyncAdapter {
17
+ name: string;
18
+ sync(config: SourceConfig, kbPath: string, cwd: string, incremental?: boolean): Promise<SyncResult>;
19
+ }
20
+ export declare function createSyncResult(sourceName: string, sourceType: string): SyncResult;
21
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/sync/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;CACrG;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAMnF"}
@@ -0,0 +1,8 @@
1
+ export function createSyncResult(sourceName, sourceType) {
2
+ return {
3
+ sourceName, sourceType,
4
+ filesWritten: 0, filesUpdated: 0, filesDeleted: 0,
5
+ errors: [], duration: 0, incremental: false,
6
+ };
7
+ }
8
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/sync/base.ts"],"names":[],"mappings":"AAuBA,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,UAAkB;IACrE,OAAO;QACL,UAAU,EAAE,UAAU;QACtB,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC;QACjD,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK;KAC5C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type SyncAdapter } from './base.js';
2
+ export declare const confluenceAdapter: SyncAdapter;
3
+ //# sourceMappingURL=confluence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confluence.d.ts","sourceRoot":"","sources":["../../src/sync/confluence.ts"],"names":[],"mappings":"AAGA,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AA4C/D,eAAO,MAAM,iBAAiB,EAAE,WAqF/B,CAAC"}
@@ -0,0 +1,111 @@
1
+ import { resolve, join } from 'node:path';
2
+ import { logger } from '../utils/logger.js';
3
+ import { ensureDir, writeFile, fileExists } from '../utils/fs.js';
4
+ import { createSyncResult } from './base.js';
5
+ import { getSourceState, updateSourceState, buildFilesSnapshot } from './state.js';
6
+ function convertHtmlToMd(html) {
7
+ return html
8
+ .replace(/<h([1-4])[^>]*>/gi, (_, n) => '\n' + '#'.repeat(Number(n)) + ' ')
9
+ .replace(/<\/h[1-6]>/gi, '\n')
10
+ .replace(/<p[^>]*>/gi, '\n').replace(/<\/p>/gi, '\n')
11
+ .replace(/<br\s*\/?>/gi, '\n')
12
+ .replace(/<strong[^>]*>/gi, '**').replace(/<\/strong>/gi, '**')
13
+ .replace(/<em[^>]*>/gi, '*').replace(/<\/em>/gi, '*')
14
+ .replace(/<code[^>]*>/gi, '`').replace(/<\/code>/gi, '`')
15
+ .replace(/<pre[^>]*>/gi, '\n```\n').replace(/<\/pre>/gi, '\n```\n')
16
+ .replace(/<a[^>]*href="([^"]*)"[^>]*>/gi, '[$1](').replace(/<\/a>/gi, ')')
17
+ .replace(/<li[^>]*>/gi, '- ').replace(/<\/li>/gi, '\n')
18
+ .replace(/<[^>]+>/g, '')
19
+ .replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
20
+ .replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, ' ')
21
+ .replace(/\n{3,}/g, '\n\n').trim();
22
+ }
23
+ function sanitizeFilename(name) {
24
+ return name.replace(/[<>:"/\\|?*]/g, '-').replace(/\s+/g, '-').slice(0, 100);
25
+ }
26
+ export const confluenceAdapter = {
27
+ name: 'confluence',
28
+ async sync(config, kbPath, cwd, incremental) {
29
+ const result = createSyncResult(config.name, config.type);
30
+ result.incremental = !!incremental;
31
+ const startTime = performance.now();
32
+ const baseUrl = config.url || process.env.CONFLUENCE_URL;
33
+ const username = process.env.CONFLUENCE_USERNAME || process.env.CONFLUENCE_EMAIL;
34
+ const apiToken = process.env.CONFLUENCE_API_TOKEN || process.env.CONFLUENCE_TOKEN;
35
+ if (!baseUrl) {
36
+ result.errors.push({ message: 'Set CONFLUENCE_URL env var or "url" in config.' });
37
+ result.duration = performance.now() - startTime;
38
+ return result;
39
+ }
40
+ if (!apiToken) {
41
+ result.errors.push({ message: 'Set CONFLUENCE_API_TOKEN env var.' });
42
+ result.duration = performance.now() - startTime;
43
+ return result;
44
+ }
45
+ const prevState = getSourceState(kbPath, config.name);
46
+ try {
47
+ const auth = Buffer.from(`${username || 'api'}:${apiToken}`).toString('base64');
48
+ const headers = { Authorization: `Basic ${auth}`, Accept: 'application/json' };
49
+ const sourceDir = resolve(kbPath, config.name);
50
+ ensureDir(sourceDir);
51
+ let cql = `space=${config.folderId || 'currentSpace()'} and type=page`;
52
+ if (incremental && prevState.lastSync) {
53
+ cql += ` and lastModified > "${prevState.lastSync}"`;
54
+ }
55
+ let searchUrl = `${baseUrl}/wiki/rest/api/content/search?cql=${encodeURIComponent(cql)}&limit=50&expand=version`;
56
+ while (searchUrl) {
57
+ const searchResponse = await fetch(searchUrl, { headers });
58
+ if (!searchResponse.ok)
59
+ throw new Error(`Confluence API error: ${searchResponse.status}`);
60
+ const searchData = (await searchResponse.json());
61
+ if (searchData.results.length === 0 && incremental) {
62
+ logger.info('No modified pages', { source: config.name });
63
+ break;
64
+ }
65
+ for (const page of searchData.results) {
66
+ try {
67
+ const pageUrl = `${baseUrl}/wiki/rest/api/content/${page.id}?expand=body.storage,version,space`;
68
+ const pageResponse = await fetch(pageUrl, { headers });
69
+ if (!pageResponse.ok) {
70
+ result.errors.push({ message: `Page failed: ${page.title}` });
71
+ continue;
72
+ }
73
+ const pageData = (await pageResponse.json());
74
+ const mdContent = convertHtmlToMd(pageData.body?.storage?.value || '');
75
+ const lines = [
76
+ `# ${pageData.title}\n`,
77
+ `> Source: ${pageData._links.base}${pageData._links.webui}\n`,
78
+ `> Space: ${pageData.space?.key || 'unknown'}\n`,
79
+ `> Last updated: ${pageData.version.when}\n\n`,
80
+ mdContent,
81
+ ];
82
+ const filePath = join(sourceDir, `${sanitizeFilename(pageData.title)}.md`);
83
+ const existed = fileExists(filePath);
84
+ writeFile(filePath, lines.join('\n'));
85
+ if (existed)
86
+ result.filesUpdated++;
87
+ else
88
+ result.filesWritten++;
89
+ }
90
+ catch (err) {
91
+ result.errors.push({ message: `Page process failed: ${page.title}`, details: String(err) });
92
+ }
93
+ }
94
+ searchUrl = searchData._links.next ? `${baseUrl}/wiki${searchData._links.next}` : '';
95
+ }
96
+ const snapshot = buildFilesSnapshot(sourceDir);
97
+ updateSourceState(kbPath, config.name, config.type, {
98
+ filesSnapshot: snapshot,
99
+ totalSynced: result.filesWritten + result.filesUpdated,
100
+ totalErrors: result.errors.length,
101
+ });
102
+ logger.info('Confluence sync complete', { source: config.name, written: result.filesWritten, updated: result.filesUpdated });
103
+ }
104
+ catch (err) {
105
+ result.errors.push({ message: 'Confluence sync failed', details: String(err) });
106
+ }
107
+ result.duration = performance.now() - startTime;
108
+ return result;
109
+ },
110
+ };
111
+ //# sourceMappingURL=confluence.js.map