@imenam/mcp-documentor 1.0.23
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 +74 -0
- package/dist/shared/schemas/doc.schema.d.ts +112 -0
- package/dist/shared/schemas/doc.schema.js +83 -0
- package/dist/src/core/embedding-engine.d.ts +23 -0
- package/dist/src/core/embedding-engine.js +119 -0
- package/dist/src/core/embedding-store.d.ts +15 -0
- package/dist/src/core/embedding-store.js +97 -0
- package/dist/src/core/migrate-embeddings.d.ts +3 -0
- package/dist/src/core/migrate-embeddings.js +30 -0
- package/dist/src/core/migrate-folders.d.ts +2 -0
- package/dist/src/core/migrate-folders.js +45 -0
- package/dist/src/core/storage-engine.d.ts +29 -0
- package/dist/src/core/storage-engine.js +191 -0
- package/dist/src/gui-worker.d.ts +1 -0
- package/dist/src/gui-worker.js +349 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +678 -0
- package/dist/src/tools/check-freshness.d.ts +18 -0
- package/dist/src/tools/check-freshness.js +167 -0
- package/dist/src/tools/check-links.d.ts +14 -0
- package/dist/src/tools/check-links.js +99 -0
- package/dist/src/tools/create-doc.d.ts +15 -0
- package/dist/src/tools/create-doc.js +78 -0
- package/dist/src/tools/delete-doc.d.ts +15 -0
- package/dist/src/tools/delete-doc.js +18 -0
- package/dist/src/tools/get-doc-metadata.d.ts +14 -0
- package/dist/src/tools/get-doc-metadata.js +40 -0
- package/dist/src/tools/get-doc-tree.d.ts +14 -0
- package/dist/src/tools/get-doc-tree.js +64 -0
- package/dist/src/tools/get-doc.d.ts +14 -0
- package/dist/src/tools/get-doc.js +55 -0
- package/dist/src/tools/git-diff-doc.d.ts +14 -0
- package/dist/src/tools/git-diff-doc.js +93 -0
- package/dist/src/tools/list-docs.d.ts +14 -0
- package/dist/src/tools/list-docs.js +78 -0
- package/dist/src/tools/list-projects.d.ts +14 -0
- package/dist/src/tools/list-projects.js +16 -0
- package/dist/src/tools/move-doc.d.ts +15 -0
- package/dist/src/tools/move-doc.js +34 -0
- package/dist/src/tools/patch-doc.d.ts +15 -0
- package/dist/src/tools/patch-doc.js +37 -0
- package/dist/src/tools/refresh-file-snapshot.d.ts +14 -0
- package/dist/src/tools/refresh-file-snapshot.js +45 -0
- package/dist/src/tools/search-docs.d.ts +15 -0
- package/dist/src/tools/search-docs.js +26 -0
- package/dist/src/tools/set-project.d.ts +14 -0
- package/dist/src/tools/set-project.js +18 -0
- package/dist/src/tools/update-doc.d.ts +15 -0
- package/dist/src/tools/update-doc.js +66 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +2 -0
- package/gui/README.md +75 -0
- package/gui/dist/assets/index-BwyK1pan.css +1 -0
- package/gui/dist/assets/index-CzuFn4Pp.js +48 -0
- package/gui/dist/index.html +14 -0
- package/gui/dist/vite.svg +1 -0
- package/package.json +61 -0
- package/scripts/generate-version.js +20 -0
- package/scripts/migrate-links.js +258 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>MCP Documentor</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-CzuFn4Pp.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BwyK1pan.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@imenam/mcp-documentor",
|
|
3
|
+
"version": "1.0.23",
|
|
4
|
+
"main": "dist/src/index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
7
|
+
"generate-version": "node scripts/generate-version.js",
|
|
8
|
+
"build:backend": "npm run generate-version && tsc",
|
|
9
|
+
"build:gui": "cd gui && npm install && npm run build",
|
|
10
|
+
"build": "npm run build:backend && npm run build:gui",
|
|
11
|
+
"start": "node dist/src/index.js",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
13
|
+
"release": "npm version patch && npm publish && git push --follow-tags"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"documentation",
|
|
18
|
+
"markdown",
|
|
19
|
+
"semantic-search",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"ai"
|
|
22
|
+
],
|
|
23
|
+
"author": "Imena",
|
|
24
|
+
"license": "ISC",
|
|
25
|
+
"description": "MCP server to create, manage and search project documentation with staleness tracking and semantic search.",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hono/node-server": "^1.19.10",
|
|
28
|
+
"@imenam/mcp-gui-interface": "^1.0.3",
|
|
29
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
30
|
+
"better-sqlite3": "^12.8.0",
|
|
31
|
+
"dotenv": "^17.3.1",
|
|
32
|
+
"github-slugger": "^2.0.0",
|
|
33
|
+
"gray-matter": "^4.0.3",
|
|
34
|
+
"hono": "^4.12.5",
|
|
35
|
+
"openai": "^6.25.0",
|
|
36
|
+
"zod": "^4.3.6"
|
|
37
|
+
},
|
|
38
|
+
"type": "module",
|
|
39
|
+
"bin": {
|
|
40
|
+
"mcp-documentor": "dist/src/index.js",
|
|
41
|
+
"mcp-documentor-migrate": "scripts/migrate-links.js"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"gui/dist",
|
|
46
|
+
"README.md",
|
|
47
|
+
"scripts"
|
|
48
|
+
],
|
|
49
|
+
"config": {
|
|
50
|
+
"FIXED_GUI_PORT": "4205",
|
|
51
|
+
"PROXY_URL": ""
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"typescript": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const rootDir = path.join(__dirname, '..');
|
|
8
|
+
|
|
9
|
+
const packagePath = path.join(rootDir, 'package.json');
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
11
|
+
|
|
12
|
+
const versionContent = `// Auto-generated version file - do not edit manually
|
|
13
|
+
export const APP_VERSION = '${pkg.version}';
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const outputPath = path.join(rootDir, 'src', 'version.ts');
|
|
17
|
+
fs.writeFileSync(outputPath, versionContent, 'utf8');
|
|
18
|
+
|
|
19
|
+
console.log(`Generated version file: ${outputPath}`);
|
|
20
|
+
console.log(` Version: ${pkg.version}`);
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* migrate-links.js
|
|
4
|
+
*
|
|
5
|
+
* Migrates inter-document references from the old slug-only format
|
|
6
|
+
* to the new full-path format (folder/name) introduced in Task 10.
|
|
7
|
+
*
|
|
8
|
+
* Fixes two types of links:
|
|
9
|
+
* - related_docs: ["old-slug"] → ["folder/old-slug"]
|
|
10
|
+
* - inline links: (doc://old-slug) → (doc://folder/old-slug)
|
|
11
|
+
* (doc://old-slug#h) → (doc://folder/old-slug#h)
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node scripts/migrate-links.js [--dry-run] [--data-dir /path/to/.docs-data]
|
|
15
|
+
*
|
|
16
|
+
* Options:
|
|
17
|
+
* --dry-run Show what would be changed without modifying any file.
|
|
18
|
+
* --data-dir Override the data directory (default: <project-root>/.docs-data).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { fileURLToPath } from "url";
|
|
24
|
+
import matter from "gray-matter";
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Setup
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
31
|
+
const rootDir = path.join(__dirname, "..");
|
|
32
|
+
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
const dryRun = args.includes("--dry-run");
|
|
35
|
+
|
|
36
|
+
const dataDirArgIdx = args.indexOf("--data-dir");
|
|
37
|
+
const dataDir =
|
|
38
|
+
dataDirArgIdx !== -1 && args[dataDirArgIdx + 1]
|
|
39
|
+
? args[dataDirArgIdx + 1]
|
|
40
|
+
: path.join(rootDir, ".docs-data");
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
function walkMdFiles(dir) {
|
|
47
|
+
if (!fs.existsSync(dir)) return [];
|
|
48
|
+
const results = [];
|
|
49
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
50
|
+
const fullPath = path.join(dir, entry.name);
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
results.push(...walkMdFiles(fullPath));
|
|
53
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
54
|
+
results.push(fullPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return results;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Derive the doc path (id) from an absolute file path within a project dir. */
|
|
61
|
+
function filePathToDocId(filePath, projectDir) {
|
|
62
|
+
return path
|
|
63
|
+
.relative(projectDir, filePath)
|
|
64
|
+
.replace(/\.md$/, "")
|
|
65
|
+
.split(path.sep)
|
|
66
|
+
.join("/");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Load config
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const configPath = path.join(dataDir, "config.json");
|
|
74
|
+
if (!fs.existsSync(configPath)) {
|
|
75
|
+
console.error(`[ERROR] config.json not found at: ${configPath}`);
|
|
76
|
+
console.error(" Make sure --data-dir points to the right directory.");
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
81
|
+
const projects = config.projects ?? ["default"];
|
|
82
|
+
|
|
83
|
+
console.log(`${dryRun ? "[DRY RUN] " : ""}migrate-links`);
|
|
84
|
+
console.log(`Data dir : ${dataDir}`);
|
|
85
|
+
console.log(`Projects : ${projects.join(", ")}`);
|
|
86
|
+
console.log("");
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Step 1 — Build the global map: slug → [{ project, fullId }]
|
|
90
|
+
//
|
|
91
|
+
// A "slug" is the last segment of a doc path (the filename without .md).
|
|
92
|
+
// If a slug appears in multiple projects or folders, the reference is ambiguous.
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/** @type {Map<string, Array<{ project: string, fullId: string }>>} */
|
|
96
|
+
const slugMap = new Map();
|
|
97
|
+
|
|
98
|
+
for (const project of projects) {
|
|
99
|
+
const projectDir = path.join(dataDir, "projects", project);
|
|
100
|
+
for (const filePath of walkMdFiles(projectDir)) {
|
|
101
|
+
const fullId = filePathToDocId(filePath, projectDir);
|
|
102
|
+
const slug = fullId.split("/").pop();
|
|
103
|
+
|
|
104
|
+
if (!slugMap.has(slug)) slugMap.set(slug, []);
|
|
105
|
+
slugMap.get(slug).push({ project, fullId });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Step 2 — Scan every doc and fix links
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
let totalDocsChanged = 0;
|
|
114
|
+
let totalLinksFixed = 0;
|
|
115
|
+
let totalLinksNotFound = 0;
|
|
116
|
+
let totalLinksAmbiguous = 0;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve an old slug-only reference to its full path.
|
|
120
|
+
* Returns { resolved: string } on success, or { error: string } otherwise.
|
|
121
|
+
*/
|
|
122
|
+
function resolveSlug(slug) {
|
|
123
|
+
const matches = slugMap.get(slug) ?? [];
|
|
124
|
+
if (matches.length === 0) return { error: "NOT FOUND" };
|
|
125
|
+
if (matches.length > 1) {
|
|
126
|
+
return { error: `AMBIGUOUS → [${matches.map((m) => m.fullId).join(", ")}]` };
|
|
127
|
+
}
|
|
128
|
+
return { resolved: matches[0].fullId };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const project of projects) {
|
|
132
|
+
const projectDir = path.join(dataDir, "projects", project);
|
|
133
|
+
const files = walkMdFiles(projectDir);
|
|
134
|
+
|
|
135
|
+
if (files.length === 0) continue;
|
|
136
|
+
|
|
137
|
+
console.log(`\n### Project: ${project} (${files.length} docs)`);
|
|
138
|
+
console.log("");
|
|
139
|
+
|
|
140
|
+
for (const filePath of files) {
|
|
141
|
+
const fullId = filePathToDocId(filePath, projectDir);
|
|
142
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
143
|
+
const parsed = matter(raw);
|
|
144
|
+
|
|
145
|
+
const docChanges = [];
|
|
146
|
+
let docChanged = false;
|
|
147
|
+
|
|
148
|
+
// -----------------------------------------------------------------------
|
|
149
|
+
// Fix related_docs
|
|
150
|
+
// -----------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
if (Array.isArray(parsed.data.related_docs)) {
|
|
153
|
+
const newRelatedDocs = [];
|
|
154
|
+
|
|
155
|
+
for (const ref of parsed.data.related_docs) {
|
|
156
|
+
// Already a full path — nothing to do
|
|
157
|
+
if (ref.includes("/")) {
|
|
158
|
+
newRelatedDocs.push(ref);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result = resolveSlug(ref);
|
|
163
|
+
|
|
164
|
+
if (result.resolved) {
|
|
165
|
+
docChanges.push(` [related_docs] "${ref}" → "${result.resolved}"`);
|
|
166
|
+
newRelatedDocs.push(result.resolved);
|
|
167
|
+
docChanged = true;
|
|
168
|
+
totalLinksFixed++;
|
|
169
|
+
} else {
|
|
170
|
+
docChanges.push(` [related_docs] "${ref}" → ${result.error} (kept as-is)`);
|
|
171
|
+
newRelatedDocs.push(ref);
|
|
172
|
+
if (result.error === "NOT FOUND") totalLinksNotFound++;
|
|
173
|
+
else totalLinksAmbiguous++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (docChanged) {
|
|
178
|
+
parsed.data.related_docs = newRelatedDocs;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// -----------------------------------------------------------------------
|
|
183
|
+
// Fix inline doc:// links in content
|
|
184
|
+
//
|
|
185
|
+
// Matches: doc://slug or doc://slug#anchor
|
|
186
|
+
// Does NOT touch: doc://folder/slug (already full path)
|
|
187
|
+
// -----------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
// Pattern captures: group1 = slug-or-path, group2 = optional #anchor
|
|
190
|
+
const docLinkRegex = /doc:\/\/([^)#\s]+)(#[^)\s]*)?/g;
|
|
191
|
+
let newContent = parsed.content;
|
|
192
|
+
|
|
193
|
+
// Collect all matches first to avoid mutation during iteration
|
|
194
|
+
const contentMatches = [];
|
|
195
|
+
let m;
|
|
196
|
+
while ((m = docLinkRegex.exec(parsed.content)) !== null) {
|
|
197
|
+
contentMatches.push({ full: m[0], ref: m[1], anchor: m[2] ?? "" });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
for (const { full, ref, anchor } of contentMatches) {
|
|
201
|
+
// Already a full path
|
|
202
|
+
if (ref.includes("/")) continue;
|
|
203
|
+
|
|
204
|
+
const result = resolveSlug(ref);
|
|
205
|
+
|
|
206
|
+
if (result.resolved) {
|
|
207
|
+
const newLink = `doc://${result.resolved}${anchor}`;
|
|
208
|
+
docChanges.push(` [doc link] "${full}" → "${newLink}"`);
|
|
209
|
+
// Replace only this specific occurrence (anchor included for precision)
|
|
210
|
+
newContent = newContent.replaceAll(full, newLink);
|
|
211
|
+
docChanged = true;
|
|
212
|
+
totalLinksFixed++;
|
|
213
|
+
} else {
|
|
214
|
+
docChanges.push(` [doc link] "${full}" → ${result.error} (kept as-is)`);
|
|
215
|
+
if (result.error === "NOT FOUND") totalLinksNotFound++;
|
|
216
|
+
else totalLinksAmbiguous++;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// -----------------------------------------------------------------------
|
|
221
|
+
// Report & write
|
|
222
|
+
// -----------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
if (docChanges.length > 0) {
|
|
225
|
+
console.log(` [${fullId}]`);
|
|
226
|
+
docChanges.forEach((c) => console.log(c));
|
|
227
|
+
console.log("");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (docChanged) {
|
|
231
|
+
totalDocsChanged++;
|
|
232
|
+
if (!dryRun) {
|
|
233
|
+
const output = matter.stringify(newContent, parsed.data);
|
|
234
|
+
fs.writeFileSync(filePath, output, "utf-8");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// Summary
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
console.log("─".repeat(60));
|
|
245
|
+
console.log(`Links fixed : ${totalLinksFixed}`);
|
|
246
|
+
if (totalLinksNotFound > 0) {
|
|
247
|
+
console.log(`Links not found : ${totalLinksNotFound} ← target doc doesn't exist`);
|
|
248
|
+
}
|
|
249
|
+
if (totalLinksAmbiguous > 0) {
|
|
250
|
+
console.log(`Links ambiguous : ${totalLinksAmbiguous} ← same slug in multiple folders, fix manually`);
|
|
251
|
+
}
|
|
252
|
+
console.log(`Docs modified : ${totalDocsChanged}`);
|
|
253
|
+
|
|
254
|
+
if (dryRun) {
|
|
255
|
+
console.log("");
|
|
256
|
+
console.log("[DRY RUN] No files were modified.");
|
|
257
|
+
console.log(" Run without --dry-run to apply the changes.");
|
|
258
|
+
}
|