@imenam/mcp-documentor 1.0.82 → 1.0.83
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 +31 -12
- package/dist/shared/schemas/doc.schema.js +31 -10
- package/dist/src/core/draft-store.d.ts +3 -1
- package/dist/src/core/draft-store.js +28 -8
- package/dist/src/core/text-input.d.ts +29 -0
- package/dist/src/core/text-input.js +29 -0
- package/dist/src/index.js +15 -5
- package/dist/src/tools/create-doc.js +13 -10
- package/dist/src/tools/patch-doc.js +26 -18
- package/dist/src/tools/update-doc.js +18 -15
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +1 -1
|
@@ -99,11 +99,18 @@ export declare const FreshnessReportSchema: z.ZodObject<{
|
|
|
99
99
|
sourceError: z.ZodOptional<z.ZodString>;
|
|
100
100
|
}, z.core.$strip>;
|
|
101
101
|
export type FreshnessReport = z.infer<typeof FreshnessReportSchema>;
|
|
102
|
+
/**
|
|
103
|
+
* Descriptions des formes alternatives d'un texte (`<x>_file_path`, `<x>_file_content`,
|
|
104
|
+
* `<x>_draft`). `<x>_file_path` / `<x>_file_content` forment la paire que le relais de
|
|
105
|
+
* mcp-http-gateway reconnaît : il lit le fichier sur la machine de l'agent et en envoie
|
|
106
|
+
* le contenu.
|
|
107
|
+
*/
|
|
108
|
+
export declare function textSourceDescriptions(prefix: string, what: string): {
|
|
109
|
+
file_path: string;
|
|
110
|
+
file_content: string;
|
|
111
|
+
draft: string;
|
|
112
|
+
};
|
|
102
113
|
export declare const CreateDocArgsSchema: z.ZodObject<{
|
|
103
|
-
project: z.ZodString;
|
|
104
|
-
title: z.ZodString;
|
|
105
|
-
content: z.ZodOptional<z.ZodString>;
|
|
106
|
-
content_file: z.ZodOptional<z.ZodString>;
|
|
107
114
|
tracked_files: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
108
115
|
path: z.ZodString;
|
|
109
116
|
lines: z.ZodArray<z.ZodObject<{
|
|
@@ -114,13 +121,14 @@ export declare const CreateDocArgsSchema: z.ZodObject<{
|
|
|
114
121
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
115
122
|
folder: z.ZodOptional<z.ZodString>;
|
|
116
123
|
related_docs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
content_file_path: z.ZodOptional<z.ZodString>;
|
|
125
|
+
content_file_content: z.ZodOptional<z.ZodString>;
|
|
126
|
+
content_draft: z.ZodOptional<z.ZodString>;
|
|
120
127
|
project: z.ZodString;
|
|
121
|
-
title: z.
|
|
128
|
+
title: z.ZodString;
|
|
122
129
|
content: z.ZodOptional<z.ZodString>;
|
|
123
|
-
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
export declare const UpdateDocArgsSchema: z.ZodObject<{
|
|
124
132
|
tracked_files: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
125
133
|
path: z.ZodString;
|
|
126
134
|
lines: z.ZodArray<z.ZodObject<{
|
|
@@ -130,6 +138,13 @@ export declare const UpdateDocArgsSchema: z.ZodObject<{
|
|
|
130
138
|
}, z.core.$strip>]>>>;
|
|
131
139
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
132
140
|
related_docs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
141
|
+
content_file_path: z.ZodOptional<z.ZodString>;
|
|
142
|
+
content_file_content: z.ZodOptional<z.ZodString>;
|
|
143
|
+
content_draft: z.ZodOptional<z.ZodString>;
|
|
144
|
+
path: z.ZodString;
|
|
145
|
+
project: z.ZodString;
|
|
146
|
+
title: z.ZodOptional<z.ZodString>;
|
|
147
|
+
content: z.ZodOptional<z.ZodString>;
|
|
133
148
|
}, z.core.$strip>;
|
|
134
149
|
export declare const GetDocArgsSchema: z.ZodObject<{
|
|
135
150
|
path: z.ZodString;
|
|
@@ -162,12 +177,16 @@ export declare const GetDocMetadataArgsSchema: z.ZodObject<{
|
|
|
162
177
|
project: z.ZodString;
|
|
163
178
|
}, z.core.$strip>;
|
|
164
179
|
export declare const PatchDocArgsSchema: z.ZodObject<{
|
|
180
|
+
replacement_file_path: z.ZodOptional<z.ZodString>;
|
|
181
|
+
replacement_file_content: z.ZodOptional<z.ZodString>;
|
|
182
|
+
replacement_draft: z.ZodOptional<z.ZodString>;
|
|
183
|
+
replacement: z.ZodOptional<z.ZodString>;
|
|
184
|
+
search_file_path: z.ZodOptional<z.ZodString>;
|
|
185
|
+
search_file_content: z.ZodOptional<z.ZodString>;
|
|
186
|
+
search_draft: z.ZodOptional<z.ZodString>;
|
|
165
187
|
path: z.ZodString;
|
|
166
188
|
project: z.ZodString;
|
|
167
189
|
search: z.ZodOptional<z.ZodString>;
|
|
168
|
-
search_file: z.ZodOptional<z.ZodString>;
|
|
169
|
-
replacement: z.ZodOptional<z.ZodString>;
|
|
170
|
-
replacement_file: z.ZodOptional<z.ZodString>;
|
|
171
190
|
}, z.core.$strip>;
|
|
172
191
|
export declare const CheckLinksArgsSchema: z.ZodObject<{
|
|
173
192
|
project: z.ZodString;
|
|
@@ -70,24 +70,45 @@ const requiredProject = (action) => z
|
|
|
70
70
|
.string()
|
|
71
71
|
.min(1)
|
|
72
72
|
.describe(`Project the documentation entry belongs to. Mandatory — ${action} is never resolved across projects, and never falls back to the current project.`);
|
|
73
|
+
/**
|
|
74
|
+
* Descriptions des formes alternatives d'un texte (`<x>_file_path`, `<x>_file_content`,
|
|
75
|
+
* `<x>_draft`). `<x>_file_path` / `<x>_file_content` forment la paire que le relais de
|
|
76
|
+
* mcp-http-gateway reconnaît : il lit le fichier sur la machine de l'agent et en envoie
|
|
77
|
+
* le contenu.
|
|
78
|
+
*/
|
|
79
|
+
export function textSourceDescriptions(prefix, what) {
|
|
80
|
+
return {
|
|
81
|
+
file_path: `Absolute path to a UTF-8 text file on the agent machine whose content is used as ${what}, instead of ${prefix}. The file is left untouched.`,
|
|
82
|
+
file_content: "Filled in by the mcp-http-gateway relay, never by the agent: the base64 content of the file read on the agent machine.",
|
|
83
|
+
draft: `Name of a draft saved by the server after a failed call (the name is given in the error message), used as ${what} instead of ${prefix}. The draft is deleted once the call succeeds.`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function textSourceFields(prefix, what) {
|
|
87
|
+
const d = textSourceDescriptions(prefix, what);
|
|
88
|
+
return {
|
|
89
|
+
[`${prefix}_file_path`]: z.string().optional().describe(d.file_path),
|
|
90
|
+
[`${prefix}_file_content`]: z.string().optional().describe(d.file_content),
|
|
91
|
+
[`${prefix}_draft`]: z.string().optional().describe(d.draft),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
73
94
|
export const CreateDocArgsSchema = z.object({
|
|
74
95
|
project: requiredProject("creation").describe("Project to create the documentation entry in. Mandatory — never defaulted to the current project. The project is created automatically if it does not exist yet."),
|
|
75
96
|
title: z.string().describe("Title of the documentation entry"),
|
|
76
97
|
content: z.string().optional().describe("Documentation content in markdown"),
|
|
77
|
-
|
|
98
|
+
...textSourceFields("content", "the content"),
|
|
78
99
|
tracked_files: z.array(TrackedFileInputSchema).optional().describe("Files to track for staleness detection. Each entry is either a plain path string (whole-file tracking) or an object { path, lines: [{ start, end }, ...] } for granular section tracking. Lines are 1-indexed and inclusive. Provide absolute paths, or paths relative to the project's code root; they are stored relative to that root so the docs stay portable across machines. Tracked files must exist at the project's code source — which is the local working copy by default, or a branch of a GitHub repository when one is configured for the project (check_freshness reports which)."),
|
|
79
100
|
tags: z.array(z.string()).optional().describe("Tags for categorization"),
|
|
80
101
|
folder: z.string().optional().describe("Folder path for organization, e.g. 'frontend/components'"),
|
|
81
102
|
related_docs: z.array(z.string()).optional().describe("IDs of related documentation entries"),
|
|
82
|
-
}).refine(d => d.content || d.
|
|
83
|
-
message: "
|
|
103
|
+
}).refine(d => d.content !== undefined || d.content_file_path !== undefined || d.content_file_content !== undefined || d.content_draft !== undefined, {
|
|
104
|
+
message: "One of content, content_file_path or content_draft is required",
|
|
84
105
|
});
|
|
85
106
|
export const UpdateDocArgsSchema = z.object({
|
|
86
107
|
path: z.string().describe("Path of the documentation entry to update, e.g. 'backend/auth/authentication' or 'authentication' for root docs"),
|
|
87
108
|
project: requiredProject("the update"),
|
|
88
109
|
title: z.string().optional().describe("Title of the documentation entry"),
|
|
89
110
|
content: z.string().optional().describe("New content (replaces existing)"),
|
|
90
|
-
|
|
111
|
+
...textSourceFields("content", "the new content"),
|
|
91
112
|
tracked_files: z.array(TrackedFileInputSchema).optional().describe("New list of tracked files (replaces existing). Files already tracked with the same scope keep their stored snapshot, so this never changes staleness — only refresh_file_snapshot acknowledges file changes. Each entry is either a plain path string (whole-file tracking) or an object { path, lines: [{ start, end }, ...] } for granular section tracking. Lines are 1-indexed and inclusive. Provide absolute paths, or paths relative to the project's code root."),
|
|
92
113
|
tags: z.array(z.string()).optional().describe("New tags (replaces existing)"),
|
|
93
114
|
related_docs: z.array(z.string()).optional().describe("Paths of related documentation entries (replaces existing)"),
|
|
@@ -131,13 +152,13 @@ export const PatchDocArgsSchema = z.object({
|
|
|
131
152
|
path: z.string().describe("Path of the documentation entry to patch, e.g. 'backend/auth/authentication'"),
|
|
132
153
|
project: requiredProject("the patch"),
|
|
133
154
|
search: z.string().optional().describe("Exact text to find in the current content"),
|
|
134
|
-
|
|
155
|
+
...textSourceFields("search", "the search text"),
|
|
135
156
|
replacement: z.string().optional().describe("Text to replace it with"),
|
|
136
|
-
|
|
137
|
-
}).refine(d => d.search || d.
|
|
138
|
-
message: "
|
|
139
|
-
}).refine(d => d.replacement || d.
|
|
140
|
-
message: "
|
|
157
|
+
...textSourceFields("replacement", "the replacement"),
|
|
158
|
+
}).refine(d => d.search !== undefined || d.search_file_path !== undefined || d.search_file_content !== undefined || d.search_draft !== undefined, {
|
|
159
|
+
message: "One of search, search_file_path or search_draft is required",
|
|
160
|
+
}).refine(d => d.replacement !== undefined || d.replacement_file_path !== undefined || d.replacement_file_content !== undefined || d.replacement_draft !== undefined, {
|
|
161
|
+
message: "One of replacement, replacement_file_path or replacement_draft is required",
|
|
141
162
|
});
|
|
142
163
|
export const CheckLinksArgsSchema = z.object({
|
|
143
164
|
project: requiredProject("the link check").describe("Project whose entries are checked. Mandatory — links are never checked across projects, and there is no current-project fallback."),
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
/** Enregistre un brouillon et renvoie son nom. */
|
|
1
2
|
export declare function persistDraft(dataDir: string, label: string, content: string): string;
|
|
2
|
-
export declare function readDraft(
|
|
3
|
+
export declare function readDraft(dataDir: string, name: string): string;
|
|
4
|
+
export declare function deleteDraft(dataDir: string, name: string): void;
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Brouillons : le texte envoyé par un appel qui a échoué, conservé côté serveur pour être
|
|
5
|
+
* réutilisé par nom. Le nom seul circule, jamais le chemin : il désigne un fichier de la
|
|
6
|
+
* machine du serveur, que l'agent n'a pas forcément sous la main.
|
|
7
|
+
*/
|
|
8
|
+
function draftsDir(dataDir) {
|
|
9
|
+
return path.join(dataDir, "errors-tmp-files");
|
|
10
|
+
}
|
|
11
|
+
function draftFile(dataDir, name) {
|
|
12
|
+
if (path.basename(name) !== name || name === "." || name === "..") {
|
|
13
|
+
throw new Error(`Invalid draft name: "${name}". Pass the name returned in the error message, without any path.`);
|
|
14
|
+
}
|
|
15
|
+
return path.join(draftsDir(dataDir), name);
|
|
16
|
+
}
|
|
17
|
+
/** Enregistre un brouillon et renvoie son nom. */
|
|
3
18
|
export function persistDraft(dataDir, label, content) {
|
|
4
|
-
|
|
5
|
-
fs.mkdirSync(draftsDir, { recursive: true });
|
|
19
|
+
fs.mkdirSync(draftsDir(dataDir), { recursive: true });
|
|
6
20
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
const name = `${timestamp}-${label}.txt`;
|
|
22
|
+
fs.writeFileSync(path.join(draftsDir(dataDir), name), content, "utf-8");
|
|
23
|
+
return name;
|
|
24
|
+
}
|
|
25
|
+
export function readDraft(dataDir, name) {
|
|
26
|
+
const file = draftFile(dataDir, name);
|
|
27
|
+
if (!fs.existsSync(file)) {
|
|
28
|
+
throw new Error(`Draft not found: "${name}".`);
|
|
29
|
+
}
|
|
30
|
+
return fs.readFileSync(file, "utf-8");
|
|
11
31
|
}
|
|
12
|
-
export function
|
|
13
|
-
|
|
32
|
+
export function deleteDraft(dataDir, name) {
|
|
33
|
+
fs.unlinkSync(draftFile(dataDir, name));
|
|
14
34
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Un texte fourni à un outil, sous l'une de trois formes exclusives :
|
|
3
|
+
* - en ligne (`inline`) ;
|
|
4
|
+
* - par un fichier de la machine de l'agent (`filePath`). Derrière mcp-http-gateway, le
|
|
5
|
+
* relais lit ce fichier et envoie son contenu en base64 dans `fileContent` ;
|
|
6
|
+
* - par le nom d'un brouillon conservé côté serveur (`draft`).
|
|
7
|
+
*/
|
|
8
|
+
export interface TextInput {
|
|
9
|
+
inline?: string;
|
|
10
|
+
filePath?: string;
|
|
11
|
+
fileContent?: string;
|
|
12
|
+
draft?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Noms des paramètres de l'outil, pour les messages d'erreur. */
|
|
15
|
+
export interface TextInputParams {
|
|
16
|
+
inline: string;
|
|
17
|
+
file: string;
|
|
18
|
+
draft: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const CONTENT_PARAMS: TextInputParams;
|
|
21
|
+
export declare const SEARCH_PARAMS: TextInputParams;
|
|
22
|
+
export declare const REPLACEMENT_PARAMS: TextInputParams;
|
|
23
|
+
export interface ResolvedText {
|
|
24
|
+
text: string;
|
|
25
|
+
/** Nom du brouillon utilisé, à supprimer une fois l'opération réussie. */
|
|
26
|
+
draft?: string;
|
|
27
|
+
}
|
|
28
|
+
/** Renvoie le texte, ou `undefined` quand aucune forme n'est fournie. Le fichier de l'agent n'est jamais modifié. */
|
|
29
|
+
export declare function resolveTextInput(dataDir: string, input: TextInput, params: TextInputParams): ResolvedText | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import { readDraft } from "./draft-store.js";
|
|
3
|
+
export const CONTENT_PARAMS = { inline: "content", file: "content_file_path", draft: "content_draft" };
|
|
4
|
+
export const SEARCH_PARAMS = { inline: "search", file: "search_file_path", draft: "search_draft" };
|
|
5
|
+
export const REPLACEMENT_PARAMS = { inline: "replacement", file: "replacement_file_path", draft: "replacement_draft" };
|
|
6
|
+
function countSources(input) {
|
|
7
|
+
const file = input.filePath !== undefined || input.fileContent !== undefined;
|
|
8
|
+
return [input.inline !== undefined, file, input.draft !== undefined].filter(Boolean).length;
|
|
9
|
+
}
|
|
10
|
+
/** Renvoie le texte, ou `undefined` quand aucune forme n'est fournie. Le fichier de l'agent n'est jamais modifié. */
|
|
11
|
+
export function resolveTextInput(dataDir, input, params) {
|
|
12
|
+
const count = countSources(input);
|
|
13
|
+
if (count === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
if (count > 1) {
|
|
16
|
+
throw new Error(`Provide only one of ${params.inline}, ${params.file} or ${params.draft}.`);
|
|
17
|
+
}
|
|
18
|
+
if (input.inline !== undefined)
|
|
19
|
+
return { text: input.inline };
|
|
20
|
+
if (input.draft !== undefined)
|
|
21
|
+
return { text: readDraft(dataDir, input.draft), draft: input.draft };
|
|
22
|
+
if (input.fileContent !== undefined) {
|
|
23
|
+
return { text: Buffer.from(input.fileContent.replace(/^data:[^;,]*;base64,/, ""), "base64").toString("utf-8") };
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(input.filePath)) {
|
|
26
|
+
throw new Error(`File not found: ${input.filePath} (${params.file}).`);
|
|
27
|
+
}
|
|
28
|
+
return { text: fs.readFileSync(input.filePath, "utf-8") };
|
|
29
|
+
}
|
package/dist/src/index.js
CHANGED
|
@@ -45,6 +45,7 @@ import { getImage } from "./tools/get-image.js";
|
|
|
45
45
|
import { SyncEngine } from "./core/git-sync.js";
|
|
46
46
|
import { syncStatus, pullDocs, pushDocs, syncDocs, resolveSyncConflict } from "./tools/sync.js";
|
|
47
47
|
import { listConflicts, getConflictHunks, resolveConflictHunks } from "./tools/conflicts.js";
|
|
48
|
+
import { textSourceDescriptions, } from "../shared/schemas/doc.schema.js";
|
|
48
49
|
import { isReadOnly, WRITE_TOOLS } from "../shared/read-only.js";
|
|
49
50
|
const __filename = fileURLToPath(import.meta.url);
|
|
50
51
|
const __dirname = path.dirname(__filename);
|
|
@@ -883,6 +884,15 @@ async function reconnectGui(args) {
|
|
|
883
884
|
}
|
|
884
885
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
885
886
|
const PROJECT_PARAM_DESCRIPTION = "Project the documentation entry belongs to. REQUIRED — never guessed, and never defaulted to the current project. Use list_projects or get_doc_tree if you are unsure which project holds the entry.";
|
|
887
|
+
/** Propriétés `<x>_file_path` / `<x>_file_content` / `<x>_draft` d'un texte, pour les schémas publiés. */
|
|
888
|
+
const textSourceProperties = (prefix, what) => {
|
|
889
|
+
const d = textSourceDescriptions(prefix, what);
|
|
890
|
+
return {
|
|
891
|
+
[`${prefix}_file_path`]: { type: "string", description: d.file_path },
|
|
892
|
+
[`${prefix}_file_content`]: { type: "string", description: d.file_content },
|
|
893
|
+
[`${prefix}_draft`]: { type: "string", description: d.draft },
|
|
894
|
+
};
|
|
895
|
+
};
|
|
886
896
|
const tools = [
|
|
887
897
|
{
|
|
888
898
|
name: "create_doc",
|
|
@@ -892,7 +902,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
892
902
|
properties: {
|
|
893
903
|
title: { type: "string", description: "Title of the documentation entry" },
|
|
894
904
|
content: { type: "string", description: "Documentation content in markdown. To link to another document inline, use the syntax [text](doc://target-doc-id) — the viewer will navigate to that document on click. To link to a heading within the same document, use [text](#heading-slug). Use check_links to validate both types. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
|
|
895
|
-
|
|
905
|
+
...textSourceProperties("content", "the content"),
|
|
896
906
|
tracked_files: {
|
|
897
907
|
type: "array",
|
|
898
908
|
items: {
|
|
@@ -973,7 +983,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
973
983
|
description: "New display title stored in frontmatter. Does not rename the file or change the document path — use move_doc to relocate or rename on disk.",
|
|
974
984
|
},
|
|
975
985
|
content: { type: "string", description: "New content (replaces existing). Supports [text](doc://target-doc-path) to link inline to other documents, and [text](#heading-slug) to link to headings within this document. For code blocks, always specify a language identifier for syntax highlighting, e.g. ```typescript, ```python, ```bash, ```json, ```yaml, ```sql. To embed a code excerpt directly from a source file, use [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block with automatic language detection, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
|
|
976
|
-
|
|
986
|
+
...textSourceProperties("content", "the new content"),
|
|
977
987
|
tracked_files: {
|
|
978
988
|
type: "array",
|
|
979
989
|
items: {
|
|
@@ -1105,12 +1115,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
1105
1115
|
properties: {
|
|
1106
1116
|
path: { type: "string", description: "Path of the documentation entry to patch, e.g. 'backend/auth/authentication'" },
|
|
1107
1117
|
search: { type: "string", description: "Exact text to find in the current content" },
|
|
1108
|
-
|
|
1118
|
+
...textSourceProperties("search", "the search text"),
|
|
1109
1119
|
replacement: { type: "string", description: "Text to replace it with. Supports [copy-from-codebase|/absolute/path/to/file.ts|startLine|endLine] — the tag is replaced at save time by a fenced code block, and the file+range is automatically added to tracked_files. IMPORTANT: always read the source file first to identify the exact line numbers before writing the tag." },
|
|
1110
|
-
|
|
1120
|
+
...textSourceProperties("replacement", "the replacement"),
|
|
1111
1121
|
project: { type: "string", description: PROJECT_PARAM_DESCRIPTION },
|
|
1112
1122
|
},
|
|
1113
|
-
required: ["path", "
|
|
1123
|
+
required: ["path", "project"],
|
|
1114
1124
|
},
|
|
1115
1125
|
},
|
|
1116
1126
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
1
|
import { CreateDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
3
|
-
import { persistDraft } from "../core/draft-store.js";
|
|
2
|
+
import { persistDraft, deleteDraft } from "../core/draft-store.js";
|
|
3
|
+
import { resolveTextInput, CONTENT_PARAMS } from "../core/text-input.js";
|
|
4
4
|
import { resolveCodeRefs, snapshotTrackedFiles } from "../core/code-ref-resolver.js";
|
|
5
5
|
export function slugify(title) {
|
|
6
6
|
return title
|
|
@@ -14,10 +14,13 @@ export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
|
14
14
|
try {
|
|
15
15
|
const validated = CreateDocArgsSchema.parse(args);
|
|
16
16
|
const { project } = validated;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
: validated.
|
|
20
|
-
|
|
17
|
+
const source = resolveTextInput(storageEngine.getDataDir(), {
|
|
18
|
+
inline: validated.content,
|
|
19
|
+
filePath: validated.content_file_path,
|
|
20
|
+
fileContent: validated.content_file_content,
|
|
21
|
+
draft: validated.content_draft,
|
|
22
|
+
}, CONTENT_PARAMS);
|
|
23
|
+
const { content, additionalTrackedFiles } = await resolveCodeRefs(source.text, project);
|
|
21
24
|
const slug = slugify(validated.title);
|
|
22
25
|
const id = validated.folder ? `${validated.folder}/${slug}` : slug;
|
|
23
26
|
const now = new Date().toISOString();
|
|
@@ -54,8 +57,8 @@ export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
|
54
57
|
await embeddingEngine.saveEmbedding(id, embedding, resolvedProject);
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
|
-
if (
|
|
58
|
-
|
|
60
|
+
if (source.draft) {
|
|
61
|
+
deleteDraft(storageEngine.getDataDir(), source.draft);
|
|
59
62
|
}
|
|
60
63
|
return {
|
|
61
64
|
content: [
|
|
@@ -70,10 +73,10 @@ export async function createDoc(storageEngine, embeddingEngine, args) {
|
|
|
70
73
|
const message = err instanceof Error ? err.message : String(err);
|
|
71
74
|
const rawContent = args?.content;
|
|
72
75
|
if (rawContent) {
|
|
73
|
-
const
|
|
76
|
+
const draft = persistDraft(storageEngine.getDataDir(), "create-doc", rawContent);
|
|
74
77
|
return {
|
|
75
78
|
isError: true,
|
|
76
|
-
content: [{ type: "text", text: `Error creating doc: ${message}\n\n⚠️ The content has been saved
|
|
79
|
+
content: [{ type: "text", text: `Error creating doc: ${message}\n\n⚠️ The content has been saved as draft "${draft}".\nYou can reuse it directly via the content_draft parameter instead of rewriting it.` }],
|
|
77
80
|
};
|
|
78
81
|
}
|
|
79
82
|
return { isError: true, content: [{ type: "text", text: `Error creating doc: ${message}` }] };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
1
|
import { PatchDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
3
|
-
import { persistDraft } from "../core/draft-store.js";
|
|
2
|
+
import { persistDraft, deleteDraft } from "../core/draft-store.js";
|
|
3
|
+
import { resolveTextInput, SEARCH_PARAMS, REPLACEMENT_PARAMS } from "../core/text-input.js";
|
|
4
4
|
import { resolveCodeRefs, mergeTrackedFiles } from "../core/code-ref-resolver.js";
|
|
5
5
|
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
6
6
|
export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
@@ -11,13 +11,21 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
11
11
|
return scopeErrorResult(resolved);
|
|
12
12
|
const { doc } = resolved;
|
|
13
13
|
const resolvedProject = validated.project;
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
: validated.search
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: validated.
|
|
20
|
-
|
|
14
|
+
const dataDir = storageEngine.getDataDir();
|
|
15
|
+
const searchSource = resolveTextInput(dataDir, {
|
|
16
|
+
inline: validated.search,
|
|
17
|
+
filePath: validated.search_file_path,
|
|
18
|
+
fileContent: validated.search_file_content,
|
|
19
|
+
draft: validated.search_draft,
|
|
20
|
+
}, SEARCH_PARAMS);
|
|
21
|
+
const replacementSource = resolveTextInput(dataDir, {
|
|
22
|
+
inline: validated.replacement,
|
|
23
|
+
filePath: validated.replacement_file_path,
|
|
24
|
+
fileContent: validated.replacement_file_content,
|
|
25
|
+
draft: validated.replacement_draft,
|
|
26
|
+
}, REPLACEMENT_PARAMS);
|
|
27
|
+
const search = searchSource.text;
|
|
28
|
+
const { content: replacement, additionalTrackedFiles } = await resolveCodeRefs(replacementSource.text, resolvedProject);
|
|
21
29
|
if (!doc.content.includes(search)) {
|
|
22
30
|
const preview = doc.content.slice(0, 300).replace(/\n/g, "↵");
|
|
23
31
|
return {
|
|
@@ -38,10 +46,10 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
38
46
|
await embeddingEngine.saveEmbedding(validated.path, embedding, resolvedProject);
|
|
39
47
|
}
|
|
40
48
|
}
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
49
|
+
if (searchSource.draft)
|
|
50
|
+
deleteDraft(dataDir, searchSource.draft);
|
|
51
|
+
if (replacementSource.draft)
|
|
52
|
+
deleteDraft(dataDir, replacementSource.draft);
|
|
45
53
|
return {
|
|
46
54
|
content: [{ type: "text", text: `Documentation entry "${validated.path}" patched successfully.` }],
|
|
47
55
|
};
|
|
@@ -53,16 +61,16 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
53
61
|
if (rawSearch || rawReplacement) {
|
|
54
62
|
const parts = [];
|
|
55
63
|
if (rawSearch) {
|
|
56
|
-
const
|
|
57
|
-
parts.push(`search → ${
|
|
64
|
+
const searchDraft = persistDraft(storageEngine.getDataDir(), "patch-doc-search", rawSearch);
|
|
65
|
+
parts.push(`search → draft "${searchDraft}"`);
|
|
58
66
|
}
|
|
59
67
|
if (rawReplacement) {
|
|
60
|
-
const
|
|
61
|
-
parts.push(`replacement → ${
|
|
68
|
+
const replacementDraft = persistDraft(storageEngine.getDataDir(), "patch-doc-replacement", rawReplacement);
|
|
69
|
+
parts.push(`replacement → draft "${replacementDraft}"`);
|
|
62
70
|
}
|
|
63
71
|
return {
|
|
64
72
|
isError: true,
|
|
65
|
-
content: [{ type: "text", text: `Error patching doc: ${message}\n\n⚠️ The following content has been saved:\n${parts.join("\n")}\nYou can reuse them directly via the
|
|
73
|
+
content: [{ type: "text", text: `Error patching doc: ${message}\n\n⚠️ The following content has been saved as drafts:\n${parts.join("\n")}\nYou can reuse them directly via the search_draft / replacement_draft parameters instead of rewriting them.` }],
|
|
66
74
|
};
|
|
67
75
|
}
|
|
68
76
|
return { isError: true, content: [{ type: "text", text: `Error patching doc: ${message}` }] };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
1
|
import { UpdateDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
3
|
-
import { persistDraft } from "../core/draft-store.js";
|
|
2
|
+
import { persistDraft, deleteDraft } from "../core/draft-store.js";
|
|
3
|
+
import { resolveTextInput, CONTENT_PARAMS } from "../core/text-input.js";
|
|
4
4
|
import { resolveCodeRefs, resnapshotPreserving, mergeTrackedFiles } from "../core/code-ref-resolver.js";
|
|
5
5
|
import { resolveDocInProject, isScopeError, scopeErrorResult } from "../core/project-scope.js";
|
|
6
6
|
export async function updateDoc(storageEngine, embeddingEngine, args) {
|
|
@@ -15,13 +15,14 @@ export async function updateDoc(storageEngine, embeddingEngine, args) {
|
|
|
15
15
|
doc.title = validated.title;
|
|
16
16
|
}
|
|
17
17
|
let additionalTrackedFiles = [];
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const source = resolveTextInput(storageEngine.getDataDir(), {
|
|
19
|
+
inline: validated.content,
|
|
20
|
+
filePath: validated.content_file_path,
|
|
21
|
+
fileContent: validated.content_file_content,
|
|
22
|
+
draft: validated.content_draft,
|
|
23
|
+
}, CONTENT_PARAMS);
|
|
24
|
+
if (source) {
|
|
25
|
+
const { content, additionalTrackedFiles: refs } = await resolveCodeRefs(source.text, resolvedProject);
|
|
25
26
|
doc.content = content;
|
|
26
27
|
additionalTrackedFiles = refs;
|
|
27
28
|
}
|
|
@@ -47,22 +48,24 @@ export async function updateDoc(storageEngine, embeddingEngine, args) {
|
|
|
47
48
|
}
|
|
48
49
|
doc.updatedAt = new Date().toISOString();
|
|
49
50
|
await storageEngine.saveDoc(doc, resolvedProject, "update");
|
|
50
|
-
if (
|
|
51
|
+
if (source && embeddingEngine.isConfigured()) {
|
|
51
52
|
const embedding = await embeddingEngine.generateEmbedding(doc.content);
|
|
52
53
|
if (embedding) {
|
|
53
54
|
await embeddingEngine.saveEmbedding(validated.path, embedding, resolvedProject);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
if (
|
|
57
|
-
|
|
57
|
+
if (source?.draft) {
|
|
58
|
+
deleteDraft(storageEngine.getDataDir(), source.draft);
|
|
58
59
|
}
|
|
59
60
|
const changes = [];
|
|
60
61
|
if (validated.title !== undefined)
|
|
61
62
|
changes.push(`title (${doc.title})`);
|
|
62
63
|
if (validated.content !== undefined)
|
|
63
64
|
changes.push("content");
|
|
64
|
-
if (validated.
|
|
65
|
+
if (validated.content_file_path !== undefined || validated.content_file_content !== undefined)
|
|
65
66
|
changes.push("content (from file)");
|
|
67
|
+
if (validated.content_draft !== undefined)
|
|
68
|
+
changes.push(`content (from draft "${validated.content_draft}")`);
|
|
66
69
|
if (trackedResult !== undefined) {
|
|
67
70
|
const parts = [`${doc.tracked_files.length} files`];
|
|
68
71
|
if (trackedResult.preserved.length > 0)
|
|
@@ -87,10 +90,10 @@ export async function updateDoc(storageEngine, embeddingEngine, args) {
|
|
|
87
90
|
const message = err instanceof Error ? err.message : String(err);
|
|
88
91
|
const rawContent = args?.content;
|
|
89
92
|
if (rawContent) {
|
|
90
|
-
const
|
|
93
|
+
const draft = persistDraft(storageEngine.getDataDir(), "update-doc", rawContent);
|
|
91
94
|
return {
|
|
92
95
|
isError: true,
|
|
93
|
-
content: [{ type: "text", text: `Error updating doc: ${message}\n\n⚠️ The content has been saved
|
|
96
|
+
content: [{ type: "text", text: `Error updating doc: ${message}\n\n⚠️ The content has been saved as draft "${draft}".\nYou can reuse it directly via the content_draft parameter instead of rewriting it.` }],
|
|
94
97
|
};
|
|
95
98
|
}
|
|
96
99
|
return { isError: true, content: [{ type: "text", text: `Error updating doc: ${message}` }] };
|
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.83";
|
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.83';
|