@imenam/mcp-documentor 1.0.28 → 1.0.30

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.
@@ -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}`);
@@ -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) {
@@ -1 +1 @@
1
- export declare const APP_VERSION = "1.0.28";
1
+ export declare const APP_VERSION = "1.0.30";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated version file - do not edit manually
2
- export const APP_VERSION = '1.0.28';
2
+ export const APP_VERSION = '1.0.30';