@aigne/doc-smith 0.9.7 → 0.9.8-beta

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 (56) hide show
  1. package/CHANGELOG.md +13 -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/generate-diagram-image.yaml +60 -0
  5. package/agents/create/replace-d2-with-image.mjs +624 -0
  6. package/agents/create/utils/init-current-content.mjs +5 -9
  7. package/agents/evaluate/document.yaml +6 -0
  8. package/agents/evaluate/index.yaml +1 -0
  9. package/agents/init/index.mjs +16 -0
  10. package/agents/media/batch-generate-media-description.yaml +2 -0
  11. package/agents/media/generate-media-description.yaml +3 -0
  12. package/agents/media/load-media-description.mjs +44 -15
  13. package/agents/publish/publish-docs.mjs +1 -4
  14. package/agents/update/check-diagram-flag.mjs +116 -0
  15. package/agents/update/check-document.mjs +0 -1
  16. package/agents/update/check-generate-diagram.mjs +48 -30
  17. package/agents/update/check-sync-image-flag.mjs +55 -0
  18. package/agents/update/check-update-is-single.mjs +11 -0
  19. package/agents/update/generate-diagram.yaml +43 -9
  20. package/agents/update/generate-document.yaml +9 -0
  21. package/agents/update/handle-document-update.yaml +10 -8
  22. package/agents/update/index.yaml +16 -1
  23. package/agents/update/sync-images-and-exit.mjs +148 -0
  24. package/agents/update/update-single/update-single-document-detail.mjs +131 -17
  25. package/agents/utils/analyze-feedback-intent.mjs +136 -0
  26. package/agents/utils/choose-docs.mjs +183 -40
  27. package/agents/utils/generate-document-or-skip.mjs +41 -0
  28. package/agents/utils/handle-diagram-operations.mjs +263 -0
  29. package/agents/utils/load-all-document-content.mjs +30 -0
  30. package/agents/utils/load-sources.mjs +2 -2
  31. package/agents/utils/read-current-document-content.mjs +46 -0
  32. package/agents/utils/save-doc.mjs +42 -0
  33. package/agents/utils/skip-if-content-exists.mjs +27 -0
  34. package/aigne.yaml +6 -1
  35. package/assets/report-template/report.html +17 -17
  36. package/docs-mcp/read-doc-content.mjs +30 -1
  37. package/package.json +4 -4
  38. package/prompts/detail/diagram/generate-image-system.md +135 -0
  39. package/prompts/detail/diagram/generate-image-user.md +32 -0
  40. package/prompts/detail/generate/user-prompt.md +27 -13
  41. package/prompts/evaluate/document.md +23 -10
  42. package/prompts/media/media-description/system-prompt.md +10 -2
  43. package/prompts/media/media-description/user-prompt.md +9 -0
  44. package/utils/check-document-has-diagram.mjs +97 -0
  45. package/utils/constants/index.mjs +46 -0
  46. package/utils/d2-utils.mjs +114 -181
  47. package/utils/delete-diagram-images.mjs +103 -0
  48. package/utils/docs-finder-utils.mjs +34 -1
  49. package/utils/image-compress.mjs +75 -0
  50. package/utils/kroki-utils.mjs +2 -3
  51. package/utils/sync-diagram-to-translations.mjs +258 -0
  52. package/utils/utils.mjs +24 -0
  53. package/agents/create/check-diagram.mjs +0 -40
  54. package/agents/create/draw-diagram.yaml +0 -27
  55. package/agents/create/merge-diagram.yaml +0 -39
  56. package/agents/create/wrap-diagram-code.mjs +0 -35
@@ -5,7 +5,15 @@ import {
5
5
  getActionText,
6
6
  getMainLanguageFiles,
7
7
  processSelectedFiles,
8
+ readFileContent,
8
9
  } from "../../utils/docs-finder-utils.mjs";
10
+ import {
11
+ hasDiagramContent,
12
+ hasBananaImages,
13
+ getDiagramTypeLabels,
14
+ formatDiagramTypeSuffix,
15
+ } from "../../utils/check-document-has-diagram.mjs";
16
+ import { debug } from "../../utils/debug.mjs";
9
17
  import { DOC_ACTION } from "../../utils/constants/index.mjs";
10
18
 
11
19
  function getFeedbackMessage(action) {
@@ -27,6 +35,9 @@ export default async function chooseDocs(
27
35
  reset = false,
28
36
  requiredFeedback = true,
29
37
  action,
38
+ shouldUpdateDiagrams = false,
39
+ shouldAutoSelectDiagrams = false,
40
+ shouldSyncImages = false,
30
41
  },
31
42
  options,
32
43
  ) {
@@ -48,55 +59,181 @@ export default async function chooseDocs(
48
59
  );
49
60
  }
50
61
 
51
- // Convert files to choices with titles
52
- const choices = mainLanguageFiles.map((file) => {
53
- // Convert filename to flat path to find corresponding documentation structure item
54
- const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
55
- const docItem = documentStructure.find((item) => {
56
- const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
57
- return itemFlattenedPath === flatName;
58
- });
62
+ // If --diagram-sync flag is set, filter documents by banana images only
63
+ if (shouldSyncImages) {
64
+ debug("🔄 Filtering documents with banana images...");
59
65
 
60
- // Use title if available, otherwise fall back to filename
61
- let displayName = docItem?.title;
62
- if (displayName) {
63
- displayName = `${displayName} (${file})`;
64
- } else {
65
- displayName = file;
66
+ // Read content for all files and filter by banana images only
67
+ const filesWithImages = [];
68
+ for (const fileName of mainLanguageFiles) {
69
+ const content = await readFileContent(docsDir, fileName);
70
+ if (content && hasBananaImages(content)) {
71
+ filesWithImages.push(fileName);
72
+ }
66
73
  }
67
74
 
68
- return {
69
- name: displayName,
70
- value: file,
71
- };
72
- });
75
+ if (filesWithImages.length === 0) {
76
+ debug("ℹ️ No documents found with banana images (DIAGRAM_IMAGE_START markers).");
77
+ return {
78
+ selectedDocs: [],
79
+ feedback: "",
80
+ selectedPaths: [],
81
+ };
82
+ }
83
+
84
+ debug(`✅ Found ${filesWithImages.length} document(s) with banana images.`);
85
+ debug("📋 Auto-selecting all documents with banana images...");
86
+ // Show diagram types for each document
87
+ for (const file of filesWithImages) {
88
+ const content = await readFileContent(docsDir, file);
89
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
90
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
91
+ debug(` • ${file}${diagramSuffix}`);
92
+ }
93
+ selectedFiles = filesWithImages;
94
+ }
95
+ // If --diagram flag is set, filter documents by diagram content
96
+ else if (shouldUpdateDiagrams) {
97
+ debug("🔄 Filtering documents with diagram content...");
73
98
 
74
- // Let user select multiple files
75
- selectedFiles = await options.prompts.checkbox({
76
- message: getActionText("Select documents to {action}:", docAction),
77
- source: (term) => {
78
- if (!term) return choices;
79
-
80
- return choices.filter((choice) => choice.name.toLowerCase().includes(term.toLowerCase()));
81
- },
82
- validate: (answer) => {
83
- if (answer.length === 0) {
84
- return "Please select at least one document";
99
+ // Read content for all files and filter by diagram content
100
+ const filesWithDiagrams = [];
101
+ for (const fileName of mainLanguageFiles) {
102
+ const content = await readFileContent(docsDir, fileName);
103
+ if (content && hasDiagramContent(content)) {
104
+ filesWithDiagrams.push(fileName);
85
105
  }
86
- return true;
87
- },
88
- });
106
+ }
107
+
108
+ if (filesWithDiagrams.length === 0) {
109
+ debug(
110
+ "ℹ️ No documents found with diagram content (d2 code blocks, placeholders, or diagram images).",
111
+ );
112
+ return {
113
+ selectedDocs: [],
114
+ feedback: "",
115
+ selectedPaths: [],
116
+ };
117
+ }
118
+
119
+ debug(`✅ Found ${filesWithDiagrams.length} document(s) with diagram content.`);
120
+
121
+ // If --diagram-all, auto-select all; otherwise let user choose
122
+ if (shouldAutoSelectDiagrams) {
123
+ debug("📋 Auto-selecting all documents with diagrams...");
124
+ // Show diagram types for each document in auto-select mode
125
+ for (const file of filesWithDiagrams) {
126
+ const content = await readFileContent(docsDir, file);
127
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
128
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
129
+ debug(` • ${file}${diagramSuffix}`);
130
+ }
131
+ selectedFiles = filesWithDiagrams;
132
+ } else {
133
+ // --diagram mode: show only documents with diagrams, let user select
134
+ const choices = await Promise.all(
135
+ filesWithDiagrams.map(async (file) => {
136
+ // Convert filename to flat path to find corresponding documentation structure item
137
+ const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
138
+ const docItem = documentStructure.find((item) => {
139
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
140
+ return itemFlattenedPath === flatName;
141
+ });
89
142
 
90
- if (!selectedFiles || selectedFiles.length === 0) {
91
- throw new Error("No documents selected");
143
+ // Read content to detect diagram types
144
+ const content = await readFileContent(docsDir, file);
145
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
146
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
147
+
148
+ // Use title if available, otherwise fall back to filename
149
+ let displayName = docItem?.title;
150
+ if (displayName) {
151
+ displayName = `${displayName} (${file})${diagramSuffix}`;
152
+ } else {
153
+ displayName = `${file}${diagramSuffix}`;
154
+ }
155
+
156
+ return {
157
+ name: displayName,
158
+ value: file,
159
+ };
160
+ }),
161
+ );
162
+
163
+ // Let user select multiple files from filtered list
164
+ selectedFiles = await options.prompts.checkbox({
165
+ message: getActionText("Select documents with diagrams to {action}:", docAction),
166
+ source: (term) => {
167
+ if (!term) return choices;
168
+
169
+ return choices.filter((choice) =>
170
+ choice.name.toLowerCase().includes(term.toLowerCase()),
171
+ );
172
+ },
173
+ validate: (answer) => {
174
+ if (answer.length === 0) {
175
+ return "Please select at least one document";
176
+ }
177
+ return true;
178
+ },
179
+ });
180
+
181
+ if (!selectedFiles || selectedFiles.length === 0) {
182
+ throw new Error("No documents selected");
183
+ }
184
+ }
185
+ } else {
186
+ // Normal flow: let user select documents from all files
187
+ // Convert files to choices with titles
188
+ const choices = mainLanguageFiles.map((file) => {
189
+ // Convert filename to flat path to find corresponding documentation structure item
190
+ const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
191
+ const docItem = documentStructure.find((item) => {
192
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
193
+ return itemFlattenedPath === flatName;
194
+ });
195
+
196
+ // Use title if available, otherwise fall back to filename
197
+ let displayName = docItem?.title;
198
+ if (displayName) {
199
+ displayName = `${displayName} (${file})`;
200
+ } else {
201
+ displayName = file;
202
+ }
203
+
204
+ return {
205
+ name: displayName,
206
+ value: file,
207
+ };
208
+ });
209
+
210
+ // Let user select multiple files
211
+ selectedFiles = await options.prompts.checkbox({
212
+ message: getActionText("Select documents to {action}:", docAction),
213
+ source: (term) => {
214
+ if (!term) return choices;
215
+
216
+ return choices.filter((choice) =>
217
+ choice.name.toLowerCase().includes(term.toLowerCase()),
218
+ );
219
+ },
220
+ validate: (answer) => {
221
+ if (answer.length === 0) {
222
+ return "Please select at least one document";
223
+ }
224
+ return true;
225
+ },
226
+ });
227
+
228
+ if (!selectedFiles || selectedFiles.length === 0) {
229
+ throw new Error("No documents selected");
230
+ }
92
231
  }
93
232
 
94
233
  // Process selected files and convert to found items
95
234
  foundItems = await processSelectedFiles(selectedFiles, documentStructure, docsDir);
96
235
  } catch (error) {
97
- console.log(
98
- getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction),
99
- );
236
+ debug(getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction));
100
237
  process.exit(0);
101
238
  }
102
239
  } else {
@@ -105,7 +242,7 @@ export default async function chooseDocs(
105
242
  const foundItem = await findItemByPath(documentStructure, docPath, boardId, docsDir, locale);
106
243
 
107
244
  if (!foundItem) {
108
- console.warn(`⚠️ Item with path "${docPath}" not found in documentStructure`);
245
+ debug(`⚠️ Item with path "${docPath}" not found in documentStructure`);
109
246
  continue;
110
247
  }
111
248
 
@@ -120,8 +257,14 @@ export default async function chooseDocs(
120
257
  }
121
258
 
122
259
  // Prompt for feedback if not provided
260
+ // Skip feedback prompt if --diagram, --diagram-all, or --diagram-sync flag is set
123
261
  let userFeedback = feedback;
124
- if (!userFeedback && (requiredFeedback || foundItems?.length > 1)) {
262
+ if (
263
+ !userFeedback &&
264
+ (requiredFeedback || foundItems?.length > 1) &&
265
+ !shouldUpdateDiagrams &&
266
+ !shouldSyncImages
267
+ ) {
125
268
  const feedbackMessage = getFeedbackMessage(docAction);
126
269
 
127
270
  userFeedback = await options.prompts.input({
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Conditionally call generateDocument. If we already have content for diagram-only intents,
3
+ * skip LLM generation and pass through the existing content.
4
+ */
5
+ export default async function generateDocumentOrSkip(input, options) {
6
+ const { intentType, content, skipGenerateDocument } = input;
7
+
8
+ const isDiagramIntent =
9
+ intentType && ["addDiagram", "updateDiagram", "deleteDiagram"].includes(intentType);
10
+ const shouldSkip = Boolean(skipGenerateDocument || (isDiagramIntent && content));
11
+
12
+ if (shouldSkip) {
13
+ // Return the existing content and mark the generation as skipped
14
+ return {
15
+ ...input,
16
+ content,
17
+ documentContent: content,
18
+ originalContent: content,
19
+ reviewContent: content,
20
+ };
21
+ }
22
+
23
+ const generateAgent = options.context?.agents?.["generateDocument"];
24
+ if (!generateAgent) {
25
+ throw new Error("generateDocument agent not found");
26
+ }
27
+
28
+ const result = await options.context.invoke(generateAgent, input);
29
+ const generatedContent = result?.content ?? result;
30
+
31
+ return {
32
+ ...input,
33
+ ...result,
34
+ content: generatedContent,
35
+ documentContent: generatedContent,
36
+ originalContent: generatedContent,
37
+ reviewContent: generatedContent,
38
+ };
39
+ }
40
+
41
+ generateDocumentOrSkip.task_render_mode = "hide";
@@ -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)`,
@@ -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";