@aigne/doc-smith 0.8.15-beta → 0.8.15-beta.10
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/CHANGELOG.md +83 -0
- package/agents/clear/choose-contents.mjs +4 -4
- package/agents/clear/clear-auth-tokens.mjs +8 -8
- package/agents/clear/clear-deployment-config.mjs +2 -2
- package/agents/clear/clear-document-config.mjs +3 -3
- package/agents/clear/clear-document-structure.mjs +10 -10
- package/agents/clear/clear-generated-docs.mjs +103 -14
- package/agents/clear/clear-media-description.mjs +7 -7
- package/agents/evaluate/document-structure.yaml +3 -1
- package/agents/evaluate/document.yaml +3 -1
- package/agents/evaluate/index.yaml +1 -3
- package/agents/generate/check-diagram.mjs +1 -1
- package/agents/generate/check-need-generate-structure.mjs +2 -7
- package/agents/generate/draw-diagram.yaml +4 -0
- package/agents/generate/generate-structure.yaml +117 -65
- package/agents/generate/index.yaml +3 -3
- package/agents/generate/{merge-d2-diagram.yaml → merge-diagram.yaml} +7 -6
- package/agents/generate/update-document-structure.yaml +1 -1
- package/agents/generate/user-review-document-structure.mjs +1 -0
- package/agents/generate/utils/merge-document-structures.mjs +30 -0
- package/agents/init/check.mjs +3 -1
- package/agents/init/index.mjs +37 -7
- package/agents/media/load-media-description.mjs +12 -24
- package/agents/publish/publish-docs.mjs +3 -8
- package/agents/schema/document-execution-structure.yaml +1 -1
- package/agents/schema/document-structure-item.yaml +23 -0
- package/agents/schema/document-structure-refine-item.yaml +20 -0
- package/agents/schema/document-structure.yaml +1 -1
- package/agents/translate/index.yaml +1 -4
- package/agents/translate/record-translation-history.mjs +6 -2
- package/agents/translate/translate-multilingual.yaml +1 -1
- package/agents/update/batch-generate-document.yaml +1 -1
- package/agents/update/batch-update-document.yaml +1 -1
- package/agents/update/check-document.mjs +50 -13
- package/agents/update/check-generate-diagram.mjs +26 -0
- package/agents/update/generate-diagram.yaml +29 -0
- package/agents/update/generate-document.yaml +17 -30
- package/agents/update/handle-document-update.yaml +10 -1
- package/agents/update/save-and-translate-document.mjs +18 -47
- package/agents/update/update-document-detail.yaml +2 -1
- package/agents/update/update-single-document.yaml +1 -1
- package/agents/update/user-review-document.mjs +6 -5
- package/agents/utils/choose-docs.mjs +8 -2
- package/agents/utils/load-sources.mjs +132 -57
- package/agents/utils/{save-docs.mjs → post-generate.mjs} +2 -51
- package/agents/utils/save-doc-translation.mjs +27 -0
- package/agents/utils/{save-single-doc.mjs → save-doc.mjs} +17 -12
- package/agents/utils/save-sidebar.mjs +59 -0
- package/agents/utils/transform-detail-data-sources.mjs +45 -0
- package/aigne.yaml +16 -8
- package/package.json +2 -1
- package/prompts/common/document/content-rules-core.md +6 -6
- package/prompts/common/document/media-file-list-usage-rules.md +12 -0
- package/prompts/common/document/openapi-usage-rules.md +36 -0
- package/prompts/common/document/role-and-personality.md +1 -2
- package/prompts/common/document-structure/conflict-resolution-guidance.md +2 -2
- package/prompts/common/document-structure/document-structure-rules.md +8 -8
- package/prompts/common/document-structure/output-constraints.md +3 -3
- package/prompts/detail/custom/custom-components.md +38 -3
- package/prompts/detail/d2-diagram/rules.md +11 -14
- package/prompts/detail/d2-diagram/system-prompt.md +33 -21
- package/prompts/detail/d2-diagram/user-prompt.md +39 -0
- package/prompts/detail/generate/document-rules.md +3 -3
- package/prompts/detail/generate/system-prompt.md +2 -6
- package/prompts/detail/generate/user-prompt.md +20 -24
- package/prompts/detail/update/system-prompt.md +2 -6
- package/prompts/detail/update/user-prompt.md +7 -6
- package/prompts/evaluate/document.md +0 -4
- package/prompts/structure/check-document-structure.md +4 -4
- package/prompts/structure/generate/system-prompt.md +0 -31
- package/prompts/structure/generate/user-prompt.md +99 -26
- package/prompts/structure/review/structure-review-system.md +79 -0
- package/prompts/structure/update/system-prompt.md +1 -1
- package/prompts/structure/update/user-prompt.md +4 -4
- package/prompts/translate/code-block.md +13 -3
- package/prompts/translate/translate-document.md +1 -1
- package/types/document-structure-schema.mjs +3 -3
- package/utils/docs-finder-utils.mjs +48 -0
- package/utils/extract-api.mjs +32 -0
- package/utils/file-utils.mjs +124 -93
- package/utils/history-utils.mjs +20 -8
- package/utils/load-config.mjs +1 -1
- package/utils/markdown-checker.mjs +35 -1
- package/utils/openapi/index.mjs +24 -0
- package/utils/utils.mjs +67 -65
- package/agents/generate/document-structure-tools/generate-sub-structure.mjs +0 -131
- package/agents/generate/generate-structure-without-tools.yaml +0 -65
- package/agents/utils/transform-detail-datasources.mjs +0 -23
- package/prompts/common/document/media-handling-rules.md +0 -9
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { statSync } from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import imageSize from "image-size";
|
|
4
5
|
import {
|
|
5
6
|
buildSourcesContent,
|
|
6
|
-
calculateFileStats,
|
|
7
7
|
loadFilesFromPaths,
|
|
8
8
|
readFileContents,
|
|
9
9
|
getMimeType,
|
|
10
|
+
isRemoteFile,
|
|
11
|
+
calculateTokens,
|
|
10
12
|
} from "../../utils/file-utils.mjs";
|
|
11
13
|
import {
|
|
12
14
|
getCurrentGitHead,
|
|
@@ -18,28 +20,64 @@ import {
|
|
|
18
20
|
DEFAULT_EXCLUDE_PATTERNS,
|
|
19
21
|
DEFAULT_INCLUDE_PATTERNS,
|
|
20
22
|
} from "../../utils/constants/index.mjs";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
23
|
+
import { isOpenAPISpecFile } from "../../utils/openapi/index.mjs";
|
|
24
|
+
import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
|
|
25
|
+
|
|
26
|
+
export default async function loadSources(
|
|
27
|
+
{
|
|
28
|
+
sources = [],
|
|
29
|
+
sourcesPath = [],
|
|
30
|
+
includePatterns,
|
|
31
|
+
excludePatterns,
|
|
32
|
+
outputDir,
|
|
33
|
+
docsDir,
|
|
34
|
+
"doc-path": docPath,
|
|
35
|
+
boardId,
|
|
36
|
+
useDefaultPatterns = true,
|
|
37
|
+
lastGitHead,
|
|
38
|
+
reset = false,
|
|
39
|
+
media,
|
|
40
|
+
} = {},
|
|
41
|
+
options,
|
|
42
|
+
) {
|
|
36
43
|
let files = Array.isArray(sources) ? [...sources] : [];
|
|
37
44
|
const { minImageWidth } = media || { minImageWidth: 800 };
|
|
38
45
|
|
|
39
46
|
if (sourcesPath) {
|
|
40
|
-
const
|
|
47
|
+
const sourcesPathList = Array.isArray(sourcesPath) ? sourcesPath : [sourcesPath];
|
|
48
|
+
const pickSourcesPath = [];
|
|
49
|
+
const omitSourcesPath = [];
|
|
50
|
+
sourcesPathList.forEach((x) => {
|
|
51
|
+
if (typeof x !== "string" || !x) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (x.startsWith("!")) {
|
|
55
|
+
omitSourcesPath.push(x.substring(1));
|
|
56
|
+
} else {
|
|
57
|
+
pickSourcesPath.push(x);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const customExcludePatterns = omitSourcesPath
|
|
62
|
+
.map((x) => {
|
|
63
|
+
try {
|
|
64
|
+
const stats = statSync(x);
|
|
65
|
+
if (stats.isFile()) {
|
|
66
|
+
return x;
|
|
67
|
+
}
|
|
68
|
+
if (stats.isDirectory()) {
|
|
69
|
+
return `${x}/**`;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn(`Failed to stat path ${x}: ${error.message}`);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.filter(Boolean);
|
|
78
|
+
const allFiles = await loadFilesFromPaths(pickSourcesPath, {
|
|
41
79
|
includePatterns,
|
|
42
|
-
excludePatterns,
|
|
80
|
+
excludePatterns: [...new Set([...(excludePatterns || []), ...customExcludePatterns])],
|
|
43
81
|
useDefaultPatterns,
|
|
44
82
|
defaultIncludePatterns: DEFAULT_INCLUDE_PATTERNS,
|
|
45
83
|
defaultExcludePatterns: DEFAULT_EXCLUDE_PATTERNS,
|
|
@@ -50,9 +88,6 @@ export default async function loadSources({
|
|
|
50
88
|
|
|
51
89
|
files = [...new Set(files)];
|
|
52
90
|
|
|
53
|
-
// all files path
|
|
54
|
-
const allFilesPaths = files.map((file) => `- ${toRelativePath(file)}`).join("\n");
|
|
55
|
-
|
|
56
91
|
// Define media file extensions
|
|
57
92
|
const mediaExtensions = [
|
|
58
93
|
".jpg",
|
|
@@ -110,7 +145,7 @@ export default async function loadSources({
|
|
|
110
145
|
files.map(async (file) => {
|
|
111
146
|
const ext = path.extname(file).toLowerCase();
|
|
112
147
|
|
|
113
|
-
if (mediaExtensions.includes(ext)) {
|
|
148
|
+
if (mediaExtensions.includes(ext) && !isRemoteFile(file)) {
|
|
114
149
|
// This is a media file
|
|
115
150
|
const relativePath = path.relative(docsDir, file);
|
|
116
151
|
const fileName = path.basename(file);
|
|
@@ -161,44 +196,49 @@ export default async function loadSources({
|
|
|
161
196
|
}
|
|
162
197
|
|
|
163
198
|
// Read all source files using the utility function
|
|
164
|
-
|
|
199
|
+
let sourceFiles = (await readFileContents(sourceFilesPaths, process.cwd())).map((i) => ({
|
|
200
|
+
...i,
|
|
201
|
+
tokens: calculateTokens(`\n${i.sourceId}\n${i.content}`),
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
// filter OpenAPI doc should after check isLargeContext
|
|
205
|
+
sourceFiles = sourceFiles.filter((file) => {
|
|
206
|
+
if (options?.context?.userContext.openAPISpec) return true;
|
|
207
|
+
|
|
208
|
+
const isOpenAPI = isOpenAPISpecFile(file.content);
|
|
209
|
+
if (isOpenAPI && options?.context?.userContext) {
|
|
210
|
+
options.context.userContext.openAPISpec = file;
|
|
211
|
+
}
|
|
212
|
+
return !isOpenAPI;
|
|
213
|
+
});
|
|
165
214
|
|
|
166
|
-
|
|
167
|
-
const
|
|
215
|
+
const totalTokens = sourceFiles.reduce((sum, file) => sum + file.tokens, 0);
|
|
216
|
+
const totalLines = sourceFiles.reduce(
|
|
217
|
+
(sum, file) => sum + file.content.split("\n").filter(Boolean).length,
|
|
218
|
+
0,
|
|
219
|
+
);
|
|
168
220
|
|
|
169
|
-
|
|
170
|
-
|
|
221
|
+
const dataSources = splitSourcesToChunks(sourceFiles, INTELLIGENT_SUGGESTION_TOKEN_THRESHOLD).map(
|
|
222
|
+
(i) => ({ dataSourceChunk: buildSourcesContent(i) }),
|
|
223
|
+
);
|
|
171
224
|
|
|
172
|
-
|
|
173
|
-
const allSources = buildSourcesContent(sourceFiles, isLargeContext);
|
|
225
|
+
const remoteFileList = [];
|
|
174
226
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const documentStructurePath = path.join(outputDir, "structure-plan.json");
|
|
179
|
-
try {
|
|
180
|
-
const documentExecutionStructure = await readFile(documentStructurePath, "utf8");
|
|
181
|
-
if (documentExecutionStructure?.trim()) {
|
|
182
|
-
try {
|
|
183
|
-
// Validate that the content looks like JSON before parsing
|
|
184
|
-
const trimmedContent = documentExecutionStructure.trim();
|
|
185
|
-
if (trimmedContent.startsWith("{") || trimmedContent.startsWith("[")) {
|
|
186
|
-
originalDocumentStructure = JSON.parse(documentExecutionStructure);
|
|
187
|
-
} else {
|
|
188
|
-
console.warn(`structure-plan.json contains non-JSON content, skipping parse`);
|
|
189
|
-
}
|
|
190
|
-
} catch (err) {
|
|
191
|
-
console.error(`Failed to parse structure-plan.json: ${err.message}`);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
} catch (err) {
|
|
195
|
-
if (err.code !== "ENOENT") {
|
|
196
|
-
console.warn(`Error reading structure-plan.json: ${err.message}`);
|
|
197
|
-
}
|
|
198
|
-
// The file does not exist or is not readable, originalDocumentStructure remains undefined
|
|
227
|
+
sourceFiles.forEach((file) => {
|
|
228
|
+
if (isRemoteFile(file.sourceId)) {
|
|
229
|
+
remoteFileList.push(file);
|
|
199
230
|
}
|
|
231
|
+
});
|
|
232
|
+
if (options?.context?.userContext) {
|
|
233
|
+
options.context.userContext.remoteFileList = remoteFileList;
|
|
200
234
|
}
|
|
201
235
|
|
|
236
|
+
// all files path
|
|
237
|
+
const allFilesPaths = sourceFiles.map((x) => `- ${toRelativePath(x.sourceId)}`).join("\n");
|
|
238
|
+
|
|
239
|
+
// Get the last documentation structure
|
|
240
|
+
const originalDocumentStructure = await loadDocumentStructure(outputDir);
|
|
241
|
+
|
|
202
242
|
// Get the last output result of the specified path
|
|
203
243
|
let content;
|
|
204
244
|
if (docPath && !reset && docsDir) {
|
|
@@ -250,7 +290,7 @@ export default async function loadSources({
|
|
|
250
290
|
}
|
|
251
291
|
|
|
252
292
|
return {
|
|
253
|
-
|
|
293
|
+
dataSources,
|
|
254
294
|
content,
|
|
255
295
|
originalDocumentStructure,
|
|
256
296
|
files,
|
|
@@ -258,7 +298,6 @@ export default async function loadSources({
|
|
|
258
298
|
totalTokens,
|
|
259
299
|
totalLines,
|
|
260
300
|
mediaFiles,
|
|
261
|
-
isLargeContext,
|
|
262
301
|
allFilesPaths,
|
|
263
302
|
};
|
|
264
303
|
}
|
|
@@ -306,8 +345,14 @@ loadSources.input_schema = {
|
|
|
306
345
|
loadSources.output_schema = {
|
|
307
346
|
type: "object",
|
|
308
347
|
properties: {
|
|
309
|
-
|
|
310
|
-
type: "
|
|
348
|
+
dataSources: {
|
|
349
|
+
type: "array",
|
|
350
|
+
items: {
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: {
|
|
353
|
+
dataSourceChunk: { type: "string" },
|
|
354
|
+
},
|
|
355
|
+
},
|
|
311
356
|
},
|
|
312
357
|
files: {
|
|
313
358
|
type: "array",
|
|
@@ -338,3 +383,33 @@ loadSources.output_schema = {
|
|
|
338
383
|
};
|
|
339
384
|
|
|
340
385
|
loadSources.task_render_mode = "hide";
|
|
386
|
+
|
|
387
|
+
function splitSourcesToChunks(sources, maxTokens) {
|
|
388
|
+
const chunks = [];
|
|
389
|
+
|
|
390
|
+
let currentChunk = [];
|
|
391
|
+
let currentTokens = 0;
|
|
392
|
+
|
|
393
|
+
for (const source of sources) {
|
|
394
|
+
const sourceTokens = source.tokens;
|
|
395
|
+
|
|
396
|
+
if (currentTokens + sourceTokens > maxTokens) {
|
|
397
|
+
// Start a new chunk
|
|
398
|
+
if (currentChunk.length > 0) {
|
|
399
|
+
chunks.push(currentChunk);
|
|
400
|
+
}
|
|
401
|
+
currentChunk = [source];
|
|
402
|
+
currentTokens = sourceTokens;
|
|
403
|
+
} else {
|
|
404
|
+
// Add to current chunk
|
|
405
|
+
currentChunk.push(source);
|
|
406
|
+
currentTokens += sourceTokens;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (currentChunk.length > 0) {
|
|
411
|
+
chunks.push(currentChunk);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return chunks;
|
|
415
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir, unlink
|
|
1
|
+
import { readdir, unlink } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
|
|
4
4
|
import { getCurrentGitHead, saveGitHeadToConfig } from "../../utils/utils.mjs";
|
|
@@ -10,7 +10,7 @@ import { getCurrentGitHead, saveGitHeadToConfig } from "../../utils/utils.mjs";
|
|
|
10
10
|
* @param {Array<string>} [params.translateLanguages] - Translation languages
|
|
11
11
|
* @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
|
|
12
12
|
*/
|
|
13
|
-
export default async function
|
|
13
|
+
export default async function postGenerate({
|
|
14
14
|
documentExecutionStructure: documentStructure,
|
|
15
15
|
docsDir,
|
|
16
16
|
translateLanguages = [],
|
|
@@ -26,15 +26,6 @@ export default async function saveDocs({
|
|
|
26
26
|
console.warn("Failed to save git HEAD:", err.message);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// Generate _sidebar.md
|
|
30
|
-
try {
|
|
31
|
-
const sidebar = generateSidebar(documentStructure);
|
|
32
|
-
const sidebarPath = join(docsDir, "_sidebar.md");
|
|
33
|
-
await writeFile(sidebarPath, sidebar, "utf8");
|
|
34
|
-
} catch (err) {
|
|
35
|
-
console.error("Failed to save _sidebar.md:", err.message);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
29
|
// Clean up invalid .md files that are no longer in the documentation structure
|
|
39
30
|
try {
|
|
40
31
|
await cleanupInvalidFiles(documentStructure, docsDir, translateLanguages, locale);
|
|
@@ -140,43 +131,3 @@ async function cleanupInvalidFiles(documentStructure, docsDir, translateLanguage
|
|
|
140
131
|
}
|
|
141
132
|
|
|
142
133
|
// Generate sidebar content, support nested structure, and the order is consistent with documentStructure
|
|
143
|
-
function generateSidebar(documentStructure) {
|
|
144
|
-
// Build tree structure
|
|
145
|
-
const root = {};
|
|
146
|
-
for (const { path, title, parentId } of documentStructure) {
|
|
147
|
-
const relPath = path.replace(/^\//, "");
|
|
148
|
-
const segments = relPath.split("/");
|
|
149
|
-
let node = root;
|
|
150
|
-
for (let i = 0; i < segments.length; i++) {
|
|
151
|
-
const seg = segments[i];
|
|
152
|
-
if (!node[seg])
|
|
153
|
-
node[seg] = {
|
|
154
|
-
__children: {},
|
|
155
|
-
__title: null,
|
|
156
|
-
__fullPath: segments.slice(0, i + 1).join("/"),
|
|
157
|
-
__parentId: parentId,
|
|
158
|
-
};
|
|
159
|
-
if (i === segments.length - 1) node[seg].__title = title;
|
|
160
|
-
node = node[seg].__children;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Recursively generate sidebar text, the link path is the flattened file name
|
|
164
|
-
function walk(node, parentSegments = [], indent = "") {
|
|
165
|
-
let out = "";
|
|
166
|
-
for (const key of Object.keys(node)) {
|
|
167
|
-
const item = node[key];
|
|
168
|
-
const fullSegments = [...parentSegments, key];
|
|
169
|
-
const flatFile = `${fullSegments.join("-")}.md`;
|
|
170
|
-
if (item.__title) {
|
|
171
|
-
const realIndent = item.__parentId === null ? "" : indent;
|
|
172
|
-
out += `${realIndent}* [${item.__title}](/${flatFile})\n`;
|
|
173
|
-
}
|
|
174
|
-
const children = item.__children;
|
|
175
|
-
if (Object.keys(children).length > 0) {
|
|
176
|
-
out += walk(children, fullSegments, `${indent} `);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return out;
|
|
180
|
-
}
|
|
181
|
-
return walk(root).replace(/\n+$/, "");
|
|
182
|
-
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { saveDocTranslation as _saveDocTranslation } from "../../utils/utils.mjs";
|
|
2
|
+
|
|
3
|
+
export default async function saveDocTranslation({
|
|
4
|
+
path,
|
|
5
|
+
docsDir,
|
|
6
|
+
translation,
|
|
7
|
+
language,
|
|
8
|
+
labels,
|
|
9
|
+
isShowMessage = false,
|
|
10
|
+
}) {
|
|
11
|
+
await _saveDocTranslation({
|
|
12
|
+
path,
|
|
13
|
+
docsDir,
|
|
14
|
+
language,
|
|
15
|
+
translation,
|
|
16
|
+
labels,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (isShowMessage) {
|
|
20
|
+
const message = `✅ Translation completed successfully.`;
|
|
21
|
+
return { message };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
saveDocTranslation.task_render_mode = "hide";
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { saveDoc as _saveDoc } from "../../utils/utils.mjs";
|
|
3
3
|
|
|
4
|
-
export default async function
|
|
4
|
+
export default async function saveDoc({
|
|
5
5
|
path,
|
|
6
6
|
content,
|
|
7
7
|
docsDir,
|
|
8
|
-
translates,
|
|
9
8
|
labels,
|
|
10
9
|
locale,
|
|
11
|
-
|
|
10
|
+
feedback,
|
|
12
11
|
isShowMessage = false,
|
|
12
|
+
...rest
|
|
13
13
|
}) {
|
|
14
|
-
|
|
14
|
+
await _saveDoc({
|
|
15
15
|
path,
|
|
16
16
|
content,
|
|
17
17
|
docsDir,
|
|
18
|
-
translates,
|
|
19
18
|
labels,
|
|
20
19
|
locale,
|
|
21
|
-
isTranslate,
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
if (isShowMessage) {
|
|
@@ -29,13 +27,20 @@ export default async function saveSingleDoc({
|
|
|
29
27
|
console.warn("Failed to shutdown mermaid worker pool:", error.message);
|
|
30
28
|
}
|
|
31
29
|
|
|
32
|
-
const message =
|
|
33
|
-
? `✅ Translation completed successfully`
|
|
34
|
-
: `✅ Document updated successfully`;
|
|
30
|
+
const message = `✅ Document updated successfully.`;
|
|
35
31
|
return { message };
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
path,
|
|
36
|
+
content,
|
|
37
|
+
docsDir,
|
|
38
|
+
labels,
|
|
39
|
+
locale,
|
|
40
|
+
feedback,
|
|
41
|
+
isShowMessage,
|
|
42
|
+
...rest,
|
|
43
|
+
};
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
|
|
46
|
+
saveDoc.task_render_mode = "hide";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
|
|
4
|
+
export default async function saveSidebar({ documentStructure, docsDir }) {
|
|
5
|
+
// Generate _sidebar.md
|
|
6
|
+
try {
|
|
7
|
+
const sidebar = generateSidebar(documentStructure);
|
|
8
|
+
const sidebarPath = join(docsDir, "_sidebar.md");
|
|
9
|
+
|
|
10
|
+
await fs.ensureDir(docsDir);
|
|
11
|
+
await fs.writeFile(sidebarPath, sidebar, "utf8");
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error("Failed to save _sidebar.md:", err.message);
|
|
14
|
+
}
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Recursively generate sidebar text, the link path is the flattened file name
|
|
19
|
+
function walk(node, parentSegments = [], indent = "") {
|
|
20
|
+
let out = "";
|
|
21
|
+
for (const key of Object.keys(node)) {
|
|
22
|
+
const item = node[key];
|
|
23
|
+
const fullSegments = [...parentSegments, key];
|
|
24
|
+
const flatFile = `${fullSegments.join("-")}.md`;
|
|
25
|
+
if (item.__title) {
|
|
26
|
+
const realIndent = item.__parentId === null ? "" : indent;
|
|
27
|
+
out += `${realIndent}* [${item.__title}](/${flatFile})\n`;
|
|
28
|
+
}
|
|
29
|
+
const children = item.__children;
|
|
30
|
+
if (Object.keys(children).length > 0) {
|
|
31
|
+
out += walk(children, fullSegments, `${indent} `);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function generateSidebar(documentStructure) {
|
|
38
|
+
// Build tree structure
|
|
39
|
+
const root = {};
|
|
40
|
+
for (const { path, title, parentId } of documentStructure) {
|
|
41
|
+
const relPath = path.replace(/^\//, "");
|
|
42
|
+
const segments = relPath.split("/");
|
|
43
|
+
let node = root;
|
|
44
|
+
for (let i = 0; i < segments.length; i++) {
|
|
45
|
+
const seg = segments[i];
|
|
46
|
+
if (!node[seg])
|
|
47
|
+
node[seg] = {
|
|
48
|
+
__children: {},
|
|
49
|
+
__title: null,
|
|
50
|
+
__fullPath: segments.slice(0, i + 1).join("/"),
|
|
51
|
+
__parentId: parentId,
|
|
52
|
+
};
|
|
53
|
+
if (i === segments.length - 1) node[seg].__title = title;
|
|
54
|
+
node = node[seg].__children;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return walk(root).replace(/\n+$/, "");
|
|
59
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { isRemoteFile } from "../../utils/file-utils.mjs";
|
|
3
|
+
import { normalizePath, toRelativePath } from "../../utils/utils.mjs";
|
|
4
|
+
|
|
5
|
+
export default function transformDetailDataSource({ sourceIds }, options = {}) {
|
|
6
|
+
// Read file content for each sourceId, ignoring failures
|
|
7
|
+
let openAPISpec;
|
|
8
|
+
const remoteFileList = options?.context?.userContext?.remoteFileList || [];
|
|
9
|
+
const contents = (sourceIds || [])
|
|
10
|
+
.filter((id) => {
|
|
11
|
+
const openApiSourceId = options?.context?.userContext?.openAPISpec?.sourceId;
|
|
12
|
+
if (openApiSourceId !== undefined && openApiSourceId === id) {
|
|
13
|
+
openAPISpec = options.context.userContext.openAPISpec;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return true;
|
|
17
|
+
})
|
|
18
|
+
.map((id) => {
|
|
19
|
+
try {
|
|
20
|
+
if (isRemoteFile(id)) {
|
|
21
|
+
const findFile = remoteFileList.find((f) => f.sourceId === id);
|
|
22
|
+
if (findFile) {
|
|
23
|
+
return `// sourceId: ${id}\n${findFile.content}\n`;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const normalizedId = normalizePath(id);
|
|
29
|
+
const content = fs.readFileSync(normalizedId, "utf8");
|
|
30
|
+
const relativeId = toRelativePath(id);
|
|
31
|
+
return `// sourceId: ${relativeId}\n${content}\n`;
|
|
32
|
+
} catch {
|
|
33
|
+
// Ignore files that cannot be read
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
detailDataSource: contents.join(""),
|
|
41
|
+
openAPISpec,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
transformDetailDataSource.task_render_mode = "hide";
|
package/aigne.yaml
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env aigne
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
model:
|
|
4
|
+
model: aignehub/gemini-2.5-pro # reasoning_effort 128-32768
|
|
5
|
+
# https://github.com/AIGNE-io/aigne-framework/blob/main/models/gemini/src/gemini-chat-model.ts#L115
|
|
6
|
+
reasoning_effort: 502
|
|
6
7
|
# name: gemini-2.5-flash
|
|
7
8
|
temperature: 0.8
|
|
8
9
|
agents:
|
|
@@ -11,14 +12,13 @@ agents:
|
|
|
11
12
|
|
|
12
13
|
# Documentation Structure Generation
|
|
13
14
|
- ./agents/generate/generate-structure.yaml
|
|
14
|
-
- ./agents/generate/generate-structure-without-tools.yaml
|
|
15
15
|
- ./agents/generate/update-document-structure.yaml
|
|
16
16
|
- ./agents/generate/check-need-generate-structure.mjs
|
|
17
17
|
- ./agents/generate/refine-document-structure.yaml
|
|
18
18
|
- ./agents/generate/check-document-structure.yaml
|
|
19
19
|
- ./agents/generate/user-review-document-structure.mjs
|
|
20
20
|
- ./agents/generate/index.yaml
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
# Documentation Structure Tools
|
|
23
23
|
- ./agents/generate/document-structure-tools/add-document.mjs
|
|
24
24
|
- ./agents/generate/document-structure-tools/delete-document.mjs
|
|
@@ -47,6 +47,7 @@ agents:
|
|
|
47
47
|
|
|
48
48
|
# Publishing
|
|
49
49
|
- ./agents/publish/publish-docs.mjs
|
|
50
|
+
- ./agents/publish/translate-meta.mjs
|
|
50
51
|
- ./agents/publish/index.yaml
|
|
51
52
|
|
|
52
53
|
# Media
|
|
@@ -65,9 +66,11 @@ agents:
|
|
|
65
66
|
|
|
66
67
|
# Utilities
|
|
67
68
|
- ./agents/utils/load-sources.mjs
|
|
68
|
-
- ./agents/utils/
|
|
69
|
-
- ./agents/utils/
|
|
70
|
-
- ./agents/utils/
|
|
69
|
+
- ./agents/utils/post-generate.mjs
|
|
70
|
+
- ./agents/utils/save-sidebar.mjs
|
|
71
|
+
- ./agents/utils/transform-detail-data-sources.mjs
|
|
72
|
+
- ./agents/utils/save-doc.mjs
|
|
73
|
+
- ./agents/utils/save-doc-translation.mjs
|
|
71
74
|
- ./agents/utils/save-output.mjs
|
|
72
75
|
- ./agents/utils/format-document-structure.mjs
|
|
73
76
|
- ./agents/utils/find-item-by-path.mjs
|
|
@@ -92,6 +95,11 @@ agents:
|
|
|
92
95
|
- ./agents/evaluate/document-structure.yaml
|
|
93
96
|
- ./agents/evaluate/document.yaml
|
|
94
97
|
- ./agents/evaluate/code-snippet.mjs
|
|
98
|
+
|
|
99
|
+
# Diagram
|
|
100
|
+
- ./agents/generate/merge-diagram.yaml
|
|
101
|
+
- ./agents/update/generate-diagram.yaml
|
|
102
|
+
- ./agents/update/check-generate-diagram.mjs
|
|
95
103
|
cli:
|
|
96
104
|
chat: ./agents/chat/index.yaml
|
|
97
105
|
agents:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/doc-smith",
|
|
3
|
-
"version": "0.8.15-beta",
|
|
3
|
+
"version": "0.8.15-beta.10",
|
|
4
4
|
"description": "AI-driven documentation generation tool built on the AIGNE Framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"remark-lint": "^10.0.1",
|
|
57
57
|
"remark-parse": "^11.0.0",
|
|
58
58
|
"terminal-link": "^4.0.0",
|
|
59
|
+
"typescript": "^5.9.3",
|
|
59
60
|
"ufo": "^1.6.1",
|
|
60
61
|
"unified": "^11.0.5",
|
|
61
62
|
"unist-util-visit": "^5.0.0",
|
|
@@ -2,12 +2,12 @@ Target Audience: {{targetAudience}}
|
|
|
2
2
|
|
|
3
3
|
Content Generation Rules:
|
|
4
4
|
|
|
5
|
-
- Use only information from
|
|
5
|
+
- Use only information from `<detail_data_source>`, never fabricate or supplement content not present in the sources
|
|
6
6
|
- Combine the current {{nodeName}} title and description to create a well-structured content plan that is rich, organized, and engaging
|
|
7
7
|
- Content style must match the target audience
|
|
8
|
-
- Clearly differentiate content from other {{nodeName}} items in the
|
|
8
|
+
- Clearly differentiate content from other {{nodeName}} items in the `<document_structure>` to avoid duplication and highlight this {{nodeName}}'s unique value
|
|
9
9
|
{% if enforceInfoCompleteness %}
|
|
10
|
-
- If
|
|
10
|
+
- If `<detail_data_source>` lack sufficient information, return an error message requesting users to provide additional content. Ensure page content is sufficiently rich, don't hesitate to ask users for supplementary information
|
|
11
11
|
- Display only valuable, engaging information. If information is insufficient, prompt users to provide more details
|
|
12
12
|
{% endif %}
|
|
13
13
|
- Output complete information including all content planned for the {{nodeName}}
|
|
@@ -15,6 +15,6 @@ Content Generation Rules:
|
|
|
15
15
|
- Maintain a strict, sequential heading hierarchy; no skipping (e.g., no jump from level-1 to level-3).
|
|
16
16
|
- Format markdown output with proper line breaks and spacing for easy reading
|
|
17
17
|
- For list data with many items, prioritize using markdown table for cleaner, more readable presentation
|
|
18
|
-
- Do not mention
|
|
19
|
-
- Do not include file paths from
|
|
20
|
-
- Avoid phrases like 'current {{nodeName}}'
|
|
18
|
+
- Do not mention `<detail_data_source>` in output, your content is for user consumption, and users are unaware of detailDataSource
|
|
19
|
+
- Do not include file paths from `<data_sources>` in output as they are meaningless to users
|
|
20
|
+
- Avoid phrases like 'current {{nodeName}}'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<media_file_list_usage_rules>
|
|
2
|
+
|
|
3
|
+
**Usage Workflow**
|
|
4
|
+
1. Read the `<media_file_list>` data and take note of each file's `path` and `description` as references.
|
|
5
|
+
2. Combine those descriptions with the current document's content to decide which images should be used and where they should be inserted.
|
|
6
|
+
3. Confirm that every inserted image path comes from `<media_file_list>`. If a path is missing from that list, replace it with one that is included.
|
|
7
|
+
|
|
8
|
+
**Usage Requirements**
|
|
9
|
+
- Insert images with Markdown syntax: ``.
|
|
10
|
+
- Never invent, reinterpret, fabricate, normalize, or rewrite any media file path under any circumstances.
|
|
11
|
+
|
|
12
|
+
</media_file_list_usage_rules>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{% if openAPISpec %}
|
|
2
|
+
<openapi_usage_rules>
|
|
3
|
+
|
|
4
|
+
**Goal:** Use the provided OpenAPI (Swagger) specification, align it with the current page objective, and leverage it to refine this document.
|
|
5
|
+
|
|
6
|
+
**OpenAPI File Content:**
|
|
7
|
+
<openapi_doc>
|
|
8
|
+
|
|
9
|
+
{{ openAPISpec }}
|
|
10
|
+
|
|
11
|
+
</openapi_doc>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
### **Documentation Requirements and Constraints**
|
|
16
|
+
|
|
17
|
+
1. **Extract the core content:**
|
|
18
|
+
* Organize the document by functional modules.
|
|
19
|
+
* For each path item, include the following elements:
|
|
20
|
+
* HTTP method and path.
|
|
21
|
+
* Concise summary.
|
|
22
|
+
* Detailed description.
|
|
23
|
+
* Request parameters: name, location (`in`), type, required flag, description.
|
|
24
|
+
* Request body: describe its structure when present.
|
|
25
|
+
* Responses: at least the key status codes (e.g., 200, 201, 400, 500) and their schemas.
|
|
26
|
+
|
|
27
|
+
2. **Mandatory API description constraints (deduplication rule):**
|
|
28
|
+
* **Ensure that throughout the document (including preface, overview, etc.), any introduction to the project APIs appears only within this OpenAPI-generated "API reference" section.**
|
|
29
|
+
* **Never** repeat or expand the interface list elsewhere in the document (for example, "Quick Start" or "Architecture Overview" sections).
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
**Expected output format:** A concise, clear, and easy-to-scan Markdown document.
|
|
34
|
+
|
|
35
|
+
</openapi_usage_rules>
|
|
36
|
+
{% endif %}
|