@aigne/doc-smith 0.8.15-beta.1 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/agents/clear/choose-contents.mjs +4 -4
  3. package/agents/clear/clear-auth-tokens.mjs +8 -8
  4. package/agents/clear/clear-deployment-config.mjs +2 -2
  5. package/agents/clear/clear-document-config.mjs +3 -3
  6. package/agents/clear/clear-document-structure.mjs +10 -10
  7. package/agents/clear/clear-generated-docs.mjs +103 -14
  8. package/agents/clear/clear-media-description.mjs +7 -7
  9. package/agents/evaluate/document-structure.yaml +3 -1
  10. package/agents/evaluate/document.yaml +3 -1
  11. package/agents/evaluate/index.yaml +1 -3
  12. package/agents/generate/check-diagram.mjs +1 -1
  13. package/agents/generate/check-need-generate-structure.mjs +2 -7
  14. package/agents/generate/draw-diagram.yaml +4 -0
  15. package/agents/generate/generate-structure.yaml +117 -65
  16. package/agents/generate/index.yaml +3 -3
  17. package/agents/generate/{merge-d2-diagram.yaml → merge-diagram.yaml} +7 -6
  18. package/agents/generate/update-document-structure.yaml +1 -1
  19. package/agents/generate/user-review-document-structure.mjs +1 -0
  20. package/agents/generate/utils/merge-document-structures.mjs +30 -0
  21. package/agents/init/check.mjs +3 -1
  22. package/agents/init/index.mjs +37 -7
  23. package/agents/media/load-media-description.mjs +12 -24
  24. package/agents/publish/publish-docs.mjs +3 -8
  25. package/agents/schema/document-execution-structure.yaml +1 -1
  26. package/agents/schema/document-structure-item.yaml +23 -0
  27. package/agents/schema/document-structure-refine-item.yaml +20 -0
  28. package/agents/schema/document-structure.yaml +1 -1
  29. package/agents/translate/index.yaml +1 -4
  30. package/agents/translate/record-translation-history.mjs +6 -2
  31. package/agents/translate/translate-multilingual.yaml +1 -1
  32. package/agents/update/batch-generate-document.yaml +1 -1
  33. package/agents/update/batch-update-document.yaml +1 -1
  34. package/agents/update/check-document.mjs +35 -13
  35. package/agents/update/check-generate-diagram.mjs +26 -0
  36. package/agents/update/generate-diagram.yaml +29 -0
  37. package/agents/update/generate-document.yaml +17 -30
  38. package/agents/update/handle-document-update.yaml +10 -1
  39. package/agents/update/save-and-translate-document.mjs +18 -47
  40. package/agents/update/update-document-detail.yaml +2 -1
  41. package/agents/update/update-single-document.yaml +1 -1
  42. package/agents/update/user-review-document.mjs +6 -5
  43. package/agents/utils/choose-docs.mjs +2 -1
  44. package/agents/utils/load-sources.mjs +62 -45
  45. package/agents/utils/{save-docs.mjs → post-generate.mjs} +2 -51
  46. package/agents/utils/save-doc-translation.mjs +27 -0
  47. package/agents/utils/{save-single-doc.mjs → save-doc.mjs} +17 -12
  48. package/agents/utils/save-sidebar.mjs +59 -0
  49. package/agents/utils/{transform-detail-datasources.mjs → transform-detail-data-sources.mjs} +7 -7
  50. package/aigne.yaml +16 -8
  51. package/package.json +2 -1
  52. package/prompts/common/document/content-rules-core.md +6 -6
  53. package/prompts/common/document/media-file-list-usage-rules.md +12 -0
  54. package/prompts/common/document/openapi-usage-rules.md +36 -0
  55. package/prompts/common/document/role-and-personality.md +1 -2
  56. package/prompts/common/document-structure/conflict-resolution-guidance.md +2 -2
  57. package/prompts/common/document-structure/document-structure-rules.md +8 -8
  58. package/prompts/common/document-structure/output-constraints.md +3 -3
  59. package/prompts/detail/custom/custom-components.md +38 -3
  60. package/prompts/detail/d2-diagram/rules.md +11 -14
  61. package/prompts/detail/d2-diagram/system-prompt.md +0 -14
  62. package/prompts/detail/d2-diagram/user-prompt.md +39 -0
  63. package/prompts/detail/generate/document-rules.md +3 -3
  64. package/prompts/detail/generate/system-prompt.md +2 -6
  65. package/prompts/detail/generate/user-prompt.md +20 -61
  66. package/prompts/detail/update/system-prompt.md +2 -6
  67. package/prompts/detail/update/user-prompt.md +7 -6
  68. package/prompts/evaluate/document.md +0 -4
  69. package/prompts/structure/check-document-structure.md +4 -4
  70. package/prompts/structure/generate/system-prompt.md +0 -31
  71. package/prompts/structure/generate/user-prompt.md +68 -29
  72. package/prompts/structure/review/structure-review-system.md +79 -0
  73. package/prompts/structure/update/system-prompt.md +1 -1
  74. package/prompts/structure/update/user-prompt.md +4 -4
  75. package/prompts/translate/code-block.md +13 -3
  76. package/prompts/translate/translate-document.md +1 -1
  77. package/types/document-structure-schema.mjs +3 -3
  78. package/utils/docs-finder-utils.mjs +48 -0
  79. package/utils/extract-api.mjs +32 -0
  80. package/utils/file-utils.mjs +56 -101
  81. package/utils/history-utils.mjs +20 -8
  82. package/utils/load-config.mjs +1 -1
  83. package/utils/markdown-checker.mjs +35 -1
  84. package/utils/utils.mjs +67 -65
  85. package/agents/generate/document-structure-tools/generate-sub-structure.mjs +0 -131
  86. package/agents/generate/generate-structure-without-tools.yaml +0 -65
  87. package/prompts/common/document/media-handling-rules.md +0 -9
@@ -2,7 +2,10 @@ import { access, readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { TeamAgent } from "@aigne/core";
5
+ import fs from "fs-extra";
6
+ import pMap from "p-map";
5
7
 
8
+ import { getFileName } from "../../utils/utils.mjs";
6
9
  import checkDetailResult from "../utils/check-detail-result.mjs";
7
10
 
8
11
  // Get current script directory
@@ -18,13 +21,13 @@ export default async function checkDocument(
18
21
  modifiedFiles,
19
22
  forceRegenerate,
20
23
  locale,
24
+ translates,
21
25
  ...rest
22
26
  },
23
27
  options,
24
28
  ) {
25
29
  // Check if the detail file already exists
26
- const flatName = path.replace(/^\//, "").replace(/\//g, "-");
27
- const fileFullName = locale === "en" ? `${flatName}.md` : `${flatName}.${locale}.md`;
30
+ const fileFullName = getFileName(path, locale);
28
31
  const filePath = join(docsDir, fileFullName);
29
32
  let detailGenerated = true;
30
33
  let fileContent = null;
@@ -84,24 +87,42 @@ export default async function checkDocument(
84
87
  contentValidationFailed = true;
85
88
  }
86
89
  }
90
+ const languages = translates.map((x) => x.language);
91
+ const lackLanguages = new Set(languages);
92
+ const skills = [];
87
93
 
88
94
  // If file exists, sourceIds haven't changed, source files haven't changed, and content validation passes, no need to regenerate
89
95
  if (detailGenerated && !sourceIdsChanged && !contentValidationFailed && !forceRegenerate) {
90
- return {
91
- path,
92
- docsDir,
93
- ...rest,
94
- detailGenerated: true,
95
- };
96
+ await pMap(
97
+ languages,
98
+ async (x) => {
99
+ const languageFileName = getFileName(path, x);
100
+ const languageFilePath = join(docsDir, languageFileName);
101
+ if (await fs.exists(languageFilePath)) {
102
+ lackLanguages.delete(x);
103
+ }
104
+ },
105
+ { concurrency: 10 },
106
+ );
107
+ if (lackLanguages.size === 0) {
108
+ return {
109
+ path,
110
+ docsDir,
111
+ ...rest,
112
+ detailGenerated: true,
113
+ };
114
+ }
115
+ // translations during generation don't need feedback, content is satisfactory
116
+ rest.content = fileContent;
117
+ } else {
118
+ skills.push(options.context.agents["handleDocumentUpdate"]);
96
119
  }
97
120
 
121
+ skills.push(options.context.agents["translateMultilingual"]);
122
+
98
123
  const teamAgent = TeamAgent.from({
99
124
  name: "generateDocument",
100
- skills: [
101
- options.context.agents["handleDocumentUpdate"],
102
- options.context.agents["translateMultilingual"],
103
- options.context.agents["saveSingleDoc"],
104
- ],
125
+ skills,
105
126
  });
106
127
  let openAPISpec = null;
107
128
 
@@ -119,6 +140,7 @@ export default async function checkDocument(
119
140
 
120
141
  const result = await options.context.invoke(teamAgent, {
121
142
  ...rest,
143
+ translates: translates.filter((x) => lackLanguages.has(x.language)),
122
144
  locale,
123
145
  docsDir,
124
146
  path,
@@ -0,0 +1,26 @@
1
+ const placeholder = "DIAGRAM_PLACEHOLDER";
2
+
3
+ export default async function checkGenerateDiagram(
4
+ { needDiagram, documentContent, locale },
5
+ options,
6
+ ) {
7
+ if (!needDiagram) {
8
+ return {};
9
+ }
10
+
11
+ const generateAgent = options.context?.agents?.["generateDiagram"];
12
+ let content = documentContent;
13
+
14
+ try {
15
+ const { diagramSourceCode } = await options.context.invoke(generateAgent, {
16
+ documentContent,
17
+ locale,
18
+ });
19
+ content = content.replace(placeholder, diagramSourceCode);
20
+ } catch (error) {
21
+ // FIXME: @zhanghan should regenerate document without diagram
22
+ content = content.replace(placeholder, "");
23
+ console.log(`⚠️ Skip generate any diagram: ${error.message}`);
24
+ }
25
+ return { content };
26
+ }
@@ -0,0 +1,29 @@
1
+ type: team
2
+ task_render_mode: collapse
3
+ name: generateDiagram
4
+ skills:
5
+ - ../generate/draw-diagram.yaml
6
+ - ../generate/wrap-diagram-code.mjs
7
+ reflection:
8
+ reviewer: ../generate/check-diagram.mjs
9
+ is_approved: isValid
10
+ max_iterations: 5
11
+ return_last_on_max_iterations: false
12
+ input_schema:
13
+ type: object
14
+ properties:
15
+ documentContent:
16
+ type: string
17
+ description: The **raw text content** of the current document. (**Note:** This is the original document and **does not include** any diagram source code.)
18
+ locale:
19
+ type: string
20
+ description: Language for diagram labels and text
21
+ default: en
22
+ required:
23
+ - documentContent
24
+ output_schema:
25
+ type: object
26
+ properties:
27
+ diagramSourceCode:
28
+ type: string
29
+ description: The **diagram source code** generated from the input text.
@@ -16,7 +16,7 @@ input_schema:
16
16
  locale:
17
17
  type: string
18
18
  description: User language, such as zh, en
19
- datasources:
19
+ detailDataSource:
20
20
  type: string
21
21
  description: Source data and context for document content generation
22
22
  targetAudience:
@@ -44,9 +44,21 @@ input_schema:
44
44
  description: Additional supplementary information
45
45
  required:
46
46
  - rules
47
- - datasources
47
+ - detailDataSource
48
48
  - originalDocumentStructure
49
- output_key: content
49
+ # output_key: content
50
+ output_schema:
51
+ type: object
52
+ properties:
53
+ content:
54
+ type: string
55
+ description: Document content
56
+ needDiagram:
57
+ type: boolean
58
+ description: Does this document need to generate diagram?
59
+ required:
60
+ - content
61
+ - detailDataSource
50
62
  afs:
51
63
  modules:
52
64
  - module: system-fs
@@ -57,30 +69,5 @@ afs:
57
69
  Codebase of the project to be documented used as context for document generation,
58
70
  should search and read as needed while generating document content
59
71
  keep_text_in_tool_uses: false
60
- skills:
61
- - type: team
62
- task_render_mode: collapse
63
- name: generateDiagram
64
- skills:
65
- - ../generate/draw-diagram.yaml
66
- - ../generate/wrap-diagram-code.mjs
67
- reflection:
68
- reviewer: ../generate/check-diagram.mjs
69
- is_approved: isValid
70
- max_iterations: 5
71
- return_last_on_max_iterations: false
72
- custom_error_message: "MUST NOT generate any diagram: validation failed after max iterations."
73
- input_schema:
74
- type: object
75
- properties:
76
- documentContent:
77
- type: string
78
- description: The **raw text content** of the current document. (**Note:** This is the original document and **does not include** any diagram source code.)
79
- required:
80
- - documentContent
81
- output_schema:
82
- type: object
83
- properties:
84
- diagramSourceCode:
85
- type: string
86
- description: The **diagram source code** generated from the input text.
72
+ # skills:
73
+ # - ./generate-diagram.yaml
@@ -2,7 +2,7 @@ type: team
2
2
  name: handleDocumentUpdate
3
3
  description: Update a document in a batch
4
4
  skills:
5
- - ../utils/transform-detail-datasources.mjs
5
+ - ../utils/transform-detail-data-sources.mjs
6
6
  - type: team
7
7
  task_render_mode: collapse
8
8
  name: generateDocumentContent
@@ -23,6 +23,15 @@ skills:
23
23
  max_iterations: 5
24
24
  return_last_on_max_iterations: true
25
25
  task_title: Generate document for '{{ title }}'
26
+ - type: transform
27
+ jsonata: |
28
+ $merge([
29
+ $,
30
+ { "documentContent": content }
31
+ ])
32
+ - ./check-generate-diagram.mjs
33
+ # - ../generate/merge-diagram.yaml
34
+ - ../utils/save-doc.mjs
26
35
  input_schema:
27
36
  type: object
28
37
  properties:
@@ -1,3 +1,4 @@
1
+ import pMap from "p-map";
1
2
  import { recordUpdate } from "../../utils/history-utils.mjs";
2
3
 
3
4
  export default async function saveAndTranslateDocument(input, options) {
@@ -7,20 +8,15 @@ export default async function saveAndTranslateDocument(input, options) {
7
8
  return {};
8
9
  }
9
10
 
10
- // Saves a document with optional translation data
11
- const saveDocument = async (doc, translates = null, isTranslate = false) => {
12
- const saveAgent = options.context.agents["saveSingleDoc"];
13
-
14
- return await options.context.invoke(saveAgent, {
15
- path: doc.path,
16
- content: doc.content,
17
- docsDir: docsDir,
18
- locale: locale,
19
- translates: translates || doc.translates,
20
- labels: doc.labels,
21
- isTranslate: isTranslate,
11
+ // Record history if feedback is provided
12
+ const doc = selectedDocs[0];
13
+ if (doc.feedback?.trim()) {
14
+ recordUpdate({
15
+ operation: "document_update",
16
+ feedback: doc.feedback.trim(),
17
+ docPaths: selectedDocs.map((v) => v.path),
22
18
  });
23
- };
19
+ }
24
20
 
25
21
  // Only prompt user if translation is actually needed
26
22
  let shouldTranslate = false;
@@ -46,28 +42,6 @@ export default async function saveAndTranslateDocument(input, options) {
46
42
 
47
43
  // Save documents in batches
48
44
  const batchSize = 3;
49
- for (let i = 0; i < selectedDocs.length; i += batchSize) {
50
- const batch = selectedDocs.slice(i, i + batchSize);
51
-
52
- const savePromises = batch.map(async (doc) => {
53
- try {
54
- await saveDocument(doc);
55
-
56
- // Record history for each document if feedback is provided
57
- if (doc.feedback?.trim()) {
58
- recordUpdate({
59
- operation: "document_update",
60
- feedback: doc.feedback.trim(),
61
- documentPath: doc.path,
62
- });
63
- }
64
- } catch (error) {
65
- console.error(`❌ Failed to save document ${doc.path}:`, error.message);
66
- }
67
- });
68
-
69
- await Promise.all(savePromises);
70
- }
71
45
 
72
46
  // Return results if user chose to skip translation
73
47
  if (!shouldTranslate) {
@@ -77,30 +51,27 @@ export default async function saveAndTranslateDocument(input, options) {
77
51
  // Translate documents in batches
78
52
  const translateAgent = options.context.agents["translateMultilingual"];
79
53
 
80
- for (let i = 0; i < selectedDocs.length; i += batchSize) {
81
- const batch = selectedDocs.slice(i, i + batchSize);
82
-
83
- const translatePromises = batch.map(async (doc) => {
54
+ await pMap(
55
+ selectedDocs,
56
+ async (doc) => {
84
57
  try {
85
58
  // Clear feedback to ensure translation is not affected by update feedback
86
59
  doc.feedback = "";
87
60
 
88
- const result = await options.context.invoke(translateAgent, {
61
+ await options.context.invoke(translateAgent, {
89
62
  ...input, // context is required
90
63
  content: doc.content,
91
64
  translates: doc.translates,
92
65
  title: doc.title,
66
+ path: doc.path,
67
+ docsDir,
93
68
  });
94
-
95
- // Save the translated content
96
- await saveDocument(doc, result.translates, true);
97
69
  } catch (error) {
98
70
  console.error(`❌ Failed to translate document ${doc.path}:`, error.message);
99
71
  }
100
- });
101
-
102
- await Promise.all(translatePromises);
103
- }
72
+ },
73
+ { concurrency: batchSize },
74
+ );
104
75
 
105
76
  return {};
106
77
  }
@@ -23,7 +23,7 @@ input_schema:
23
23
  locale:
24
24
  type: string
25
25
  description: User language, e.g. zh, en
26
- datasources:
26
+ detailDataSource:
27
27
  type: string
28
28
  description: Context for document content
29
29
  glossary:
@@ -57,4 +57,5 @@ afs:
57
57
  keep_text_in_tool_uses: false
58
58
  skills:
59
59
  - ./document-tools/update-document-content.mjs
60
+ # - ./generate-diagram.yaml
60
61
  task_render_mode: collapse
@@ -1,7 +1,7 @@
1
1
  type: team
2
2
  name: updateSingleDocument
3
3
  skills:
4
- - ../utils/transform-detail-datasources.mjs
4
+ - ../utils/transform-detail-data-sources.mjs
5
5
  - ../update/user-review-document.mjs
6
6
  iterate_on: selectedDocs
7
7
  concurrency: 1
@@ -105,6 +105,7 @@ async function showDocumentDetail(content, title) {
105
105
  renderer: new markedTerminal(),
106
106
  });
107
107
 
108
+ // FIXME: @zhanghan fix error "Could not find the language 'd2', did you forget to load/include a language module?"
108
109
  const renderedMarkdown = marked(content);
109
110
 
110
111
  // Restore original console.error
@@ -121,16 +122,15 @@ async function showDocumentDetail(content, title) {
121
122
  }
122
123
  }
123
124
 
124
- export default async function userReviewDocument(
125
- { content, title, description, ...rest },
126
- options,
127
- ) {
125
+ export default async function userReviewDocument({ content, description, ...rest }, options) {
128
126
  // Check if document content exists
129
127
  if (!content || typeof content !== "string" || content.trim().length === 0) {
130
128
  console.log("Please provide document content to review.");
131
129
  return { content };
132
130
  }
133
131
 
132
+ const title = rest.documentStructure?.find((x) => x.path === rest.path)?.title;
133
+
134
134
  // Print current document headings structure
135
135
  printDocumentHeadings(content, title || "Untitled Document");
136
136
 
@@ -190,7 +190,7 @@ export default async function userReviewDocument(
190
190
  feedbacks.push(feedback.trim());
191
191
 
192
192
  // Get the updateDocument agent
193
- const updateAgent = options.context.agents["updateDocumentDetail"];
193
+ const updateAgent = options.context.agents["handleDocumentUpdate"];
194
194
  if (!updateAgent) {
195
195
  console.log(
196
196
  "We can't process your feedback right now. The document update feature is temporarily unavailable.",
@@ -213,6 +213,7 @@ export default async function userReviewDocument(
213
213
  originalContent: options.context.userContext.currentContent,
214
214
  feedback: feedback.trim(),
215
215
  userPreferences,
216
+ title,
216
217
  });
217
218
 
218
219
  // Check if feedback should be saved as user preference
@@ -18,6 +18,7 @@ export default async function chooseDocs(
18
18
  locale,
19
19
  reset = false,
20
20
  requiredFeedback = true,
21
+ title,
21
22
  },
22
23
  options,
23
24
  ) {
@@ -65,7 +66,7 @@ export default async function chooseDocs(
65
66
 
66
67
  // Let user select multiple files
67
68
  selectedFiles = await options.prompts.checkbox({
68
- message: getActionText(isTranslate, "Select documents to {action}:"),
69
+ message: title || getActionText(isTranslate, "Select documents to {action}:"),
69
70
  source: (term) => {
70
71
  if (!term) return choices;
71
72
 
@@ -4,11 +4,11 @@ import path from "node:path";
4
4
  import imageSize from "image-size";
5
5
  import {
6
6
  buildSourcesContent,
7
- calculateFileStats,
8
7
  loadFilesFromPaths,
9
8
  readFileContents,
10
9
  getMimeType,
11
- checkIsRemoteFile,
10
+ isRemoteFile,
11
+ calculateTokens,
12
12
  } from "../../utils/file-utils.mjs";
13
13
  import {
14
14
  getCurrentGitHead,
@@ -21,6 +21,7 @@ import {
21
21
  DEFAULT_INCLUDE_PATTERNS,
22
22
  } from "../../utils/constants/index.mjs";
23
23
  import { isOpenAPISpecFile } from "../../utils/openapi/index.mjs";
24
+ import { loadDocumentStructure } from "../../utils/docs-finder-utils.mjs";
24
25
 
25
26
  export default async function loadSources(
26
27
  {
@@ -144,7 +145,7 @@ export default async function loadSources(
144
145
  files.map(async (file) => {
145
146
  const ext = path.extname(file).toLowerCase();
146
147
 
147
- if (mediaExtensions.includes(ext) && !checkIsRemoteFile(file)) {
148
+ if (mediaExtensions.includes(ext) && !isRemoteFile(file)) {
148
149
  // This is a media file
149
150
  const relativePath = path.relative(docsDir, file);
150
151
  const fileName = path.basename(file);
@@ -195,13 +196,10 @@ export default async function loadSources(
195
196
  }
196
197
 
197
198
  // Read all source files using the utility function
198
- let sourceFiles = await readFileContents(sourceFilesPaths, process.cwd());
199
-
200
- // Count tokens and lines using utility function
201
- const { totalTokens, totalLines } = calculateFileStats(sourceFiles);
202
-
203
- // check if totalTokens is too large
204
- const isLargeContext = totalTokens > INTELLIGENT_SUGGESTION_TOKEN_THRESHOLD;
199
+ let sourceFiles = (await readFileContents(sourceFilesPaths, process.cwd())).map((i) => ({
200
+ ...i,
201
+ tokens: calculateTokens(`\n${i.sourceId}\n${i.content}`),
202
+ }));
205
203
 
206
204
  // filter OpenAPI doc should after check isLargeContext
207
205
  sourceFiles = sourceFiles.filter((file) => {
@@ -214,48 +212,32 @@ export default async function loadSources(
214
212
  return !isOpenAPI;
215
213
  });
216
214
 
217
- const httpFileList = [];
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
+ );
220
+
221
+ const dataSources = splitSourcesToChunks(sourceFiles, INTELLIGENT_SUGGESTION_TOKEN_THRESHOLD).map(
222
+ (i) => ({ dataSourceChunk: buildSourcesContent(i) }),
223
+ );
224
+
225
+ const remoteFileList = [];
218
226
 
219
227
  sourceFiles.forEach((file) => {
220
- if (checkIsRemoteFile(file.sourceId)) {
221
- httpFileList.push(file);
228
+ if (isRemoteFile(file.sourceId)) {
229
+ remoteFileList.push(file);
222
230
  }
223
231
  });
224
232
  if (options?.context?.userContext) {
225
- options.context.userContext.httpFileList = httpFileList;
233
+ options.context.userContext.remoteFileList = remoteFileList;
226
234
  }
227
235
 
228
- // Build allSources string using utility function
229
- const allSources = buildSourcesContent(sourceFiles, isLargeContext);
230
236
  // all files path
231
237
  const allFilesPaths = sourceFiles.map((x) => `- ${toRelativePath(x.sourceId)}`).join("\n");
232
238
 
233
239
  // Get the last documentation structure
234
- let originalDocumentStructure;
235
- if (outputDir) {
236
- const documentStructurePath = path.join(outputDir, "structure-plan.json");
237
- try {
238
- const documentExecutionStructure = await readFile(documentStructurePath, "utf8");
239
- if (documentExecutionStructure?.trim()) {
240
- try {
241
- // Validate that the content looks like JSON before parsing
242
- const trimmedContent = documentExecutionStructure.trim();
243
- if (trimmedContent.startsWith("{") || trimmedContent.startsWith("[")) {
244
- originalDocumentStructure = JSON.parse(documentExecutionStructure);
245
- } else {
246
- console.warn(`structure-plan.json contains non-JSON content, skipping parse`);
247
- }
248
- } catch (err) {
249
- console.error(`Failed to parse structure-plan.json: ${err.message}`);
250
- }
251
- }
252
- } catch (err) {
253
- if (err.code !== "ENOENT") {
254
- console.warn(`Error reading structure-plan.json: ${err.message}`);
255
- }
256
- // The file does not exist or is not readable, originalDocumentStructure remains undefined
257
- }
258
- }
240
+ const originalDocumentStructure = await loadDocumentStructure(outputDir);
259
241
 
260
242
  // Get the last output result of the specified path
261
243
  let content;
@@ -308,7 +290,7 @@ export default async function loadSources(
308
290
  }
309
291
 
310
292
  return {
311
- datasources: allSources,
293
+ dataSources,
312
294
  content,
313
295
  originalDocumentStructure,
314
296
  files,
@@ -316,7 +298,6 @@ export default async function loadSources(
316
298
  totalTokens,
317
299
  totalLines,
318
300
  mediaFiles,
319
- isLargeContext,
320
301
  allFilesPaths,
321
302
  };
322
303
  }
@@ -364,8 +345,14 @@ loadSources.input_schema = {
364
345
  loadSources.output_schema = {
365
346
  type: "object",
366
347
  properties: {
367
- datasources: {
368
- type: "string",
348
+ dataSources: {
349
+ type: "array",
350
+ items: {
351
+ type: "object",
352
+ properties: {
353
+ dataSourceChunk: { type: "string" },
354
+ },
355
+ },
369
356
  },
370
357
  files: {
371
358
  type: "array",
@@ -396,3 +383,33 @@ loadSources.output_schema = {
396
383
  };
397
384
 
398
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, writeFile } from "node:fs/promises";
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 saveDocs({
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
- }