@aigne/doc-smith 0.9.7 → 0.9.8-alpha.0

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 (65) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/agents/create/analyze-diagram-type-llm.yaml +160 -0
  3. package/agents/create/analyze-diagram-type.mjs +297 -0
  4. package/agents/create/check-need-generate-structure.mjs +1 -34
  5. package/agents/create/generate-diagram-image.yaml +60 -0
  6. package/agents/create/index.yaml +9 -5
  7. package/agents/create/replace-d2-with-image.mjs +625 -0
  8. package/agents/create/user-review-document-structure.mjs +8 -7
  9. package/agents/create/utils/init-current-content.mjs +5 -9
  10. package/agents/evaluate/document.yaml +6 -0
  11. package/agents/evaluate/index.yaml +1 -0
  12. package/agents/init/index.mjs +36 -388
  13. package/agents/localize/index.yaml +4 -4
  14. package/agents/media/batch-generate-media-description.yaml +2 -0
  15. package/agents/media/generate-media-description.yaml +3 -0
  16. package/agents/media/load-media-description.mjs +44 -15
  17. package/agents/publish/index.yaml +1 -0
  18. package/agents/publish/publish-docs.mjs +1 -4
  19. package/agents/update/check-diagram-flag.mjs +116 -0
  20. package/agents/update/check-document.mjs +0 -1
  21. package/agents/update/check-generate-diagram.mjs +48 -30
  22. package/agents/update/check-sync-image-flag.mjs +55 -0
  23. package/agents/update/check-update-is-single.mjs +11 -0
  24. package/agents/update/generate-diagram.yaml +43 -9
  25. package/agents/update/generate-document.yaml +9 -0
  26. package/agents/update/handle-document-update.yaml +10 -8
  27. package/agents/update/index.yaml +25 -7
  28. package/agents/update/sync-images-and-exit.mjs +148 -0
  29. package/agents/update/update-single/update-single-document-detail.mjs +131 -17
  30. package/agents/utils/analyze-feedback-intent.mjs +136 -0
  31. package/agents/utils/choose-docs.mjs +185 -40
  32. package/agents/utils/generate-document-or-skip.mjs +41 -0
  33. package/agents/utils/handle-diagram-operations.mjs +263 -0
  34. package/agents/utils/load-all-document-content.mjs +30 -0
  35. package/agents/utils/load-sources.mjs +2 -2
  36. package/agents/utils/post-generate.mjs +14 -3
  37. package/agents/utils/read-current-document-content.mjs +46 -0
  38. package/agents/utils/save-doc-translation.mjs +34 -0
  39. package/agents/utils/save-doc.mjs +42 -0
  40. package/agents/utils/save-sidebar.mjs +19 -6
  41. package/agents/utils/skip-if-content-exists.mjs +27 -0
  42. package/aigne.yaml +15 -3
  43. package/assets/report-template/report.html +17 -17
  44. package/docs-mcp/read-doc-content.mjs +30 -1
  45. package/package.json +8 -7
  46. package/prompts/detail/diagram/generate-image-system.md +135 -0
  47. package/prompts/detail/diagram/generate-image-user.md +32 -0
  48. package/prompts/detail/generate/user-prompt.md +27 -13
  49. package/prompts/evaluate/document.md +23 -10
  50. package/prompts/media/media-description/system-prompt.md +10 -2
  51. package/prompts/media/media-description/user-prompt.md +9 -0
  52. package/utils/check-document-has-diagram.mjs +95 -0
  53. package/utils/constants/index.mjs +46 -0
  54. package/utils/d2-utils.mjs +119 -178
  55. package/utils/delete-diagram-images.mjs +99 -0
  56. package/utils/docs-finder-utils.mjs +133 -25
  57. package/utils/image-compress.mjs +75 -0
  58. package/utils/kroki-utils.mjs +2 -3
  59. package/utils/load-config.mjs +29 -0
  60. package/utils/sync-diagram-to-translations.mjs +262 -0
  61. package/utils/utils.mjs +24 -0
  62. package/agents/create/check-diagram.mjs +0 -40
  63. package/agents/create/draw-diagram.yaml +0 -27
  64. package/agents/create/merge-diagram.yaml +0 -39
  65. package/agents/create/wrap-diagram-code.mjs +0 -35
@@ -0,0 +1,263 @@
1
+ import { AIAgent } from "@aigne/core";
2
+ import { pick } from "@aigne/core/utils/type-utils.js";
3
+ import z from "zod";
4
+ import {
5
+ DIAGRAM_PLACEHOLDER,
6
+ replaceD2WithPlaceholder,
7
+ replaceDiagramsWithPlaceholder,
8
+ } from "../../utils/d2-utils.mjs";
9
+ import { readFileContent } from "../../utils/docs-finder-utils.mjs";
10
+ import { getFileName, userContextAt } from "../../utils/utils.mjs";
11
+ import { debug } from "../../utils/debug.mjs";
12
+
13
+ /**
14
+ * Read current document content from file system
15
+ * Reuses the same logic as initCurrentContent but returns the content
16
+ * First checks userContext, then reads from file if not in context
17
+ */
18
+ async function readCurrentContent(input, options) {
19
+ const { path, docsDir, locale = "en" } = input;
20
+
21
+ if (!path || !docsDir) {
22
+ return null;
23
+ }
24
+
25
+ // First check if content is already in userContext
26
+ const contentContext = userContextAt(options, `currentContents.${path}`);
27
+ const existingContent = contentContext.get();
28
+ if (existingContent) {
29
+ return existingContent;
30
+ }
31
+
32
+ // If not in context, read from file (same logic as initCurrentContent)
33
+ try {
34
+ const fileName = getFileName(path, locale);
35
+ const content = await readFileContent(docsDir, fileName);
36
+
37
+ if (!content) {
38
+ console.warn(`⚠️ Could not read content from ${fileName}`);
39
+ return null;
40
+ }
41
+
42
+ // Set in userContext for future use
43
+ contentContext.set(content);
44
+
45
+ return content;
46
+ } catch (error) {
47
+ console.warn(`Failed to read current content for ${path}: ${error.message}`);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Save document content
54
+ */
55
+ async function saveDoc(input, options, { content, intentType }) {
56
+ const saveAgent = options.context?.agents?.["saveDoc"];
57
+ if (!saveAgent) {
58
+ console.warn("saveDoc agent not found");
59
+ return;
60
+ }
61
+ await options.context.invoke(saveAgent, {
62
+ ...pick(input, ["path", "docsDir", "labels", "locale"]),
63
+ content,
64
+ intentType, // Pass intentType so saveDoc can handle translation sync
65
+ });
66
+ }
67
+
68
+ /**
69
+ * Handle addDiagram intent
70
+ */
71
+ async function addDiagram(input, options) {
72
+ // readCurrentContent will check userContext first, then read from file if needed
73
+ const currentContent = await readCurrentContent(input, options);
74
+ if (!currentContent) {
75
+ throw new Error(`Could not read current content for ${input.path}`);
76
+ }
77
+
78
+ const contentContext = userContextAt(options, `currentContents.${input.path}`);
79
+
80
+ const generateDiagramAgent = options.context.agents["checkGenerateDiagram"];
81
+ if (!generateDiagramAgent) {
82
+ throw new Error("checkGenerateDiagram agent not found");
83
+ }
84
+
85
+ const generateDiagramResult = await options.context.invoke(generateDiagramAgent, {
86
+ ...pick(input, ["locale", "diagramming", "feedback", "path", "docsDir"]),
87
+ documentContent: currentContent,
88
+ originalContent: currentContent,
89
+ });
90
+
91
+ const content = generateDiagramResult.content;
92
+ contentContext.set(content);
93
+ // Pass intentType to saveDoc so it can handle translation sync automatically
94
+ await saveDoc(input, options, { content, intentType: "addDiagram" });
95
+ return { content };
96
+ }
97
+
98
+ /**
99
+ * Handle updateDiagram intent
100
+ */
101
+ async function updateDiagram(input, options) {
102
+ // readCurrentContent will check userContext first, then read from file if needed
103
+ const currentContent = await readCurrentContent(input, options);
104
+ if (!currentContent) {
105
+ throw new Error(`Could not read current content for ${input.path}`);
106
+ }
107
+
108
+ const contentContext = userContextAt(options, `currentContents.${input.path}`);
109
+
110
+ let [content] = replaceD2WithPlaceholder({
111
+ content: currentContent,
112
+ });
113
+
114
+ const generateAgent = options.context?.agents?.["generateDiagram"];
115
+ if (!generateAgent) {
116
+ throw new Error("generateDiagram agent not found");
117
+ }
118
+
119
+ const result = await options.context.invoke(generateAgent, {
120
+ documentContent: content,
121
+ locale: input.locale,
122
+ diagramming: input.diagramming || {},
123
+ feedback: input.feedback,
124
+ originalContent: currentContent, // Pass original content to find existing diagrams
125
+ path: input.path,
126
+ docsDir: input.docsDir,
127
+ });
128
+
129
+ // generateDiagram now returns { content } with image already inserted
130
+ // The image replaces DIAGRAM_PLACEHOLDER or D2 code blocks
131
+ if (result?.content) {
132
+ content = result.content;
133
+ }
134
+
135
+ contentContext.set(content);
136
+ // Pass intentType to saveDoc so it can handle translation sync automatically
137
+ await saveDoc(input, options, { content, intentType: "updateDiagram" });
138
+ return { content };
139
+ }
140
+
141
+ /**
142
+ * Handle deleteDiagram intent
143
+ */
144
+ async function deleteDiagram(input, options) {
145
+ // readCurrentContent will check userContext first, then read from file if needed
146
+ const currentContent = await readCurrentContent(input, options);
147
+ if (!currentContent) {
148
+ throw new Error(`Could not read current content for ${input.path}`);
149
+ }
150
+
151
+ const contentContext = userContextAt(options, `currentContents.${input.path}`);
152
+
153
+ // Extract diagram index from feedback if provided
154
+ // This allows deleting a specific diagram when multiple diagrams exist
155
+ let diagramIndex = input.diagramIndex;
156
+ if (diagramIndex === undefined && input.feedback) {
157
+ // Import extractDiagramIndexFromFeedback from replace-d2-with-image.mjs
158
+ const { extractDiagramIndexFromFeedback } = await import("../create/replace-d2-with-image.mjs");
159
+ const extractedIndex = extractDiagramIndexFromFeedback(input.feedback);
160
+ if (extractedIndex !== null) {
161
+ diagramIndex = extractedIndex;
162
+ debug(`Extracted diagram index ${diagramIndex} from feedback: "${input.feedback}"`);
163
+ }
164
+ }
165
+
166
+ // Replace all diagrams (D2 code blocks, generated images, Mermaid) with placeholder
167
+ // If diagramIndex is provided, only replace that specific diagram
168
+ // This ensures LLM can identify and remove the diagram regardless of its type
169
+ const documentContent = replaceDiagramsWithPlaceholder({
170
+ content: currentContent,
171
+ diagramIndex,
172
+ });
173
+
174
+ const instructions = `<role>
175
+ Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (based on the user's feedback) to make it easier to understand.
176
+ </role>
177
+
178
+ <document_content>
179
+ {{documentContent}}
180
+ </document_content>
181
+
182
+ <user_feedback>
183
+ {{feedback}}
184
+ </user_feedback>
185
+
186
+ <output_constraints>
187
+ - Do not provide any explanations; include only the document content itself
188
+ </output_constraints>`;
189
+
190
+ const deleteAgent = AIAgent.from({
191
+ name: "deleteDiagram",
192
+ description: "Remove a diagram from the document content",
193
+ task_render_mode: "hide",
194
+ instructions,
195
+ inputSchema: z.object({
196
+ documentContent: z.string().describe("Source content of the document"),
197
+ feedback: z.string().describe("User feedback for content modifications"),
198
+ }),
199
+ outputKey: "message",
200
+ });
201
+
202
+ const { message: content } = await options.context.invoke(deleteAgent, {
203
+ documentContent,
204
+ feedback: input.feedback,
205
+ });
206
+
207
+ // Delete associated diagram image files
208
+ if (input.docsDir) {
209
+ try {
210
+ const { deleteDiagramImages } = await import("../../utils/delete-diagram-images.mjs");
211
+ const { deleted } = await deleteDiagramImages(currentContent, input.path, input.docsDir);
212
+ if (deleted > 0) {
213
+ debug(`Deleted ${deleted} diagram image file(s) for ${input.path}`);
214
+ }
215
+ } catch (error) {
216
+ // Don't fail the operation if image deletion fails
217
+ console.warn(`Failed to delete diagram images: ${error.message}`);
218
+ }
219
+ }
220
+
221
+ contentContext.set(content);
222
+ // Pass intentType to saveDoc so it can handle translation sync automatically
223
+ await saveDoc(input, options, { content, intentType: "deleteDiagram" });
224
+
225
+ return { content };
226
+ }
227
+
228
+ /**
229
+ * Handle diagram operations based on intent type
230
+ * Supports: addDiagram, updateDiagram, deleteDiagram
231
+ */
232
+ export default async function handleDiagramOperations(
233
+ { intentType, path, docsDir, locale, feedback, diagramming, labels },
234
+ options,
235
+ ) {
236
+ if (!intentType || !["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)) {
237
+ throw new Error(`Invalid intent type for diagram operations: ${intentType}`);
238
+ }
239
+
240
+ const input = {
241
+ path,
242
+ docsDir,
243
+ locale,
244
+ feedback,
245
+ diagramming,
246
+ labels,
247
+ };
248
+
249
+ const fnMap = {
250
+ addDiagram,
251
+ updateDiagram,
252
+ deleteDiagram,
253
+ };
254
+
255
+ if (fnMap[intentType]) {
256
+ const result = await fnMap[intentType](input, options);
257
+ return result;
258
+ }
259
+
260
+ throw new Error(`Unsupported intent type: ${intentType}`);
261
+ }
262
+
263
+ handleDiagramOperations.task_render_mode = "hide";
@@ -0,0 +1,30 @@
1
+ import pMap from "p-map";
2
+ import { findItemByPath } from "../../utils/docs-finder-utils.mjs";
3
+
4
+ /**
5
+ * Loads all document content from the file system based on the provided document structure.
6
+ *
7
+ * @async
8
+ * @function loadAllDocumentContent
9
+ * @param {Object} params - The parameters object
10
+ * @param {string} params.docsDir - The root directory path where documents are located
11
+ * @param {Array<Object>} params.documentStructure - The document structure array containing items with path information
12
+ * @param {string} params.documentStructure[].path - The file path of each document item
13
+ * @returns {Promise<Array>} returns.allDocumentContentList - An array of document content items loaded from the file system
14
+ * @example
15
+ * const result = await loadAllDocumentContent({
16
+ * docsDir: './docs',
17
+ * documentStructure: [{ path: 'readme.md' }, { path: 'guide.md' }]
18
+ * });
19
+ */
20
+ export default async function loadAllDocumentContent({ docsDir, documentStructure = [] }) {
21
+ const allDocumentContentList = await pMap(documentStructure, async (item) => {
22
+ const itemResult = await findItemByPath(documentStructure, item.path, null, docsDir);
23
+ return itemResult;
24
+ });
25
+ return {
26
+ allDocumentContentList,
27
+ };
28
+ }
29
+
30
+ loadAllDocumentContent.taskRenderMode = "hide";
@@ -157,8 +157,8 @@ export default async function loadSources(
157
157
  mediaItem.width = dimensions.width;
158
158
  mediaItem.height = dimensions.height;
159
159
 
160
- // Filter out images with width less than minImageWidth
161
- if (dimensions.width < minImageWidth) {
160
+ // Filter out images with width less than minImageWidth, but not SVG images
161
+ if (dimensions.width < minImageWidth && mediaItem.mimeType !== "image/svg+xml") {
162
162
  filteredImageCount++;
163
163
  console.log(
164
164
  `Ignored image: ${fileName} (${dimensions.width}x${dimensions.height}px < ${minImageWidth}px minimum)`,
@@ -16,6 +16,8 @@ export default async function postGenerate({
16
16
  translateLanguages = [],
17
17
  locale,
18
18
  projectInfoMessage,
19
+ isChat,
20
+ feedback,
19
21
  }) {
20
22
  const _results = [];
21
23
  // Save current git HEAD to config.yaml for change detection
@@ -33,12 +35,21 @@ export default async function postGenerate({
33
35
  console.error("Failed to cleanup invalid .md files:", err.message);
34
36
  }
35
37
 
36
- const message = `
38
+ let message = `
37
39
  ✅ Documentation generated successfully! (\`${documentStructure.length}\` documents → \`${docsDir}\`)
38
- ${projectInfoMessage || ""}
40
+ ${projectInfoMessage || ""} `;
41
+
42
+ if (!isChat) {
43
+ message += `
39
44
  🚀 Next: Make your documentation live and generate a shareable link, run: \`aigne doc publish\`
40
45
  💡 Optional: Update specific document (\`aigne doc update\`) or refine documentation structure (\`aigne doc create\`)
41
- `;
46
+ `;
47
+ }
48
+
49
+ if (isChat && feedback) {
50
+ message = `User feedback: ${feedback}
51
+ The documentation structure and relevant documents have been updated according to the user's feedback. All feedback has been fully processed.`;
52
+ }
42
53
 
43
54
  // Shutdown mermaid worker pool to ensure clean exit
44
55
  try {
@@ -0,0 +1,46 @@
1
+ import { readFileContent } from "../../utils/docs-finder-utils.mjs";
2
+ import { getFileName } from "../../utils/utils.mjs";
3
+
4
+ /**
5
+ * Read current document content from file system
6
+ * Used when skipping document generation (e.g., for diagram-only updates)
7
+ * Only reads content if intentType is diagram-related, otherwise returns input unchanged
8
+ */
9
+ export default async function readCurrentDocumentContent(input) {
10
+ const { path, docsDir, locale = "en", intentType } = input;
11
+
12
+ // Only read content if intentType is diagram-related
13
+ // Otherwise, return input unchanged to allow document generation to proceed
14
+ if (!intentType || !["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)) {
15
+ return input;
16
+ }
17
+
18
+ if (!path || !docsDir) {
19
+ return input;
20
+ }
21
+
22
+ try {
23
+ // Read document content using the same utility as other parts of the system
24
+ const fileName = getFileName(path, locale);
25
+ const content = await readFileContent(docsDir, fileName);
26
+
27
+ if (!content) {
28
+ console.warn(`⚠️ Could not read content from ${fileName}`);
29
+ return input;
30
+ }
31
+
32
+ // Return content as both content, documentContent, and originalContent
33
+ // This matches the structure expected by downstream agents
34
+ return {
35
+ ...input,
36
+ content,
37
+ documentContent: content,
38
+ originalContent: content,
39
+ };
40
+ } catch (error) {
41
+ console.warn(`Failed to read current content for ${path}: ${error.message}`);
42
+ return input;
43
+ }
44
+ }
45
+
46
+ readCurrentDocumentContent.task_render_mode = "hide";
@@ -1,4 +1,8 @@
1
1
  import { saveDocTranslation as _saveDocTranslation } from "../../utils/utils.mjs";
2
+ import { readFileContent } from "../../utils/docs-finder-utils.mjs";
3
+ import { getFileName } from "../../utils/utils.mjs";
4
+ import { debug } from "../../utils/debug.mjs";
5
+ import { syncDiagramToTranslations } from "../../utils/sync-diagram-to-translations.mjs";
2
6
 
3
7
  export default async function saveDocTranslation({
4
8
  path,
@@ -7,6 +11,7 @@ export default async function saveDocTranslation({
7
11
  language,
8
12
  labels,
9
13
  isShowMessage = false,
14
+ locale,
10
15
  }) {
11
16
  await _saveDocTranslation({
12
17
  path,
@@ -16,6 +21,35 @@ export default async function saveDocTranslation({
16
21
  labels,
17
22
  });
18
23
 
24
+ // Sync diagram images from main document to translations
25
+ // This ensures all images (including diagrams) in the main document are synced to translation files
26
+ if (path && docsDir && locale) {
27
+ try {
28
+ // Read main document content (it should already be saved)
29
+ const mainFileName = getFileName(path, locale);
30
+ const mainContent = await readFileContent(docsDir, mainFileName);
31
+
32
+ if (mainContent) {
33
+ const syncResult = await syncDiagramToTranslations(
34
+ mainContent,
35
+ path,
36
+ docsDir,
37
+ locale,
38
+ "sync",
39
+ );
40
+
41
+ if (syncResult.updated > 0) {
42
+ debug(
43
+ `✅ Synced diagram images to ${syncResult.updated} translation file(s) after translation`,
44
+ );
45
+ }
46
+ }
47
+ } catch (error) {
48
+ // Don't fail the translation if sync fails
49
+ debug(`⚠️ Failed to sync diagram images after translation: ${error.message}`);
50
+ }
51
+ }
52
+
19
53
  if (isShowMessage) {
20
54
  const message = `✅ Translation completed successfully.`;
21
55
  return { message };
@@ -1,5 +1,6 @@
1
1
  import { shutdownMermaidWorkerPool } from "../../utils/mermaid-worker-pool.mjs";
2
2
  import { saveDoc as _saveDoc } from "../../utils/utils.mjs";
3
+ import { debug } from "../../utils/debug.mjs";
3
4
 
4
5
  export default async function saveDoc({
5
6
  path,
@@ -9,6 +10,8 @@ export default async function saveDoc({
9
10
  locale,
10
11
  feedback,
11
12
  isShowMessage = false,
13
+ intentType,
14
+ originalContent,
12
15
  ...rest
13
16
  }) {
14
17
  await _saveDoc({
@@ -19,6 +22,43 @@ export default async function saveDoc({
19
22
  locale,
20
23
  });
21
24
 
25
+ // Sync diagram changes to translation documents if needed
26
+ // Only sync for diagram-related operations (addDiagram, updateDiagram, deleteDiagram)
27
+ if (
28
+ docsDir &&
29
+ path &&
30
+ intentType &&
31
+ ["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType)
32
+ ) {
33
+ try {
34
+ const { syncDiagramToTranslations } = await import(
35
+ "../../utils/sync-diagram-to-translations.mjs"
36
+ );
37
+
38
+ // Determine operation type for sync
39
+ // deleteDiagram -> "delete" (process even if 0 diagrams)
40
+ // addDiagram/updateDiagram -> "update" (skip if 0 diagrams)
41
+ const operationType = intentType === "deleteDiagram" ? "delete" : "update";
42
+
43
+ const syncResult = await syncDiagramToTranslations(
44
+ content,
45
+ path,
46
+ docsDir,
47
+ locale || "en",
48
+ operationType,
49
+ );
50
+
51
+ if (syncResult.updated > 0) {
52
+ debug(
53
+ `✅ Synced diagram changes to ${syncResult.updated} translation file(s) for ${intentType}`,
54
+ );
55
+ }
56
+ } catch (error) {
57
+ // Don't fail the operation if sync fails
58
+ debug(`⚠️ Failed to sync diagram to translations: ${error.message}`);
59
+ }
60
+ }
61
+
22
62
  if (isShowMessage) {
23
63
  // Shutdown mermaid worker pool to ensure clean exit
24
64
  try {
@@ -39,6 +79,8 @@ export default async function saveDoc({
39
79
  locale,
40
80
  feedback,
41
81
  isShowMessage,
82
+ intentType,
83
+ originalContent,
42
84
  ...rest,
43
85
  };
44
86
  }
@@ -2,10 +2,14 @@ import { join } from "node:path";
2
2
  import fs from "fs-extra";
3
3
  import { buildDocumentTree } from "../../utils/docs-finder-utils.mjs";
4
4
 
5
- export default async function saveSidebar({ documentStructure, docsDir }) {
5
+ export default async function saveSidebar({
6
+ documentStructure,
7
+ originalDocumentStructure,
8
+ docsDir,
9
+ }) {
6
10
  // Generate _sidebar.md
7
11
  try {
8
- const sidebar = generateSidebar(documentStructure);
12
+ const sidebar = generateSidebar(documentStructure || originalDocumentStructure);
9
13
  const sidebarPath = join(docsDir, "_sidebar.md");
10
14
 
11
15
  await fs.ensureDir(docsDir);
@@ -16,14 +20,23 @@ export default async function saveSidebar({ documentStructure, docsDir }) {
16
20
  return {};
17
21
  }
18
22
 
19
- // Recursively generate sidebar text, the link path is the flattened file name
23
+ // Recursively generate sidebar text
20
24
  function walk(nodes, indent = "") {
21
25
  let out = "";
22
26
  for (const node of nodes) {
23
- const relPath = node.path.replace(/^\//, "");
24
- const flatFile = `${relPath.split("/").join("-")}.md`;
25
27
  const realIndent = node.parentId === null ? "" : indent;
26
- out += `${realIndent}* [${node.title}](/${flatFile})\n`;
28
+
29
+ // If path already ends with .md, use it directly
30
+ let linkPath;
31
+ if (node.path.endsWith(".md")) {
32
+ linkPath = node.path.startsWith("/") ? node.path : `/${node.path}`;
33
+ } else {
34
+ // Otherwise, convert to flattened file name
35
+ const relPath = node.path.replace(/^\//, "");
36
+ linkPath = `/${relPath.split("/").join("-")}.md`;
37
+ }
38
+
39
+ out += `${realIndent}* [${node.title}](${linkPath})\n`;
27
40
 
28
41
  if (node.children && node.children.length > 0) {
29
42
  out += walk(node.children, `${indent} `);
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Determine whether to skip document generation when the document already exists.
3
+ * If intentType is diagram-related *and* content is already present, mark that
4
+ * generation should be skipped so downstream agents can short-circuit.
5
+ */
6
+ export default async function skipIfContentExists(input) {
7
+ const { intentType, content } = input;
8
+
9
+ const isDiagramIntent =
10
+ intentType && ["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType);
11
+ const shouldSkipGeneration = Boolean(isDiagramIntent && content);
12
+
13
+ return {
14
+ ...input,
15
+ skipGenerateDocument: shouldSkipGeneration,
16
+ // Ensure downstream steps have content available when skipping
17
+ ...(shouldSkipGeneration
18
+ ? {
19
+ content,
20
+ documentContent: content,
21
+ originalContent: content,
22
+ }
23
+ : {}),
24
+ };
25
+ }
26
+
27
+ skipIfContentExists.task_render_mode = "hide";
package/aigne.yaml CHANGED
@@ -1,13 +1,18 @@
1
1
  #!/usr/bin/env aigne
2
2
 
3
3
  model:
4
- # model: gemini-3-pro-preview
5
- model: gemini-2.5-pro
4
+ model: gemini-3-pro-preview
5
+ # model: gemini-2.5-pro
6
6
  temperature: 0.8
7
7
  # https://github.com/AIGNE-io/aigne-framework/blob/main/models/gemini/src/gemini-chat-model.ts#L115
8
8
  reasoning_effort:
9
9
  $get: reasoningEffort
10
10
  agents:
11
+ # - ./_exp_/index.yaml
12
+ # - ./_exp_/generateStructureOrchestrator.yaml
13
+
14
+ - ./agentic-agents/create/index.yaml
15
+
11
16
  # Initialization
12
17
  - ./agents/init/index.mjs
13
18
 
@@ -19,6 +24,7 @@ agents:
19
24
  - ./agents/create/check-document-structure.yaml
20
25
  - ./agents/create/user-review-document-structure.mjs
21
26
  - ./agents/create/index.yaml
27
+ - ./agentic-agents/structure/index.yaml
22
28
 
23
29
  # Documentation Structure Tools
24
30
  - ./agents/create/document-structure-tools/add-document.mjs
@@ -105,10 +111,15 @@ agents:
105
111
  - ./agents/evaluate/code-snippet.mjs
106
112
 
107
113
  # Diagram
108
- - ./agents/create/merge-diagram.yaml
114
+ - ./agents/create/analyze-diagram-type.mjs
115
+ - ./agents/create/analyze-diagram-type-llm.yaml
116
+ - ./agents/create/generate-diagram-image.yaml
117
+ - ./agents/create/replace-d2-with-image.mjs
109
118
  - ./agents/update/generate-diagram.yaml
110
119
  - ./agents/update/check-generate-diagram.mjs
111
120
  - ./agents/update/pre-check-generate-diagram.yaml
121
+ - ./agents/update/check-sync-image-flag.mjs
122
+ - ./agents/update/sync-images-and-exit.mjs
112
123
  cli:
113
124
  chat: ./agents/chat/index.mjs
114
125
  agents:
@@ -133,6 +144,7 @@ cli:
133
144
  alias: ["remove", "rm"]
134
145
  url: ./agents/create/user-remove-document/index.yaml
135
146
  - ./agents/clear/index.yaml
147
+ - ./agentic-agents/create/index.yaml
136
148
  mcp_server:
137
149
  agents:
138
150
  - ./docs-mcp/get-docs-structure.mjs