@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
@@ -4,18 +4,72 @@ import z from "zod";
4
4
  import {
5
5
  DIAGRAM_PLACEHOLDER,
6
6
  replaceD2WithPlaceholder,
7
- replacePlaceholderWithD2,
7
+ replaceDiagramsWithPlaceholder,
8
8
  } from "../../../utils/d2-utils.mjs";
9
9
  import { userContextAt } from "../../../utils/utils.mjs";
10
10
 
11
11
  async function getIntentType(input, options) {
12
12
  const instructions = `<role>
13
13
  You are a feedback intent analyzer. Your task is to determine which type of content modifications are needed based on the user's feedback.
14
+
15
+ You must analyze the user's feedback and classify it into one of the following intent types:
16
+ - addDiagram: User wants to add a new diagram/image/chart
17
+ - deleteDiagram: User wants to remove/delete a diagram/image/chart
18
+ - updateDiagram: User wants to modify/update an existing diagram/image/chart
19
+ - updateDocument: User wants to update document content (text, sections, etc.) without diagram operations
14
20
  </role>
15
21
 
22
+ <intent_classification_rules>
23
+ **deleteDiagram** - Use this when user explicitly wants to remove or delete a diagram/image/chart:
24
+ - Keywords: remove, delete, 删除, 移除, 去掉, 清除
25
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图, 架构图
26
+ - Examples:
27
+ - "Remove the diagram"
28
+ - "Delete the image"
29
+ - "删除这张图片"
30
+ - "Remove the second diagram"
31
+ - "去掉架构图"
32
+ - "Remove image from page 3"
33
+ - "Delete the chart showing the flow"
34
+
35
+ **addDiagram** - Use this when user wants to add a new diagram:
36
+ - Keywords: add, create, insert, 添加, 创建, 插入
37
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
38
+ - Examples:
39
+ - "Add a diagram showing the architecture"
40
+ - "Create a flow chart"
41
+ - "添加一个架构图"
42
+
43
+ **updateDiagram** - Use this when user wants to modify an existing diagram:
44
+ - Keywords: update, modify, change, improve, 更新, 修改, 改进
45
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
46
+ - Examples:
47
+ - "Update the diagram to show the new process"
48
+ - "Modify the chart to include more details"
49
+ - "更新架构图"
50
+
51
+ **updateDocument** - Use this for all other content modifications:
52
+ - Text changes, section updates, content improvements
53
+ - No mention of diagrams/images/charts
54
+ - Examples:
55
+ - "Update the introduction section"
56
+ - "Fix the typo in paragraph 2"
57
+ - "Improve the explanation"
58
+ </intent_classification_rules>
59
+
16
60
  <user_feedback>
17
61
  {{feedback}}
18
- </user_feedback>`;
62
+ </user_feedback>
63
+
64
+ <analysis_guidelines>
65
+ 1. Pay close attention to action verbs (remove, delete, add, update, etc.)
66
+ 2. Identify the target object (diagram, image, chart, or general content)
67
+ 3. If feedback mentions removing/deleting a diagram/image/chart → deleteDiagram
68
+ 4. If feedback mentions adding a diagram/image/chart → addDiagram
69
+ 5. If feedback mentions updating a diagram/image/chart → updateDiagram
70
+ 6. If feedback is about general content without diagram references → updateDocument
71
+ 7. When in doubt, prioritize the most explicit action mentioned in the feedback
72
+ </analysis_guidelines>`;
19
73
  const analyzeUpdateFeedbackIntentAgent = AIAgent.from({
20
74
  name: "analyzeUpdateFeedbackIntent",
21
75
  description:
@@ -43,11 +97,12 @@ You are a feedback intent analyzer. Your task is to determine which type of cont
43
97
  return intentType;
44
98
  }
45
99
 
46
- async function saveDoc(input, options, { content }) {
100
+ async function saveDoc(input, options, { content, intentType }) {
47
101
  const saveAgent = options.context?.agents?.["saveDoc"];
48
102
  await options.context.invoke(saveAgent, {
49
103
  ...pick(input, ["path", "docsDir", "labels", "locale"]),
50
104
  content,
105
+ intentType, // Pass intentType so saveDoc can handle translation sync
51
106
  });
52
107
  }
53
108
 
@@ -56,42 +111,70 @@ async function addDiagram(input, options) {
56
111
  const currentContent = contentContext.get();
57
112
  const generateDiagramAgent = options.context.agents["checkGenerateDiagram"];
58
113
  const generateDiagramResult = await options.context.invoke(generateDiagramAgent, {
59
- ...pick(input, ["locale", "path", "diagramming", "feedback"]),
114
+ ...pick(input, ["locale", "path", "docsDir", "diagramming", "feedback"]),
60
115
  documentContent: currentContent,
116
+ originalContent: currentContent,
61
117
  });
62
118
  const content = generateDiagramResult.content;
63
119
  contentContext.set(content);
64
- await saveDoc(input, options, { content });
120
+ // Pass intentType to saveDoc so it can handle translation sync automatically
121
+ await saveDoc(input, options, { content, intentType: "addDiagram" });
65
122
  return { content };
66
123
  }
67
124
 
68
125
  async function updateDiagram(input, options) {
69
126
  const contentContext = userContextAt(options, `currentContents.${input.path}`);
70
127
  const currentContent = contentContext.get();
71
- let [content, previousDiagramContent] = replaceD2WithPlaceholder({
128
+ let [content] = replaceD2WithPlaceholder({
72
129
  content: currentContent,
73
130
  });
74
131
  const generateAgent = options.context?.agents?.["generateDiagram"];
75
- const { diagramSourceCode } = await options.context.invoke(generateAgent, {
132
+ const result = await options.context.invoke(generateAgent, {
76
133
  documentContent: content,
77
134
  locale: input.locale,
78
- previousDiagramContent,
135
+ diagramming: input.diagramming || {},
79
136
  feedback: input.feedback,
137
+ originalContent: currentContent, // Pass original content to find existing diagrams
138
+ path: input.path,
139
+ docsDir: input.docsDir,
80
140
  });
81
- content = replacePlaceholderWithD2({
82
- content,
83
- diagramSourceCode,
84
- });
141
+
142
+ // generateDiagram now returns { content } with image already inserted
143
+ // The image replaces DIAGRAM_PLACEHOLDER or D2 code blocks
144
+ if (result?.content) {
145
+ content = result.content;
146
+ }
147
+
85
148
  contentContext.set(content);
86
- await saveDoc(input, options, { content });
149
+ // Pass intentType to saveDoc so it can handle translation sync automatically
150
+ await saveDoc(input, options, { content, intentType: "updateDiagram" });
87
151
  return { content };
88
152
  }
89
153
 
90
154
  async function deleteDiagram(input, options) {
91
155
  const contentContext = userContextAt(options, `currentContents.${input.path}`);
92
156
  const currentContent = contentContext.get();
93
- const [documentContent] = replaceD2WithPlaceholder({
157
+
158
+ // Extract diagram index from feedback if provided
159
+ // This allows deleting a specific diagram when multiple diagrams exist
160
+ let diagramIndex = input.diagramIndex;
161
+ if (diagramIndex === undefined && input.feedback) {
162
+ // Import extractDiagramIndexFromFeedback from replace-d2-with-image.mjs
163
+ const { extractDiagramIndexFromFeedback } = await import(
164
+ "../../create/replace-d2-with-image.mjs"
165
+ );
166
+ const extractedIndex = extractDiagramIndexFromFeedback(input.feedback);
167
+ if (extractedIndex !== null) {
168
+ diagramIndex = extractedIndex;
169
+ }
170
+ }
171
+
172
+ // Replace all diagrams (D2 code blocks, generated images, Mermaid) with placeholder
173
+ // If diagramIndex is provided, only replace that specific diagram
174
+ // This ensures LLM can identify and remove the diagram regardless of its type
175
+ const documentContent = replaceDiagramsWithPlaceholder({
94
176
  content: currentContent,
177
+ diagramIndex,
95
178
  });
96
179
  const instructions = `<role>
97
180
  Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (based on the user's feedback) to make it easier to understand.
@@ -123,8 +206,24 @@ Your task is to remove ${DIAGRAM_PLACEHOLDER} and adjust the document context (b
123
206
  documentContent,
124
207
  feedback: input.feedback,
125
208
  });
209
+
210
+ // Delete associated diagram image files
211
+ if (input.docsDir) {
212
+ try {
213
+ const { deleteDiagramImages } = await import("../../../utils/delete-diagram-images.mjs");
214
+ const { deleted } = await deleteDiagramImages(currentContent, input.path, input.docsDir);
215
+ if (deleted > 0) {
216
+ console.log(`Deleted ${deleted} diagram image file(s) for ${input.path}`);
217
+ }
218
+ } catch (error) {
219
+ // Don't fail the operation if image deletion fails
220
+ console.warn(`Failed to delete diagram images: ${error.message}`);
221
+ }
222
+ }
223
+
126
224
  contentContext.set(content);
127
- await saveDoc(input, options, { content });
225
+ // Pass intentType to saveDoc so it can handle translation sync automatically
226
+ await saveDoc(input, options, { content, intentType: "deleteDiagram" });
128
227
 
129
228
  return { content };
130
229
  }
@@ -149,7 +248,19 @@ async function updateDocument(input, options) {
149
248
  }
150
249
 
151
250
  export default async function updateSingleDocumentDetail(input, options) {
152
- const intentType = await getIntentType(input, options);
251
+ // Use intentType from input if available (analyzed in parent flow),
252
+ // otherwise fall back to analyzing it here (for backward compatibility)
253
+ let intentType = input.intentType;
254
+ if (!intentType && input.feedback) {
255
+ intentType = await getIntentType(input, options);
256
+ }
257
+
258
+ // If intentType is still null or undefined, default to updateDocument
259
+ // This ensures that some operation is always performed, even if intent analysis failed
260
+ // or no explicit intent was provided
261
+ if (!intentType) {
262
+ intentType = "updateDocument";
263
+ }
153
264
 
154
265
  const fnMap = {
155
266
  addDiagram,
@@ -162,5 +273,8 @@ export default async function updateSingleDocumentDetail(input, options) {
162
273
  const result = await fnMap[intentType](input, options);
163
274
  return result;
164
275
  }
165
- return {};
276
+
277
+ // Fallback: if intentType is not in fnMap, default to updateDocument
278
+ console.warn(`Unknown intentType: ${intentType}, defaulting to updateDocument`);
279
+ return await updateDocument(input, options);
166
280
  }
@@ -0,0 +1,136 @@
1
+ import { AIAgent } from "@aigne/core";
2
+ import z from "zod";
3
+
4
+ /**
5
+ * Analyze user feedback to determine the intent type for document updates
6
+ * Returns one of: "addDiagram", "deleteDiagram", "updateDiagram", "updateDocument"
7
+ * Returns null if feedback is empty or invalid
8
+ */
9
+ export default async function analyzeFeedbackIntent({ feedback, shouldUpdateDiagrams }, options) {
10
+ // Check if feedback exists and is not empty
11
+ // If feedback is empty and --diagram flag is set, default to updateDiagram
12
+ // Otherwise return null
13
+ if (!feedback || typeof feedback !== "string" || !feedback.trim()) {
14
+ // If --diagram flag is set, default to updateDiagram (user wants to update diagrams)
15
+ if (shouldUpdateDiagrams) {
16
+ return { intentType: "updateDiagram" };
17
+ }
18
+ return { intentType: null };
19
+ }
20
+
21
+ // Always analyze user feedback first, even if --diagram flag is set
22
+ // This ensures user's explicit intent (e.g., "remove image", "delete diagram") is respected
23
+ // The --diagram flag should only be a hint, not override explicit user commands
24
+
25
+ const instructions = `<role>
26
+ You are a feedback intent analyzer. Your task is to determine which type of content modifications are needed based on the user's feedback.
27
+
28
+ You must analyze the user's feedback and classify it into one of the following intent types:
29
+ - addDiagram: User wants to add a new diagram/image/chart
30
+ - deleteDiagram: User wants to remove/delete a diagram/image/chart
31
+ - updateDiagram: User wants to modify/update an existing diagram/image/chart
32
+ - updateDocument: User wants to update document content (text, sections, etc.) without diagram operations
33
+ </role>
34
+
35
+ <intent_classification_rules>
36
+ **deleteDiagram** - Use this when user explicitly wants to remove or delete a diagram/image/chart:
37
+ - Keywords: remove, delete, 删除, 移除, 去掉, 清除
38
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图, 架构图
39
+ - Examples:
40
+ - "Remove the diagram"
41
+ - "Delete the image"
42
+ - "删除这张图片"
43
+ - "Remove the second diagram"
44
+ - "去掉架构图"
45
+ - "Remove image from page 3"
46
+ - "Delete the chart showing the flow"
47
+
48
+ **addDiagram** - Use this when user wants to add a new diagram:
49
+ - Keywords: add, create, insert, 添加, 创建, 插入
50
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
51
+ - Examples:
52
+ - "Add a diagram showing the architecture"
53
+ - "Create a flow chart"
54
+ - "添加一个架构图"
55
+
56
+ **updateDiagram** - Use this when user wants to modify an existing diagram:
57
+ - Keywords: update, modify, change, improve, 更新, 修改, 改进
58
+ - Combined with: diagram, image, picture, chart, graph, 图表, 图片, 图
59
+ - Examples:
60
+ - "Update the diagram to show the new process"
61
+ - "Modify the chart to include more details"
62
+ - "更新架构图"
63
+
64
+ **updateDocument** - Use this for all other content modifications:
65
+ - Text changes, section updates, content improvements
66
+ - No mention of diagrams/images/charts
67
+ - Examples:
68
+ - "Update the introduction section"
69
+ - "Fix the typo in paragraph 2"
70
+ - "Improve the explanation"
71
+ </intent_classification_rules>
72
+
73
+ <user_feedback>
74
+ {{feedback}}
75
+ </user_feedback>
76
+
77
+ <analysis_guidelines>
78
+ 1. Pay close attention to action verbs (remove, delete, add, update, etc.)
79
+ 2. Identify the target object (diagram, image, chart, or general content)
80
+ 3. If feedback mentions removing/deleting a diagram/image/chart → deleteDiagram
81
+ 4. If feedback mentions adding a diagram/image/chart → addDiagram
82
+ 5. If feedback mentions updating a diagram/image/chart → updateDiagram
83
+ 6. If feedback is about general content without diagram references → updateDocument
84
+ 7. When in doubt, prioritize the most explicit action mentioned in the feedback
85
+ </analysis_guidelines>`;
86
+
87
+ try {
88
+ const analyzeUpdateFeedbackIntentAgent = AIAgent.from({
89
+ name: "analyzeUpdateFeedbackIntent",
90
+ description:
91
+ "Analyze user feedback to determine if document are needed for content modifications",
92
+ task_render_mode: "hide",
93
+ instructions,
94
+ inputSchema: z.object({
95
+ feedback: z.string().describe("User feedback for content modifications"),
96
+ }),
97
+ outputSchema: z.object({
98
+ intentType: z
99
+ .enum(["addDiagram", "deleteDiagram", "updateDiagram", "updateDocument"])
100
+ .describe(
101
+ "The primary type of user intention: one of addDiagram, deleteDiagram, updateDiagram, updateDocument",
102
+ ),
103
+ }),
104
+ });
105
+
106
+ const { intentType } = await options.context.invoke(analyzeUpdateFeedbackIntentAgent, {
107
+ feedback: feedback.trim(),
108
+ });
109
+
110
+ // If --diagram flag is set and user didn't explicitly request delete/add,
111
+ // default to updateDiagram (for backward compatibility)
112
+ // But if user explicitly requested delete/add, respect that intent
113
+ if (
114
+ shouldUpdateDiagrams &&
115
+ intentType &&
116
+ !["deleteDiagram", "addDiagram"].includes(intentType)
117
+ ) {
118
+ return { intentType: "updateDiagram" };
119
+ }
120
+
121
+ return { intentType };
122
+ } catch (error) {
123
+ // If analysis fails and --diagram flag is set, default to updateDiagram
124
+ // Otherwise return null to fall back to default document update flow
125
+ if (shouldUpdateDiagrams) {
126
+ console.warn(
127
+ `Failed to analyze feedback intent, defaulting to updateDiagram due to --diagram flag: ${error.message}`,
128
+ );
129
+ return { intentType: "updateDiagram" };
130
+ }
131
+ console.warn(`Failed to analyze feedback intent: ${error.message}`);
132
+ return { intentType: null };
133
+ }
134
+ }
135
+
136
+ analyzeFeedbackIntent.task_render_mode = "hide";
@@ -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,10 @@ 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,
41
+ ...rest
30
42
  },
31
43
  options,
32
44
  ) {
@@ -48,55 +60,181 @@ export default async function chooseDocs(
48
60
  );
49
61
  }
50
62
 
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
- });
63
+ // If --diagram-sync flag is set, filter documents by banana images only
64
+ if (shouldSyncImages) {
65
+ debug("🔄 Filtering documents with banana images...");
59
66
 
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;
67
+ // Read content for all files and filter by banana images only
68
+ const filesWithImages = [];
69
+ for (const fileName of mainLanguageFiles) {
70
+ const content = await readFileContent(docsDir, fileName);
71
+ if (content && hasBananaImages(content)) {
72
+ filesWithImages.push(fileName);
73
+ }
66
74
  }
67
75
 
68
- return {
69
- name: displayName,
70
- value: file,
71
- };
72
- });
76
+ if (filesWithImages.length === 0) {
77
+ debug("ℹ️ No documents found with banana images (DIAGRAM_IMAGE_START markers).");
78
+ return {
79
+ selectedDocs: [],
80
+ feedback: "",
81
+ selectedPaths: [],
82
+ };
83
+ }
84
+
85
+ debug(`✅ Found ${filesWithImages.length} document(s) with banana images.`);
86
+ debug("📋 Auto-selecting all documents with banana images...");
87
+ // Show diagram types for each document
88
+ for (const file of filesWithImages) {
89
+ const content = await readFileContent(docsDir, file);
90
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
91
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
92
+ debug(` • ${file}${diagramSuffix}`);
93
+ }
94
+ selectedFiles = filesWithImages;
95
+ }
96
+ // If --diagram flag is set, filter documents by diagram content
97
+ else if (shouldUpdateDiagrams) {
98
+ debug("🔄 Filtering documents with diagram content...");
73
99
 
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";
100
+ // Read content for all files and filter by diagram content
101
+ const filesWithDiagrams = [];
102
+ for (const fileName of mainLanguageFiles) {
103
+ const content = await readFileContent(docsDir, fileName);
104
+ if (content && hasDiagramContent(content)) {
105
+ filesWithDiagrams.push(fileName);
85
106
  }
86
- return true;
87
- },
88
- });
107
+ }
108
+
109
+ if (filesWithDiagrams.length === 0) {
110
+ debug(
111
+ "ℹ️ No documents found with diagram content (d2 code blocks, placeholders, or diagram images).",
112
+ );
113
+ return {
114
+ selectedDocs: [],
115
+ feedback: "",
116
+ selectedPaths: [],
117
+ };
118
+ }
119
+
120
+ debug(`✅ Found ${filesWithDiagrams.length} document(s) with diagram content.`);
121
+
122
+ // If --diagram-all, auto-select all; otherwise let user choose
123
+ if (shouldAutoSelectDiagrams) {
124
+ debug("📋 Auto-selecting all documents with diagrams...");
125
+ // Show diagram types for each document in auto-select mode
126
+ for (const file of filesWithDiagrams) {
127
+ const content = await readFileContent(docsDir, file);
128
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
129
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
130
+ debug(` • ${file}${diagramSuffix}`);
131
+ }
132
+ selectedFiles = filesWithDiagrams;
133
+ } else {
134
+ // --diagram mode: show only documents with diagrams, let user select
135
+ const choices = await Promise.all(
136
+ filesWithDiagrams.map(async (file) => {
137
+ // Convert filename to flat path to find corresponding documentation structure item
138
+ const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
139
+ const docItem = documentStructure.find((item) => {
140
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
141
+ return itemFlattenedPath === flatName;
142
+ });
89
143
 
90
- if (!selectedFiles || selectedFiles.length === 0) {
91
- throw new Error("No documents selected");
144
+ // Read content to detect diagram types
145
+ const content = await readFileContent(docsDir, file);
146
+ const diagramLabels = content ? getDiagramTypeLabels(content) : [];
147
+ const diagramSuffix = formatDiagramTypeSuffix(diagramLabels);
148
+
149
+ // Use title if available, otherwise fall back to filename
150
+ let displayName = docItem?.title;
151
+ if (displayName) {
152
+ displayName = `${displayName} (${file})${diagramSuffix}`;
153
+ } else {
154
+ displayName = `${file}${diagramSuffix}`;
155
+ }
156
+
157
+ return {
158
+ name: displayName,
159
+ value: file,
160
+ };
161
+ }),
162
+ );
163
+
164
+ // Let user select multiple files from filtered list
165
+ selectedFiles = await options.prompts.checkbox({
166
+ message: getActionText("Select documents with diagrams to {action}:", docAction),
167
+ source: (term) => {
168
+ if (!term) return choices;
169
+
170
+ return choices.filter((choice) =>
171
+ choice.name.toLowerCase().includes(term.toLowerCase()),
172
+ );
173
+ },
174
+ validate: (answer) => {
175
+ if (answer.length === 0) {
176
+ return "Please select at least one document";
177
+ }
178
+ return true;
179
+ },
180
+ });
181
+
182
+ if (!selectedFiles || selectedFiles.length === 0) {
183
+ throw new Error("No documents selected");
184
+ }
185
+ }
186
+ } else {
187
+ // Normal flow: let user select documents from all files
188
+ // Convert files to choices with titles
189
+ const choices = mainLanguageFiles.map((file) => {
190
+ // Convert filename to flat path to find corresponding documentation structure item
191
+ const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
192
+ const docItem = documentStructure.find((item) => {
193
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
194
+ return itemFlattenedPath === flatName;
195
+ });
196
+
197
+ // Use title if available, otherwise fall back to filename
198
+ let displayName = docItem?.title;
199
+ if (displayName) {
200
+ displayName = `${displayName} (${file})`;
201
+ } else {
202
+ displayName = file;
203
+ }
204
+
205
+ return {
206
+ name: displayName,
207
+ value: file,
208
+ };
209
+ });
210
+
211
+ // Let user select multiple files
212
+ selectedFiles = await options.prompts.checkbox({
213
+ message: getActionText("Select documents to {action}:", docAction),
214
+ source: (term) => {
215
+ if (!term) return choices;
216
+
217
+ return choices.filter((choice) =>
218
+ choice.name.toLowerCase().includes(term.toLowerCase()),
219
+ );
220
+ },
221
+ validate: (answer) => {
222
+ if (answer.length === 0) {
223
+ return "Please select at least one document";
224
+ }
225
+ return true;
226
+ },
227
+ });
228
+
229
+ if (!selectedFiles || selectedFiles.length === 0) {
230
+ throw new Error("No documents selected");
231
+ }
92
232
  }
93
233
 
94
234
  // Process selected files and convert to found items
95
235
  foundItems = await processSelectedFiles(selectedFiles, documentStructure, docsDir);
96
236
  } catch (error) {
97
- console.log(
98
- getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction),
99
- );
237
+ debug(getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction));
100
238
  process.exit(0);
101
239
  }
102
240
  } else {
@@ -105,7 +243,7 @@ export default async function chooseDocs(
105
243
  const foundItem = await findItemByPath(documentStructure, docPath, boardId, docsDir, locale);
106
244
 
107
245
  if (!foundItem) {
108
- console.warn(`⚠️ Item with path "${docPath}" not found in documentStructure`);
246
+ debug(`⚠️ Item with path "${docPath}" not found in documentStructure`);
109
247
  continue;
110
248
  }
111
249
 
@@ -120,8 +258,15 @@ export default async function chooseDocs(
120
258
  }
121
259
 
122
260
  // Prompt for feedback if not provided
261
+ // Skip feedback prompt if --diagram, --diagram-all, or --diagram-sync flag is set
123
262
  let userFeedback = feedback;
124
- if (!userFeedback && (requiredFeedback || foundItems?.length > 1)) {
263
+ if (
264
+ !userFeedback &&
265
+ (requiredFeedback || foundItems?.length > 1) &&
266
+ !shouldUpdateDiagrams &&
267
+ !shouldSyncImages &&
268
+ !rest.isChat
269
+ ) {
125
270
  const feedbackMessage = getFeedbackMessage(docAction);
126
271
 
127
272
  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";