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.
- package/README.md +171 -0
- package/dhm.js +2 -0
- package/dist/cli/commands.d.ts +11 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +112 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +120 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +76 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/index/builder.d.ts +25 -0
- package/dist/index/builder.d.ts.map +1 -0
- package/dist/index/builder.js +40 -0
- package/dist/index/builder.js.map +1 -0
- package/dist/index/parser.d.ts +20 -0
- package/dist/index/parser.d.ts.map +1 -0
- package/dist/index/parser.js +162 -0
- package/dist/index/parser.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +92 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +97 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +57 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +70 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/search/cache.d.ts +12 -0
- package/dist/search/cache.d.ts.map +1 -0
- package/dist/search/cache.js +36 -0
- package/dist/search/cache.js.map +1 -0
- package/dist/search/exact.d.ts +3 -0
- package/dist/search/exact.d.ts.map +1 -0
- package/dist/search/exact.js +56 -0
- package/dist/search/exact.js.map +1 -0
- package/dist/search/graph.d.ts +6 -0
- package/dist/search/graph.d.ts.map +1 -0
- package/dist/search/graph.js +137 -0
- package/dist/search/graph.js.map +1 -0
- package/dist/search/pipeline.d.ts +18 -0
- package/dist/search/pipeline.d.ts.map +1 -0
- package/dist/search/pipeline.js +99 -0
- package/dist/search/pipeline.js.map +1 -0
- package/dist/search/semantic.d.ts +4 -0
- package/dist/search/semantic.d.ts.map +1 -0
- package/dist/search/semantic.js +120 -0
- package/dist/search/semantic.js.map +1 -0
- package/dist/sync/base.d.ts +21 -0
- package/dist/sync/base.d.ts.map +1 -0
- package/dist/sync/base.js +8 -0
- package/dist/sync/base.js.map +1 -0
- package/dist/sync/confluence.d.ts +3 -0
- package/dist/sync/confluence.d.ts.map +1 -0
- package/dist/sync/confluence.js +111 -0
- package/dist/sync/confluence.js.map +1 -0
- package/dist/sync/git-wiki.d.ts +3 -0
- package/dist/sync/git-wiki.d.ts.map +1 -0
- package/dist/sync/git-wiki.js +103 -0
- package/dist/sync/git-wiki.js.map +1 -0
- package/dist/sync/google-docs.d.ts +3 -0
- package/dist/sync/google-docs.d.ts.map +1 -0
- package/dist/sync/google-docs.js +87 -0
- package/dist/sync/google-docs.js.map +1 -0
- package/dist/sync/notion.d.ts +3 -0
- package/dist/sync/notion.d.ts.map +1 -0
- package/dist/sync/notion.js +126 -0
- package/dist/sync/notion.js.map +1 -0
- package/dist/sync/slack.d.ts +3 -0
- package/dist/sync/slack.d.ts.map +1 -0
- package/dist/sync/slack.js +92 -0
- package/dist/sync/slack.js.map +1 -0
- package/dist/sync/state.d.ts +25 -0
- package/dist/sync/state.d.ts.map +1 -0
- package/dist/sync/state.js +80 -0
- package/dist/sync/state.js.map +1 -0
- package/dist/utils/fs.d.ts +8 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +43 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +7 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +39 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +21 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +82 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/ui.html +210 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
20
|
+
.replace(/"/g, '"').replace(/'/g, "'").replace(/ /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
|