@imenam/mcp-documentor 1.0.28 → 1.0.29
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/dist/shared/schemas/doc.schema.d.ts +14 -0
- package/dist/shared/schemas/doc.schema.js +14 -0
- package/dist/src/core/history-store.d.ts +25 -0
- package/dist/src/core/history-store.js +92 -0
- package/dist/src/core/storage-engine.d.ts +4 -1
- package/dist/src/core/storage-engine.js +17 -2
- package/dist/src/gui-worker.js +17 -0
- package/dist/src/index.js +103 -0
- package/dist/src/tools/create-doc.js +1 -1
- package/dist/src/tools/get-doc-version.d.ts +15 -0
- package/dist/src/tools/get-doc-version.js +45 -0
- package/dist/src/tools/list-doc-versions.d.ts +15 -0
- package/dist/src/tools/list-doc-versions.js +35 -0
- package/dist/src/tools/patch-doc.js +1 -1
- package/dist/src/tools/restore-doc-version.d.ts +16 -0
- package/dist/src/tools/restore-doc-version.js +54 -0
- package/dist/src/tools/update-doc.js +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/gui/dist/assets/index-BEjYxjtw.js +51 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/gui/dist/assets/index-IwQLNV3c.js +0 -51
|
@@ -103,6 +103,20 @@ export declare const GitDiffDocArgsSchema: z.ZodObject<{
|
|
|
103
103
|
export declare const SetProjectArgsSchema: z.ZodObject<{
|
|
104
104
|
project: z.ZodString;
|
|
105
105
|
}, z.core.$strip>;
|
|
106
|
+
export declare const ListDocVersionsArgsSchema: z.ZodObject<{
|
|
107
|
+
path: z.ZodString;
|
|
108
|
+
project: z.ZodOptional<z.ZodString>;
|
|
109
|
+
}, z.core.$strip>;
|
|
110
|
+
export declare const GetDocVersionArgsSchema: z.ZodObject<{
|
|
111
|
+
path: z.ZodString;
|
|
112
|
+
version: z.ZodNumber;
|
|
113
|
+
project: z.ZodOptional<z.ZodString>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
export declare const RestoreDocVersionArgsSchema: z.ZodObject<{
|
|
116
|
+
path: z.ZodString;
|
|
117
|
+
version: z.ZodNumber;
|
|
118
|
+
project: z.ZodOptional<z.ZodString>;
|
|
119
|
+
}, z.core.$strip>;
|
|
106
120
|
export declare const IPCMessageSchema: z.ZodObject<{
|
|
107
121
|
type: z.ZodString;
|
|
108
122
|
correlationId: z.ZodOptional<z.ZodString>;
|
|
@@ -75,6 +75,20 @@ export const GitDiffDocArgsSchema = z.object({
|
|
|
75
75
|
export const SetProjectArgsSchema = z.object({
|
|
76
76
|
project: z.string().describe("Project name to switch to (created if it does not exist)"),
|
|
77
77
|
});
|
|
78
|
+
export const ListDocVersionsArgsSchema = z.object({
|
|
79
|
+
path: z.string(),
|
|
80
|
+
project: z.string().optional(),
|
|
81
|
+
});
|
|
82
|
+
export const GetDocVersionArgsSchema = z.object({
|
|
83
|
+
path: z.string(),
|
|
84
|
+
version: z.number().int().positive(),
|
|
85
|
+
project: z.string().optional(),
|
|
86
|
+
});
|
|
87
|
+
export const RestoreDocVersionArgsSchema = z.object({
|
|
88
|
+
path: z.string(),
|
|
89
|
+
version: z.number().int().positive(),
|
|
90
|
+
project: z.string().optional(),
|
|
91
|
+
});
|
|
78
92
|
export const IPCMessageSchema = z.object({
|
|
79
93
|
type: z.string(),
|
|
80
94
|
correlationId: z.string().optional(),
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DocEntry, TrackedFile } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
export interface DocHistoryEntry {
|
|
3
|
+
id: number;
|
|
4
|
+
project: string;
|
|
5
|
+
docId: string;
|
|
6
|
+
version: number;
|
|
7
|
+
content?: string;
|
|
8
|
+
metadata: {
|
|
9
|
+
title: string;
|
|
10
|
+
tags: string[];
|
|
11
|
+
tracked_files: TrackedFile[];
|
|
12
|
+
related_docs: string[];
|
|
13
|
+
};
|
|
14
|
+
changedAt: string;
|
|
15
|
+
changeType: "create" | "update" | "patch" | "delete";
|
|
16
|
+
}
|
|
17
|
+
export declare class HistoryStore {
|
|
18
|
+
private db;
|
|
19
|
+
init(dataDir: string): void;
|
|
20
|
+
private getDb;
|
|
21
|
+
record(project: string, doc: DocEntry, changeType: "create" | "update" | "patch" | "delete"): void;
|
|
22
|
+
listVersions(project: string, docId: string): DocHistoryEntry[];
|
|
23
|
+
getVersion(project: string, docId: string, version: number): DocHistoryEntry | null;
|
|
24
|
+
deleteForDoc(project: string, docId: string): void;
|
|
25
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { createLogger } from "@imenam/mcp-gui-interface";
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const Database = require("better-sqlite3");
|
|
7
|
+
const logger = createLogger("HistoryStore");
|
|
8
|
+
const DB_FILENAME = "history.db";
|
|
9
|
+
export class HistoryStore {
|
|
10
|
+
db = null;
|
|
11
|
+
init(dataDir) {
|
|
12
|
+
if (this.db)
|
|
13
|
+
return;
|
|
14
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
15
|
+
this.db = new Database(path.join(dataDir, DB_FILENAME));
|
|
16
|
+
this.db.pragma("journal_mode = WAL");
|
|
17
|
+
this.db.pragma("synchronous = NORMAL");
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS doc_history (
|
|
20
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
21
|
+
project TEXT NOT NULL,
|
|
22
|
+
doc_id TEXT NOT NULL,
|
|
23
|
+
version INTEGER NOT NULL,
|
|
24
|
+
content TEXT NOT NULL,
|
|
25
|
+
metadata TEXT NOT NULL,
|
|
26
|
+
changed_at TEXT NOT NULL,
|
|
27
|
+
change_type TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_doc_history_lookup
|
|
31
|
+
ON doc_history(project, doc_id, version DESC);
|
|
32
|
+
`);
|
|
33
|
+
logger.info("HistoryStore initialized");
|
|
34
|
+
}
|
|
35
|
+
getDb() {
|
|
36
|
+
if (!this.db)
|
|
37
|
+
throw new Error("HistoryStore not initialized. Call init() first.");
|
|
38
|
+
return this.db;
|
|
39
|
+
}
|
|
40
|
+
record(project, doc, changeType) {
|
|
41
|
+
const db = this.getDb();
|
|
42
|
+
const row = db
|
|
43
|
+
.prepare("SELECT COALESCE(MAX(version), 0) + 1 AS next FROM doc_history WHERE project = ? AND doc_id = ?")
|
|
44
|
+
.get(project, doc.id);
|
|
45
|
+
const version = row.next;
|
|
46
|
+
const metadata = JSON.stringify({
|
|
47
|
+
title: doc.title,
|
|
48
|
+
tags: doc.tags,
|
|
49
|
+
tracked_files: doc.tracked_files,
|
|
50
|
+
related_docs: doc.related_docs,
|
|
51
|
+
});
|
|
52
|
+
db.prepare("INSERT INTO doc_history (project, doc_id, version, content, metadata, changed_at, change_type) VALUES (?, ?, ?, ?, ?, ?, ?)").run(project, doc.id, version, doc.content, metadata, new Date().toISOString(), changeType);
|
|
53
|
+
logger.debug(`History recorded: [${project}] ${doc.id} v${version} (${changeType})`);
|
|
54
|
+
}
|
|
55
|
+
listVersions(project, docId) {
|
|
56
|
+
const rows = this.getDb()
|
|
57
|
+
.prepare("SELECT id, project, doc_id, version, metadata, changed_at, change_type FROM doc_history WHERE project = ? AND doc_id = ? ORDER BY version DESC")
|
|
58
|
+
.all(project, docId);
|
|
59
|
+
return rows.map((r) => ({
|
|
60
|
+
id: r.id,
|
|
61
|
+
project: r.project,
|
|
62
|
+
docId: r.doc_id,
|
|
63
|
+
version: r.version,
|
|
64
|
+
metadata: JSON.parse(r.metadata),
|
|
65
|
+
changedAt: r.changed_at,
|
|
66
|
+
changeType: r.change_type,
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
getVersion(project, docId, version) {
|
|
70
|
+
const row = this.getDb()
|
|
71
|
+
.prepare("SELECT id, project, doc_id, version, content, metadata, changed_at, change_type FROM doc_history WHERE project = ? AND doc_id = ? AND version = ?")
|
|
72
|
+
.get(project, docId, version);
|
|
73
|
+
if (!row)
|
|
74
|
+
return null;
|
|
75
|
+
return {
|
|
76
|
+
id: row.id,
|
|
77
|
+
project: row.project,
|
|
78
|
+
docId: row.doc_id,
|
|
79
|
+
version: row.version,
|
|
80
|
+
content: row.content,
|
|
81
|
+
metadata: JSON.parse(row.metadata),
|
|
82
|
+
changedAt: row.changed_at,
|
|
83
|
+
changeType: row.change_type,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
deleteForDoc(project, docId) {
|
|
87
|
+
this.getDb()
|
|
88
|
+
.prepare("DELETE FROM doc_history WHERE project = ? AND doc_id = ?")
|
|
89
|
+
.run(project, docId);
|
|
90
|
+
logger.debug(`History deleted for: [${project}] ${docId}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DocEntry, DocSummary } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
import type { HistoryStore } from "./history-store.js";
|
|
2
3
|
interface GlobalConfig {
|
|
3
4
|
currentProject: string;
|
|
4
5
|
projects: string[];
|
|
@@ -6,7 +7,9 @@ interface GlobalConfig {
|
|
|
6
7
|
}
|
|
7
8
|
export declare class StorageEngine {
|
|
8
9
|
private rootDir;
|
|
10
|
+
private historyStore?;
|
|
9
11
|
constructor(rootDir: string);
|
|
12
|
+
setHistoryStore(store: HistoryStore): void;
|
|
10
13
|
getDataDir(): string;
|
|
11
14
|
getProjectDir(project?: string): string;
|
|
12
15
|
private getConfigPath;
|
|
@@ -16,7 +19,7 @@ export declare class StorageEngine {
|
|
|
16
19
|
private walkMdFiles;
|
|
17
20
|
private docIdToFilePath;
|
|
18
21
|
private cleanEmptyDirs;
|
|
19
|
-
saveDoc(doc: DocEntry, project?: string): Promise<void>;
|
|
22
|
+
saveDoc(doc: DocEntry, project?: string, changeType?: "create" | "update" | "patch"): Promise<void>;
|
|
20
23
|
getDoc(id: string, project?: string): Promise<DocEntry | null>;
|
|
21
24
|
listDocs(project?: string): Promise<DocSummary[]>;
|
|
22
25
|
resolveDoc(id: string): Promise<{
|
|
@@ -8,9 +8,13 @@ function escapeRegex(s) {
|
|
|
8
8
|
}
|
|
9
9
|
export class StorageEngine {
|
|
10
10
|
rootDir;
|
|
11
|
+
historyStore;
|
|
11
12
|
constructor(rootDir) {
|
|
12
13
|
this.rootDir = rootDir;
|
|
13
14
|
}
|
|
15
|
+
setHistoryStore(store) {
|
|
16
|
+
this.historyStore = store;
|
|
17
|
+
}
|
|
14
18
|
getDataDir() {
|
|
15
19
|
const envDir = process.env.MCP_DATA_DIR;
|
|
16
20
|
return envDir && envDir.trim() !== "" ? envDir : path.join(this.rootDir, ".docs-data");
|
|
@@ -82,7 +86,9 @@ export class StorageEngine {
|
|
|
82
86
|
// ignore — dir may have been removed already
|
|
83
87
|
}
|
|
84
88
|
}
|
|
85
|
-
async saveDoc(doc, project) {
|
|
89
|
+
async saveDoc(doc, project, changeType = "update") {
|
|
90
|
+
const config = this.getConfig();
|
|
91
|
+
const proj = project ?? config.currentProject;
|
|
86
92
|
const projectDir = this.getProjectDir(project);
|
|
87
93
|
const targetPath = this.docIdToFilePath(doc.id, projectDir);
|
|
88
94
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
@@ -97,6 +103,7 @@ export class StorageEngine {
|
|
|
97
103
|
const output = matter.stringify(doc.content, frontmatter);
|
|
98
104
|
fs.writeFileSync(targetPath, output, "utf-8");
|
|
99
105
|
logger.debug(`Doc saved: ${targetPath}`);
|
|
106
|
+
this.historyStore?.record(proj, doc, changeType);
|
|
100
107
|
}
|
|
101
108
|
async getDoc(id, project) {
|
|
102
109
|
const projectDir = this.getProjectDir(project);
|
|
@@ -159,10 +166,18 @@ export class StorageEngine {
|
|
|
159
166
|
return null;
|
|
160
167
|
}
|
|
161
168
|
async deleteDoc(id, project) {
|
|
169
|
+
const config = this.getConfig();
|
|
170
|
+
const proj = project ?? config.currentProject;
|
|
162
171
|
const projectDir = this.getProjectDir(project);
|
|
163
172
|
const filePath = this.docIdToFilePath(id, projectDir);
|
|
164
173
|
if (!fs.existsSync(filePath))
|
|
165
174
|
return false;
|
|
175
|
+
if (this.historyStore) {
|
|
176
|
+
const doc = await this.getDoc(id, project);
|
|
177
|
+
if (doc) {
|
|
178
|
+
this.historyStore.record(proj, doc, "delete");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
166
181
|
fs.unlinkSync(filePath);
|
|
167
182
|
this.cleanEmptyDirs(path.dirname(filePath), projectDir);
|
|
168
183
|
logger.info(`Doc deleted: ${filePath}`);
|
|
@@ -185,7 +200,7 @@ export class StorageEngine {
|
|
|
185
200
|
doc.folder = newParts.length > 1 ? newParts.slice(0, -1).join("/") : undefined;
|
|
186
201
|
doc.updatedAt = new Date().toISOString();
|
|
187
202
|
fs.mkdirSync(path.dirname(newFilePath), { recursive: true });
|
|
188
|
-
await this.saveDoc(doc, project);
|
|
203
|
+
await this.saveDoc(doc, project, "update");
|
|
189
204
|
fs.unlinkSync(oldFilePath);
|
|
190
205
|
this.cleanEmptyDirs(path.dirname(oldFilePath), projectDir);
|
|
191
206
|
logger.info(`Doc moved: ${oldFilePath} → ${newFilePath}`);
|
package/dist/src/gui-worker.js
CHANGED
|
@@ -282,6 +282,23 @@ app.get("/api/git-diff/*", async (c) => {
|
|
|
282
282
|
return c.json({ error: result.error }, 400);
|
|
283
283
|
return c.json(result.data);
|
|
284
284
|
});
|
|
285
|
+
app.get("/api/history/*", async (c) => {
|
|
286
|
+
const id = decodeURIComponent(c.req.path.slice("/api/history/".length));
|
|
287
|
+
const result = await ipcRequest("GET_DOC_HISTORY", { id });
|
|
288
|
+
if (result.error)
|
|
289
|
+
return c.json({ error: result.error }, 400);
|
|
290
|
+
return c.json(result.data);
|
|
291
|
+
});
|
|
292
|
+
app.get("/api/history-version/*", async (c) => {
|
|
293
|
+
const rest = decodeURIComponent(c.req.path.slice("/api/history-version/".length));
|
|
294
|
+
const lastSlash = rest.lastIndexOf("/");
|
|
295
|
+
const id = rest.slice(0, lastSlash);
|
|
296
|
+
const version = parseInt(rest.slice(lastSlash + 1), 10);
|
|
297
|
+
const result = await ipcRequest("GET_DOC_VERSION", { id, version });
|
|
298
|
+
if (result.error)
|
|
299
|
+
return c.json({ error: result.error }, 400);
|
|
300
|
+
return c.json(result.data);
|
|
301
|
+
});
|
|
285
302
|
app.post("/api/search", async (c) => {
|
|
286
303
|
const { query, top_k, all_projects } = await c.req.json();
|
|
287
304
|
if (!query)
|
package/dist/src/index.js
CHANGED
|
@@ -29,6 +29,10 @@ import { moveDoc } from "./tools/move-doc.js";
|
|
|
29
29
|
import { getDocTree } from "./tools/get-doc-tree.js";
|
|
30
30
|
import { migrateFolders } from "./core/migrate-folders.js";
|
|
31
31
|
import { migrateEmbeddings } from "./core/migrate-embeddings.js";
|
|
32
|
+
import { HistoryStore } from "./core/history-store.js";
|
|
33
|
+
import { listDocVersions } from "./tools/list-doc-versions.js";
|
|
34
|
+
import { getDocVersion } from "./tools/get-doc-version.js";
|
|
35
|
+
import { restoreDocVersion } from "./tools/restore-doc-version.js";
|
|
32
36
|
const __filename = fileURLToPath(import.meta.url);
|
|
33
37
|
const __dirname = path.dirname(__filename);
|
|
34
38
|
function findProjectRoot(startDir) {
|
|
@@ -61,6 +65,9 @@ const embeddingEngine = new EmbeddingEngine(embeddingStore);
|
|
|
61
65
|
storageEngine.ensureProject(storageEngine.getConfig().currentProject);
|
|
62
66
|
const openaiModel = process.env.MCP_DOCUMENTOR_OPENAI_MODEL || "text-embedding-3-small";
|
|
63
67
|
embeddingStore.init(storageEngine.getDataDir(), openaiModel);
|
|
68
|
+
const historyStore = new HistoryStore();
|
|
69
|
+
historyStore.init(storageEngine.getDataDir());
|
|
70
|
+
storageEngine.setHistoryStore(historyStore);
|
|
64
71
|
const server = new Server({ name: "mcp-documentor", version: APP_VERSION }, { capabilities: { tools: {} } });
|
|
65
72
|
let guiLauncher;
|
|
66
73
|
const sseControllers = new Set();
|
|
@@ -112,6 +119,12 @@ function handleIPCMessage(msg) {
|
|
|
112
119
|
else if (msg.type === "GIT_DIFF") {
|
|
113
120
|
handleGitDiff(msg);
|
|
114
121
|
}
|
|
122
|
+
else if (msg.type === "GET_DOC_HISTORY") {
|
|
123
|
+
handleGetDocHistory(msg);
|
|
124
|
+
}
|
|
125
|
+
else if (msg.type === "GET_DOC_VERSION") {
|
|
126
|
+
handleGetDocVersionIPC(msg);
|
|
127
|
+
}
|
|
115
128
|
else {
|
|
116
129
|
logger.debug(`Unhandled message from GUI: ${msg.type}`);
|
|
117
130
|
}
|
|
@@ -361,6 +374,52 @@ async function handleUpdateConfig(msg) {
|
|
|
361
374
|
});
|
|
362
375
|
}
|
|
363
376
|
}
|
|
377
|
+
async function handleGetDocHistory(msg) {
|
|
378
|
+
try {
|
|
379
|
+
const id = msg.data?.id ?? msg.data?.path;
|
|
380
|
+
const config = storageEngine.getConfig();
|
|
381
|
+
const project = msg.data?.project ?? config.currentProject;
|
|
382
|
+
const versions = historyStore.listVersions(project, id);
|
|
383
|
+
broadcastToGUI({
|
|
384
|
+
type: "GET_DOC_HISTORY_RESPONSE",
|
|
385
|
+
correlationId: msg.correlationId,
|
|
386
|
+
data: { versions },
|
|
387
|
+
timestamp: new Date().toISOString(),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
broadcastToGUI({
|
|
392
|
+
type: "GET_DOC_HISTORY_RESPONSE",
|
|
393
|
+
correlationId: msg.correlationId,
|
|
394
|
+
error: err.message,
|
|
395
|
+
timestamp: new Date().toISOString(),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async function handleGetDocVersionIPC(msg) {
|
|
400
|
+
try {
|
|
401
|
+
const id = msg.data?.id ?? msg.data?.path;
|
|
402
|
+
const version = msg.data?.version;
|
|
403
|
+
const config = storageEngine.getConfig();
|
|
404
|
+
const project = msg.data?.project ?? config.currentProject;
|
|
405
|
+
const entry = historyStore.getVersion(project, id, version);
|
|
406
|
+
broadcastToGUI({
|
|
407
|
+
type: "GET_DOC_VERSION_RESPONSE",
|
|
408
|
+
correlationId: msg.correlationId,
|
|
409
|
+
data: entry ?? null,
|
|
410
|
+
error: entry ? undefined : "Version not found",
|
|
411
|
+
timestamp: new Date().toISOString(),
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
catch (err) {
|
|
415
|
+
broadcastToGUI({
|
|
416
|
+
type: "GET_DOC_VERSION_RESPONSE",
|
|
417
|
+
correlationId: msg.correlationId,
|
|
418
|
+
error: err.message,
|
|
419
|
+
timestamp: new Date().toISOString(),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
364
423
|
guiLauncher = new GuiLauncher({
|
|
365
424
|
guiPath: path.join(__dirname, "gui-worker.js"),
|
|
366
425
|
env: { ...process.env },
|
|
@@ -612,6 +671,44 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
612
671
|
},
|
|
613
672
|
},
|
|
614
673
|
},
|
|
674
|
+
{
|
|
675
|
+
name: "list_doc_versions",
|
|
676
|
+
description: "List all saved versions of a documentation entry, with date and change type.",
|
|
677
|
+
inputSchema: {
|
|
678
|
+
type: "object",
|
|
679
|
+
properties: {
|
|
680
|
+
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
681
|
+
project: { type: "string", description: "Project to look in (defaults to current project)" },
|
|
682
|
+
},
|
|
683
|
+
required: ["path"],
|
|
684
|
+
},
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: "get_doc_version",
|
|
688
|
+
description: "Retrieve the full content of a specific version of a documentation entry.",
|
|
689
|
+
inputSchema: {
|
|
690
|
+
type: "object",
|
|
691
|
+
properties: {
|
|
692
|
+
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
693
|
+
version: { type: "number", description: "Version number to retrieve" },
|
|
694
|
+
project: { type: "string", description: "Project to look in (defaults to current project)" },
|
|
695
|
+
},
|
|
696
|
+
required: ["path", "version"],
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: "restore_doc_version",
|
|
701
|
+
description: "Restore a documentation entry to a previous version. The current state is saved as a new version before overwriting.",
|
|
702
|
+
inputSchema: {
|
|
703
|
+
type: "object",
|
|
704
|
+
properties: {
|
|
705
|
+
path: { type: "string", description: "Path of the documentation entry, e.g. 'backend/auth/authentication'" },
|
|
706
|
+
version: { type: "number", description: "Version number to restore" },
|
|
707
|
+
project: { type: "string", description: "Project to look in (defaults to current project)" },
|
|
708
|
+
},
|
|
709
|
+
required: ["path", "version"],
|
|
710
|
+
},
|
|
711
|
+
},
|
|
615
712
|
],
|
|
616
713
|
};
|
|
617
714
|
});
|
|
@@ -651,6 +748,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
651
748
|
return await moveDoc(storageEngine, embeddingEngine, args);
|
|
652
749
|
case "get_doc_tree":
|
|
653
750
|
return await getDocTree(storageEngine, args);
|
|
751
|
+
case "list_doc_versions":
|
|
752
|
+
return await listDocVersions(historyStore, storageEngine, args);
|
|
753
|
+
case "get_doc_version":
|
|
754
|
+
return await getDocVersion(historyStore, storageEngine, args);
|
|
755
|
+
case "restore_doc_version":
|
|
756
|
+
return await restoreDocVersion(historyStore, storageEngine, embeddingEngine, args);
|
|
654
757
|
default:
|
|
655
758
|
throw new Error(`Tool not found: ${name}`);
|
|
656
759
|
}
|
|
@@ -55,7 +55,7 @@ export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
|
55
55
|
updatedAt: now,
|
|
56
56
|
};
|
|
57
57
|
const resolvedProject = project ?? storageEngine.getConfig().currentProject;
|
|
58
|
-
await storageEngine.saveDoc(doc, resolvedProject);
|
|
58
|
+
await storageEngine.saveDoc(doc, resolvedProject, "create");
|
|
59
59
|
if (embeddingEngine.isConfigured()) {
|
|
60
60
|
const embedding = await embeddingEngine.generateEmbedding(validated.content);
|
|
61
61
|
if (embedding) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HistoryStore } from "../core/history-store.js";
|
|
2
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
3
|
+
export declare function getDocVersion(historyStore: HistoryStore, storageEngine: StorageEngine, args: unknown): Promise<{
|
|
4
|
+
isError: boolean;
|
|
5
|
+
content: {
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
} | {
|
|
10
|
+
content: {
|
|
11
|
+
type: string;
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError?: undefined;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { GetDocVersionArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
export async function getDocVersion(historyStore, storageEngine, args) {
|
|
3
|
+
try {
|
|
4
|
+
const validated = GetDocVersionArgsSchema.parse(args);
|
|
5
|
+
const config = storageEngine.getConfig();
|
|
6
|
+
const project = validated.project ?? config.currentProject;
|
|
7
|
+
const entry = historyStore.getVersion(project, validated.path, validated.version);
|
|
8
|
+
if (!entry) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `Version ${validated.version} of "${validated.path}" not found in project "${project}".`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const date = new Date(entry.changedAt).toLocaleString();
|
|
20
|
+
const meta = entry.metadata;
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: [
|
|
26
|
+
`# ${meta.title} (v${entry.version})`,
|
|
27
|
+
`**Date:** ${date}`,
|
|
28
|
+
`**Change type:** ${entry.changeType}`,
|
|
29
|
+
`**Tags:** ${meta.tags.join(", ") || "none"}`,
|
|
30
|
+
`**Related docs:** ${meta.related_docs.join(", ") || "none"}`,
|
|
31
|
+
`**Tracked files:** ${meta.tracked_files.map((f) => f.path).join(", ") || "none"}`,
|
|
32
|
+
"",
|
|
33
|
+
"---",
|
|
34
|
+
"",
|
|
35
|
+
entry.content,
|
|
36
|
+
].join("\n"),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
return { isError: true, content: [{ type: "text", text: `Error getting version: ${message}` }] };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HistoryStore } from "../core/history-store.js";
|
|
2
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
3
|
+
export declare function listDocVersions(historyStore: HistoryStore, storageEngine: StorageEngine, args: unknown): Promise<{
|
|
4
|
+
content: {
|
|
5
|
+
type: string;
|
|
6
|
+
text: string;
|
|
7
|
+
}[];
|
|
8
|
+
isError?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
isError: boolean;
|
|
11
|
+
content: {
|
|
12
|
+
type: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ListDocVersionsArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
export async function listDocVersions(historyStore, storageEngine, args) {
|
|
3
|
+
try {
|
|
4
|
+
const validated = ListDocVersionsArgsSchema.parse(args);
|
|
5
|
+
const config = storageEngine.getConfig();
|
|
6
|
+
const project = validated.project ?? config.currentProject;
|
|
7
|
+
const versions = historyStore.listVersions(project, validated.path);
|
|
8
|
+
if (versions.length === 0) {
|
|
9
|
+
return {
|
|
10
|
+
content: [
|
|
11
|
+
{
|
|
12
|
+
type: "text",
|
|
13
|
+
text: `No version history found for "${validated.path}" in project "${project}".`,
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const lines = versions.map((v) => {
|
|
19
|
+
const date = new Date(v.changedAt).toLocaleString();
|
|
20
|
+
return `v${v.version} — ${date} — ${v.changeType} — "${v.metadata.title}"`;
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
content: [
|
|
24
|
+
{
|
|
25
|
+
type: "text",
|
|
26
|
+
text: `Version history for "${validated.path}" in project "${project}" (${versions.length} version(s)):\n\n${lines.join("\n")}`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
return { isError: true, content: [{ type: "text", text: `Error listing versions: ${message}` }] };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -19,7 +19,7 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
19
19
|
}
|
|
20
20
|
doc.content = doc.content.replace(validated.search, validated.replacement);
|
|
21
21
|
doc.updatedAt = new Date().toISOString();
|
|
22
|
-
await storageEngine.saveDoc(doc, resolvedProject);
|
|
22
|
+
await storageEngine.saveDoc(doc, resolvedProject, "patch");
|
|
23
23
|
if (embeddingEngine.isConfigured()) {
|
|
24
24
|
const embedding = await embeddingEngine.generateEmbedding(doc.content);
|
|
25
25
|
if (embedding) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HistoryStore } from "../core/history-store.js";
|
|
2
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
3
|
+
import type { EmbeddingEngine } from "../core/embedding-engine.js";
|
|
4
|
+
export declare function restoreDocVersion(historyStore: HistoryStore, storageEngine: StorageEngine, embeddingEngine: EmbeddingEngine, args: unknown): Promise<{
|
|
5
|
+
isError: boolean;
|
|
6
|
+
content: {
|
|
7
|
+
type: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}[];
|
|
10
|
+
} | {
|
|
11
|
+
content: {
|
|
12
|
+
type: string;
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
isError?: undefined;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { RestoreDocVersionArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
2
|
+
export async function restoreDocVersion(historyStore, storageEngine, embeddingEngine, args) {
|
|
3
|
+
try {
|
|
4
|
+
const validated = RestoreDocVersionArgsSchema.parse(args);
|
|
5
|
+
const config = storageEngine.getConfig();
|
|
6
|
+
const project = validated.project ?? config.currentProject;
|
|
7
|
+
const entry = historyStore.getVersion(project, validated.path, validated.version);
|
|
8
|
+
if (!entry || entry.content === undefined) {
|
|
9
|
+
return {
|
|
10
|
+
isError: true,
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `Version ${validated.version} of "${validated.path}" not found in project "${project}".`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const meta = entry.metadata;
|
|
20
|
+
const now = new Date().toISOString();
|
|
21
|
+
const currentDoc = await storageEngine.getDoc(validated.path, project);
|
|
22
|
+
const doc = {
|
|
23
|
+
id: validated.path,
|
|
24
|
+
title: meta.title,
|
|
25
|
+
content: entry.content,
|
|
26
|
+
tracked_files: meta.tracked_files,
|
|
27
|
+
tags: meta.tags,
|
|
28
|
+
related_docs: meta.related_docs,
|
|
29
|
+
createdAt: currentDoc?.createdAt ?? now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
};
|
|
32
|
+
await storageEngine.saveDoc(doc, project, "update");
|
|
33
|
+
const newVersions = historyStore.listVersions(project, validated.path);
|
|
34
|
+
const newVersion = newVersions[0]?.version ?? "?";
|
|
35
|
+
if (embeddingEngine.isConfigured()) {
|
|
36
|
+
const embedding = await embeddingEngine.generateEmbedding(doc.content);
|
|
37
|
+
if (embedding) {
|
|
38
|
+
await embeddingEngine.saveEmbedding(validated.path, embedding, project);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `Documentation entry "${validated.path}" restored to version ${validated.version}.\nA new version (v${newVersion}) has been recorded in history.`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
52
|
+
return { isError: true, content: [{ type: "text", text: `Error restoring version: ${message}` }] };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -40,7 +40,7 @@ export async function updateDoc(storageEngine, embeddingEngine, args) {
|
|
|
40
40
|
doc.related_docs = validated.related_docs;
|
|
41
41
|
}
|
|
42
42
|
doc.updatedAt = new Date().toISOString();
|
|
43
|
-
await storageEngine.saveDoc(doc, resolvedProject);
|
|
43
|
+
await storageEngine.saveDoc(doc, resolvedProject, "update");
|
|
44
44
|
if (validated.content !== undefined && embeddingEngine.isConfigured()) {
|
|
45
45
|
const embedding = await embeddingEngine.generateEmbedding(validated.content);
|
|
46
46
|
if (embedding) {
|
package/dist/src/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const APP_VERSION = "1.0.
|
|
1
|
+
export declare const APP_VERSION = "1.0.29";
|
package/dist/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated version file - do not edit manually
|
|
2
|
-
export const APP_VERSION = '1.0.
|
|
2
|
+
export const APP_VERSION = '1.0.29';
|