simple-dynamsoft-mcp 7.6.1 → 7.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/data/metadata/data-manifest.json +24 -24
- package/data/metadata/dynamsoft_sdks.json +3 -3
- package/package.json +4 -2
- package/scripts/eval-retrieval.mjs +64 -0
- package/scripts/lib/retrieval-eval.mjs +134 -0
- package/scripts/lib/scope-matcher.mjs +17 -0
- package/scripts/prebuild-rag-index.mjs +21 -4
- package/scripts/verify-doc-resources.mjs +2 -1
- package/src/data/repo-map.js +23 -4
- package/src/rag/config.js +4 -6
- package/src/rag/index.js +22 -2
- package/src/rag/lexical-provider.js +105 -53
- package/src/rag/search-utils.js +12 -2
- package/src/rag/vector-cache.js +77 -31
- package/src/server/resource-index/builders.js +56 -1
- package/src/server/resource-index/docs-loader.js +57 -4
- package/src/server/resource-index/shared-docs.js +161 -0
- package/src/server/resource-index.js +57 -37
- package/src/server/tools/register-index-tools.js +4 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveSiteLinks } from "./docs-loader.js";
|
|
4
|
+
|
|
5
|
+
// DBR's public API is built on shared Capture Vision (DCV) modules such as
|
|
6
|
+
// CaptureVisionRouter, whose reference pages live only in the DCV docs. DBR's
|
|
7
|
+
// own docs and navigation sidebars link to exactly the DCV pages DBR relies on,
|
|
8
|
+
// so those links are the dependency map: only linked pages are indexed under
|
|
9
|
+
// DBR, never the whole DCV corpus.
|
|
10
|
+
|
|
11
|
+
const DCV_CURRENT_DOCS_PREFIX = "https://www.dynamsoft.com/capture-vision/docs/";
|
|
12
|
+
|
|
13
|
+
// Sidebar include `sidelist-programming/programming-<segment>.html` scopes its
|
|
14
|
+
// links to one DBR platform; other includes apply to the whole edition.
|
|
15
|
+
const SIDEBAR_PLATFORM_BY_SEGMENT = {
|
|
16
|
+
javascript: "web",
|
|
17
|
+
android: "android",
|
|
18
|
+
oc: "ios",
|
|
19
|
+
ios: "ios",
|
|
20
|
+
flutter: "flutter",
|
|
21
|
+
maui: "maui",
|
|
22
|
+
"react-native": "react-native",
|
|
23
|
+
cpp: "cpp",
|
|
24
|
+
dotnet: "dotnet",
|
|
25
|
+
java: "java",
|
|
26
|
+
python: "python",
|
|
27
|
+
nodejs: "nodejs"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function normalizeDocUrl(url) {
|
|
31
|
+
// Some DBR links contain `//` inside the path (e.g. `api-reference//core/`).
|
|
32
|
+
let normalized = String(url || "").split("#")[0].split("?")[0].replace(/([^:])\/{2,}/g, "$1/");
|
|
33
|
+
if (!normalized) return "";
|
|
34
|
+
if (normalized.endsWith("/")) return `${normalized}index.html`;
|
|
35
|
+
const lastSegment = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
36
|
+
if (!lastSegment.includes(".")) normalized = `${normalized}.html`;
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractDcvLinks(text) {
|
|
41
|
+
const links = new Set();
|
|
42
|
+
const pattern = /https:\/\/www\.dynamsoft\.com\/capture-vision\/docs\/[^\s)"'<>\]`]+/g;
|
|
43
|
+
for (const match of String(text || "").matchAll(pattern)) {
|
|
44
|
+
const normalized = normalizeDocUrl(match[0]);
|
|
45
|
+
if (normalized.startsWith(DCV_CURRENT_DOCS_PREFIX)) links.add(normalized);
|
|
46
|
+
}
|
|
47
|
+
return links;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listIncludeFiles(dir) {
|
|
51
|
+
const files = [];
|
|
52
|
+
if (!existsSync(dir)) return files;
|
|
53
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
const fullPath = join(dir, entry.name);
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
files.push(...listIncludeFiles(fullPath));
|
|
57
|
+
} else if (/\.(html|md)$/i.test(entry.name)) {
|
|
58
|
+
files.push({ path: fullPath, name: entry.name });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sidebarPlatform(fileName) {
|
|
65
|
+
const match = String(fileName).match(/^programming-(.+)\.html$/i);
|
|
66
|
+
return match ? SIDEBAR_PLATFORM_BY_SEGMENT[match[1].toLowerCase()] || "" : "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// url -> Set of DBR platforms whose docs link to it ("*" = whole edition).
|
|
70
|
+
function buildDcvLinkMap({ dbrArticles, dbrRoot, siteVars }) {
|
|
71
|
+
const linkMap = new Map();
|
|
72
|
+
const addLinks = (links, platform) => {
|
|
73
|
+
for (const url of links) {
|
|
74
|
+
if (!linkMap.has(url)) linkMap.set(url, new Set());
|
|
75
|
+
linkMap.get(url).add(platform);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
for (const article of dbrArticles || []) {
|
|
80
|
+
addLinks(extractDcvLinks(article.content), article.platform || "*");
|
|
81
|
+
}
|
|
82
|
+
for (const file of listIncludeFiles(join(dbrRoot || "", "_includes"))) {
|
|
83
|
+
const text = resolveSiteLinks(readFileSync(file.path, "utf8"), siteVars);
|
|
84
|
+
addLinks(extractDcvLinks(text), sidebarPlatform(file.name) || "*");
|
|
85
|
+
}
|
|
86
|
+
return linkMap;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseVersionParts(version) {
|
|
90
|
+
return String(version || "").split(".").map((part) => Number.parseInt(part, 10));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// DBR and DCV ship in lockstep: 11.6.3200 pairs with 3.6.3200. Edition pages
|
|
94
|
+
// must match minor and build; core reference pages are edition-neutral and
|
|
95
|
+
// only need the same minor.
|
|
96
|
+
function isCompatibleSharedVersion(dbrVersion, dcvVersion, { requireBuild }) {
|
|
97
|
+
const [, dbrMinor, dbrBuild] = parseVersionParts(dbrVersion);
|
|
98
|
+
const [, dcvMinor, dcvBuild] = parseVersionParts(dcvVersion);
|
|
99
|
+
if (!Number.isFinite(dbrMinor) || dbrMinor !== dcvMinor) return false;
|
|
100
|
+
if (requireBuild && dbrBuild !== dcvBuild) return false;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Select DCV pages linked from one DBR edition.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} options
|
|
108
|
+
* @param {Array} options.dbrArticles DBR edition articles (with platform).
|
|
109
|
+
* @param {string} options.dbrRoot DBR docs repo root (for `_includes`).
|
|
110
|
+
* @param {Map} options.siteVars DBR repo `{{ site.* }}` variables.
|
|
111
|
+
* @param {string} options.dbrVersion DBR edition version.
|
|
112
|
+
* @param {Array<{articles: Array, version: string, editionSpecific: boolean, slugPrefix: string}>} options.sources
|
|
113
|
+
* @param {Function} [options.onSkip] Called with details when a source is version-incompatible.
|
|
114
|
+
* @returns {Array} Articles with `slugPrefix` (doc:// namespace), `platform`, `platforms`, `sourceVersion`.
|
|
115
|
+
*/
|
|
116
|
+
function selectSharedDcvDocs({ dbrArticles, dbrRoot, siteVars, dbrVersion, sources, onSkip }) {
|
|
117
|
+
const linkMap = buildDcvLinkMap({ dbrArticles, dbrRoot, siteVars });
|
|
118
|
+
const editionPlatforms = [...new Set((dbrArticles || []).map((article) => article.platform).filter(Boolean))].sort();
|
|
119
|
+
const selected = [];
|
|
120
|
+
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
if (!isCompatibleSharedVersion(dbrVersion, source.version, { requireBuild: source.editionSpecific })) {
|
|
123
|
+
onSkip?.({ dbrVersion, dcvVersion: source.version, slugPrefix: source.slugPrefix });
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
for (const article of source.articles || []) {
|
|
127
|
+
const linkedFrom = linkMap.get(normalizeDocUrl(article.url));
|
|
128
|
+
if (!linkedFrom) continue;
|
|
129
|
+
|
|
130
|
+
let platforms;
|
|
131
|
+
if (source.editionSpecific) {
|
|
132
|
+
// An edition page belongs to one platform (its programming/<lang>/ dir).
|
|
133
|
+
if (!editionPlatforms.includes(article.platform) && editionPlatforms.length > 0) continue;
|
|
134
|
+
platforms = [article.platform];
|
|
135
|
+
} else {
|
|
136
|
+
platforms = linkedFrom.has("*")
|
|
137
|
+
? editionPlatforms
|
|
138
|
+
: [...linkedFrom].filter((platform) => editionPlatforms.includes(platform)).sort();
|
|
139
|
+
}
|
|
140
|
+
if (platforms.length === 0) continue;
|
|
141
|
+
|
|
142
|
+
selected.push({
|
|
143
|
+
...article,
|
|
144
|
+
slugPrefix: source.slugPrefix,
|
|
145
|
+
platform: platforms.length === 1 ? platforms[0] : (source.defaultPlatform || platforms[0]),
|
|
146
|
+
platforms,
|
|
147
|
+
sourceVersion: source.version
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return selected;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
buildDcvLinkMap,
|
|
157
|
+
extractDcvLinks,
|
|
158
|
+
isCompatibleSharedVersion,
|
|
159
|
+
normalizeDocUrl,
|
|
160
|
+
selectSharedDcvDocs
|
|
161
|
+
};
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
} from "./normalizers.js";
|
|
18
18
|
import { DOCS_CONFIG } from "./resource-index/config.js";
|
|
19
19
|
import { loadMarkdownDocs } from "./resource-index/docs-loader.js";
|
|
20
|
+
import { selectSharedDcvDocs } from "./resource-index/shared-docs.js";
|
|
21
|
+
import { logEvent } from "../observability/logging.js";
|
|
20
22
|
import {
|
|
21
23
|
registryPath,
|
|
22
24
|
DOC_ROOTS,
|
|
@@ -149,40 +151,47 @@ let dcvMobileDocs = [];
|
|
|
149
151
|
let dcvServerDocs = [];
|
|
150
152
|
let dwtDocs = { articles: [] };
|
|
151
153
|
let ddvDocs = { articles: [] };
|
|
154
|
+
let dbrSharedDocs = { web: [], mobile: [], server: [] };
|
|
155
|
+
|
|
156
|
+
function loadDbrDocs(key, edition, platformResolver) {
|
|
157
|
+
const loaded = loadMarkdownDocs({
|
|
158
|
+
rootDir: DOC_ROOTS[key],
|
|
159
|
+
urlBase: DOCS_CONFIG[key].urlBase,
|
|
160
|
+
excludeDirs: DOCS_CONFIG[key].excludeDirs,
|
|
161
|
+
excludeFiles: DOCS_CONFIG[key].excludeFiles
|
|
162
|
+
});
|
|
163
|
+
return { articles: withEditionScope(loaded.articles, edition, platformResolver), siteVars: loaded.siteVars };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function selectDbrSharedDocs(key, edition, dbr, dcvEditionDocs) {
|
|
167
|
+
const dbrVersion = LATEST_VERSIONS.dbr[edition];
|
|
168
|
+
return selectSharedDcvDocs({
|
|
169
|
+
dbrArticles: dbr.articles,
|
|
170
|
+
dbrRoot: DOC_ROOTS[key],
|
|
171
|
+
siteVars: dbr.siteVars,
|
|
172
|
+
dbrVersion,
|
|
173
|
+
sources: [
|
|
174
|
+
{ articles: dcvEditionDocs, version: LATEST_VERSIONS.dcv[edition], editionSpecific: true, slugPrefix: "dcv", defaultPlatform: edition },
|
|
175
|
+
{ articles: dcvCoreDocs, version: LATEST_VERSIONS.dcv.core, editionSpecific: false, slugPrefix: "dcv-core", defaultPlatform: edition }
|
|
176
|
+
],
|
|
177
|
+
onSkip: ({ dcvVersion, slugPrefix }) => logEvent("data", "shared_docs_version_mismatch", {
|
|
178
|
+
edition,
|
|
179
|
+
dbr_version: dbrVersion,
|
|
180
|
+
dcv_version: dcvVersion,
|
|
181
|
+
source: slugPrefix
|
|
182
|
+
}, { level: "warn" })
|
|
183
|
+
});
|
|
184
|
+
}
|
|
152
185
|
|
|
153
186
|
function loadDocumentationSets() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
rootDir: DOC_ROOTS.dbrWeb,
|
|
157
|
-
urlBase: DOCS_CONFIG.dbrWeb.urlBase,
|
|
158
|
-
excludeDirs: DOCS_CONFIG.dbrWeb.excludeDirs,
|
|
159
|
-
excludeFiles: DOCS_CONFIG.dbrWeb.excludeFiles
|
|
160
|
-
}).articles,
|
|
161
|
-
"web",
|
|
162
|
-
() => "web"
|
|
163
|
-
);
|
|
187
|
+
const dbrWeb = loadDbrDocs("dbrWeb", "web", () => "web");
|
|
188
|
+
dbrWebDocs = dbrWeb.articles;
|
|
164
189
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
rootDir: DOC_ROOTS.dbrMobile,
|
|
168
|
-
urlBase: DOCS_CONFIG.dbrMobile.urlBase,
|
|
169
|
-
excludeDirs: DOCS_CONFIG.dbrMobile.excludeDirs,
|
|
170
|
-
excludeFiles: DOCS_CONFIG.dbrMobile.excludeFiles
|
|
171
|
-
}).articles,
|
|
172
|
-
"mobile",
|
|
173
|
-
inferDbrMobilePlatform
|
|
174
|
-
);
|
|
190
|
+
const dbrMobile = loadDbrDocs("dbrMobile", "mobile", inferDbrMobilePlatform);
|
|
191
|
+
dbrMobileDocs = dbrMobile.articles;
|
|
175
192
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
rootDir: DOC_ROOTS.dbrServer,
|
|
179
|
-
urlBase: DOCS_CONFIG.dbrServer.urlBase,
|
|
180
|
-
excludeDirs: DOCS_CONFIG.dbrServer.excludeDirs,
|
|
181
|
-
excludeFiles: DOCS_CONFIG.dbrServer.excludeFiles
|
|
182
|
-
}).articles,
|
|
183
|
-
"server",
|
|
184
|
-
inferDbrServerPlatform
|
|
185
|
-
);
|
|
193
|
+
const dbrServer = loadDbrDocs("dbrServer", "server", inferDbrServerPlatform);
|
|
194
|
+
dbrServerDocs = dbrServer.articles;
|
|
186
195
|
|
|
187
196
|
dcvCoreDocs = withEditionScope(
|
|
188
197
|
loadMarkdownDocs({
|
|
@@ -252,6 +261,7 @@ function loadDocumentationSets() {
|
|
|
252
261
|
|
|
253
262
|
dwtDocs = loadMarkdownDocs({
|
|
254
263
|
rootDir: DOC_ROOTS.dwtArticles,
|
|
264
|
+
siteRoot: DOC_ROOTS.dwt,
|
|
255
265
|
urlBase: DOCS_CONFIG.dwt.urlBase,
|
|
256
266
|
includeDirNames: DOCS_CONFIG.dwt.includeDirNames
|
|
257
267
|
});
|
|
@@ -262,6 +272,12 @@ function loadDocumentationSets() {
|
|
|
262
272
|
excludeDirs: DOCS_CONFIG.ddv.excludeDirs,
|
|
263
273
|
excludeFiles: DOCS_CONFIG.ddv.excludeFiles
|
|
264
274
|
});
|
|
275
|
+
|
|
276
|
+
dbrSharedDocs = {
|
|
277
|
+
web: selectDbrSharedDocs("dbrWeb", "web", dbrWeb, dcvWebDocs),
|
|
278
|
+
mobile: selectDbrSharedDocs("dbrMobile", "mobile", dbrMobile, dcvMobileDocs),
|
|
279
|
+
server: selectDbrSharedDocs("dbrServer", "server", dbrServer, dcvServerDocs)
|
|
280
|
+
};
|
|
265
281
|
}
|
|
266
282
|
|
|
267
283
|
const dbrServerSdk = registry.sdks["dbr-server"];
|
|
@@ -339,6 +355,7 @@ function buildIndexData() {
|
|
|
339
355
|
dbrWebDocs,
|
|
340
356
|
dbrMobileDocs,
|
|
341
357
|
dbrServerDocs,
|
|
358
|
+
dbrSharedDocs,
|
|
342
359
|
dwtDocs,
|
|
343
360
|
ddvDocs,
|
|
344
361
|
discoverDcvWebSamples,
|
|
@@ -380,6 +397,7 @@ function buildResourceIndex() {
|
|
|
380
397
|
dbrWebDocs,
|
|
381
398
|
dbrMobileDocs,
|
|
382
399
|
dbrServerDocs,
|
|
400
|
+
dbrSharedDocs,
|
|
383
401
|
dwtDocs,
|
|
384
402
|
ddvDocs,
|
|
385
403
|
discoverDcvMobileSamples,
|
|
@@ -444,6 +462,8 @@ function editionMatches(normalizedEdition, entryEdition) {
|
|
|
444
462
|
function platformMatches(normalizedPlatform, entry) {
|
|
445
463
|
if (!normalizedPlatform) return true;
|
|
446
464
|
if (normalizedPlatform === entry.platform) return true;
|
|
465
|
+
// Shared pages (e.g. Capture Vision core reference) serve several platforms.
|
|
466
|
+
if (Array.isArray(entry.platforms) && entry.platforms.includes(normalizedPlatform)) return true;
|
|
447
467
|
if (normalizedPlatform === "web") return entry.platform === "web";
|
|
448
468
|
if (isWebFrameworkPlatform(normalizedPlatform)) {
|
|
449
469
|
const aliases = WEB_FRAMEWORK_TAG_ALIASES[normalizedPlatform] || [normalizedPlatform];
|
|
@@ -514,13 +534,12 @@ function buildResourceLookupCandidates(uri) {
|
|
|
514
534
|
const parts = String(rest || "").split("/").filter(Boolean);
|
|
515
535
|
if (parts.length < 5) return candidates;
|
|
516
536
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const canonical = `${scheme}://${head.join("/")}/${encodeURIComponent(slug)}`;
|
|
537
|
+
// Doc slugs are source paths; accept them with or without per-segment
|
|
538
|
+
// percent-encoding (clients differ in how they echo URIs back).
|
|
539
|
+
const head = parts.slice(0, 4).join("/");
|
|
540
|
+
const slugSegments = parts.slice(4).map((segment) => safeDecodeURIComponent(segment));
|
|
541
|
+
for (const slug of [slugSegments.join("/"), slugSegments.map((segment) => encodeURIComponent(segment)).join("/")]) {
|
|
542
|
+
const canonical = `${scheme}://${head}/${slug}`;
|
|
524
543
|
if (!candidates.includes(canonical)) {
|
|
525
544
|
candidates.push(canonical);
|
|
526
545
|
}
|
|
@@ -562,6 +581,7 @@ function getRagSignatureData() {
|
|
|
562
581
|
dbrWebDocCount: dbrWebDocs.length,
|
|
563
582
|
dbrMobileDocCount: dbrMobileDocs.length,
|
|
564
583
|
dbrServerDocCount: dbrServerDocs.length,
|
|
584
|
+
dbrSharedDocCount: dbrSharedDocs.web.length + dbrSharedDocs.mobile.length + dbrSharedDocs.server.length,
|
|
565
585
|
dwtDocCount: dwtDocs.articles.length,
|
|
566
586
|
ddvDocCount: ddvDocs.articles.length,
|
|
567
587
|
versions: LATEST_VERSIONS,
|
|
@@ -34,7 +34,10 @@ export function registerIndexTools({
|
|
|
34
34
|
const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
|
|
35
35
|
const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
|
|
36
36
|
const scoreLabel = formatScoreLabel(entry);
|
|
37
|
-
|
|
37
|
+
const sharedLabel = entry.type === "doc" && Array.isArray(entry.tags) && entry.tags.includes("shared")
|
|
38
|
+
? ` | shared Capture Vision API used by DBR (DCV v${entry.sourceVersion || "n/a"})`
|
|
39
|
+
: "";
|
|
40
|
+
let desc = `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${sharedLabel}${scoreLabel} - ${entry.summary}${sampleHint}`;
|
|
38
41
|
if (entry.matchedSnippet) {
|
|
39
42
|
const snippet = String(entry.matchedSnippet).replace(/\s+/g, " ").slice(0, 220);
|
|
40
43
|
desc += ` — "${snippet}"`;
|