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

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 (80) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/agentic-agents/common/base-info.md +50 -0
  3. package/agentic-agents/common/planner.md +115 -0
  4. package/agentic-agents/common/worker.md +51 -0
  5. package/agentic-agents/create/index.yaml +79 -0
  6. package/agentic-agents/create/objective.md +44 -0
  7. package/agentic-agents/create/set-custom-prompt.mjs +43 -0
  8. package/agentic-agents/detail/index.yaml +82 -0
  9. package/agentic-agents/detail/objective.md +9 -0
  10. package/agentic-agents/detail/set-custom-prompt.mjs +88 -0
  11. package/agentic-agents/structure/design-rules.md +39 -0
  12. package/agentic-agents/structure/index.yaml +63 -0
  13. package/agentic-agents/structure/objective.md +14 -0
  14. package/agentic-agents/structure/review-criteria.md +55 -0
  15. package/agentic-agents/structure/set-custom-prompt.mjs +78 -0
  16. package/agentic-agents/utils/load-base-sources.mjs +96 -0
  17. package/agents/create/analyze-diagram-type-llm.yaml +160 -0
  18. package/agents/create/analyze-diagram-type.mjs +297 -0
  19. package/agents/create/check-need-generate-structure.mjs +1 -34
  20. package/agents/create/generate-diagram-image.yaml +60 -0
  21. package/agents/create/index.yaml +9 -5
  22. package/agents/create/replace-d2-with-image.mjs +625 -0
  23. package/agents/create/user-review-document-structure.mjs +8 -7
  24. package/agents/create/utils/init-current-content.mjs +5 -9
  25. package/agents/evaluate/document.yaml +6 -0
  26. package/agents/evaluate/index.yaml +1 -0
  27. package/agents/init/index.mjs +36 -388
  28. package/agents/localize/index.yaml +4 -4
  29. package/agents/media/batch-generate-media-description.yaml +2 -0
  30. package/agents/media/generate-media-description.yaml +3 -0
  31. package/agents/media/load-media-description.mjs +44 -15
  32. package/agents/publish/index.yaml +1 -0
  33. package/agents/publish/publish-docs.mjs +1 -4
  34. package/agents/update/check-diagram-flag.mjs +116 -0
  35. package/agents/update/check-document.mjs +0 -1
  36. package/agents/update/check-generate-diagram.mjs +48 -30
  37. package/agents/update/check-sync-image-flag.mjs +55 -0
  38. package/agents/update/check-update-is-single.mjs +11 -0
  39. package/agents/update/generate-diagram.yaml +43 -9
  40. package/agents/update/generate-document.yaml +9 -0
  41. package/agents/update/handle-document-update.yaml +10 -8
  42. package/agents/update/index.yaml +25 -7
  43. package/agents/update/sync-images-and-exit.mjs +148 -0
  44. package/agents/update/update-single/update-single-document-detail.mjs +131 -17
  45. package/agents/utils/analyze-feedback-intent.mjs +136 -0
  46. package/agents/utils/choose-docs.mjs +185 -40
  47. package/agents/utils/generate-document-or-skip.mjs +41 -0
  48. package/agents/utils/handle-diagram-operations.mjs +263 -0
  49. package/agents/utils/load-all-document-content.mjs +30 -0
  50. package/agents/utils/load-sources.mjs +2 -2
  51. package/agents/utils/post-generate.mjs +14 -3
  52. package/agents/utils/read-current-document-content.mjs +46 -0
  53. package/agents/utils/save-doc-translation.mjs +34 -0
  54. package/agents/utils/save-doc.mjs +42 -0
  55. package/agents/utils/save-sidebar.mjs +19 -6
  56. package/agents/utils/skip-if-content-exists.mjs +27 -0
  57. package/aigne.yaml +15 -3
  58. package/assets/report-template/report.html +17 -17
  59. package/docs-mcp/read-doc-content.mjs +30 -1
  60. package/package.json +9 -7
  61. package/prompts/detail/diagram/generate-image-system.md +135 -0
  62. package/prompts/detail/diagram/generate-image-user.md +32 -0
  63. package/prompts/detail/generate/user-prompt.md +27 -13
  64. package/prompts/evaluate/document.md +23 -10
  65. package/prompts/media/media-description/system-prompt.md +10 -2
  66. package/prompts/media/media-description/user-prompt.md +9 -0
  67. package/utils/check-document-has-diagram.mjs +95 -0
  68. package/utils/constants/index.mjs +46 -0
  69. package/utils/d2-utils.mjs +119 -178
  70. package/utils/delete-diagram-images.mjs +99 -0
  71. package/utils/docs-finder-utils.mjs +133 -25
  72. package/utils/image-compress.mjs +75 -0
  73. package/utils/kroki-utils.mjs +2 -3
  74. package/utils/load-config.mjs +29 -0
  75. package/utils/sync-diagram-to-translations.mjs +262 -0
  76. package/utils/utils.mjs +24 -0
  77. package/agents/create/check-diagram.mjs +0 -40
  78. package/agents/create/draw-diagram.yaml +0 -27
  79. package/agents/create/merge-diagram.yaml +0 -39
  80. package/agents/create/wrap-diagram-code.mjs +0 -35
@@ -0,0 +1,75 @@
1
+ import sharp from "sharp";
2
+ import path from "node:path";
3
+ import { debug } from "./debug.mjs";
4
+
5
+ /**
6
+ * Compress an image using sharp
7
+ * Supports JPEG, PNG, and WebP formats
8
+ * @param {string} inputPath - Path to the input image file
9
+ * @param {object} options - Compression options
10
+ * @param {number} options.quality - Compression quality (0-100, default: 80)
11
+ * @param {string} options.outputFormat - Output format: 'jpeg', 'png', 'webp' (default: auto-detect from input)
12
+ * @param {string} options.outputPath - Output path for compressed image (if not provided, creates temp file)
13
+ * @returns {Promise<string>} - Path to the compressed image (outputPath if provided, or temp path, or inputPath if compression fails)
14
+ */
15
+ export async function compressImage(inputPath, options = {}) {
16
+ const { quality = 80, outputFormat, outputPath } = options;
17
+
18
+ try {
19
+ const inputExt = path.extname(inputPath).toLowerCase();
20
+
21
+ // Determine output format
22
+ let format = outputFormat;
23
+ if (!format) {
24
+ // Auto-detect from input extension
25
+ if (inputExt === ".jpg" || inputExt === ".jpeg") {
26
+ format = "jpeg";
27
+ } else if (inputExt === ".png") {
28
+ format = "png";
29
+ } else if (inputExt === ".webp") {
30
+ format = "webp";
31
+ } else {
32
+ // Default to JPEG for unknown formats
33
+ format = "jpeg";
34
+ debug(`Unknown image format ${inputExt}, defaulting to JPEG`);
35
+ }
36
+ }
37
+
38
+ // Determine output path
39
+ let finalOutputPath = outputPath;
40
+ if (!finalOutputPath) {
41
+ // If no output path provided, create temp file in same directory as input
42
+ const outputExt = format === "jpeg" ? ".jpg" : format === "png" ? ".png" : ".webp";
43
+ const inputDir = path.dirname(inputPath);
44
+ const inputBase = path.basename(inputPath, path.extname(inputPath));
45
+ finalOutputPath = path.join(inputDir, `${inputBase}.compressed${outputExt}`);
46
+ }
47
+
48
+ // Create sharp instance and compress
49
+ let sharpInstance = sharp(inputPath);
50
+
51
+ // Apply format-specific compression options
52
+ if (format === "jpeg") {
53
+ // mozjpeg is a valid sharp option for better JPEG compression
54
+ const jpegOptions = { quality, mozjpeg: true };
55
+ sharpInstance = sharpInstance.jpeg(jpegOptions);
56
+ } else if (format === "png") {
57
+ sharpInstance = sharpInstance.png({ quality, compressionLevel: 9 });
58
+ } else if (format === "webp") {
59
+ sharpInstance = sharpInstance.webp({ quality });
60
+ }
61
+
62
+ // Write compressed image directly to output path
63
+ await sharpInstance.toFile(finalOutputPath);
64
+
65
+ debug(
66
+ `✅ Image compressed: ${inputPath} -> ${finalOutputPath} (format: ${format}, quality: ${quality})`,
67
+ );
68
+
69
+ return finalOutputPath;
70
+ } catch (error) {
71
+ debug(`⚠️ Failed to compress image ${inputPath}: ${error.message}`);
72
+ // Return original path if compression fails
73
+ return inputPath;
74
+ }
75
+ }
@@ -15,6 +15,7 @@ import {
15
15
  TMP_DIR,
16
16
  } from "./constants/index.mjs";
17
17
  import { getContentHash } from "./utils.mjs";
18
+ import { d2CodeBlockRegex } from "./d2-utils.mjs";
18
19
 
19
20
  const debug = Debug("doc-smith");
20
21
 
@@ -59,11 +60,9 @@ export async function saveD2Assets({ markdown, docsDir }) {
59
60
  return markdown;
60
61
  }
61
62
 
62
- const codeBlockRegex = /```d2.*\n([\s\S]*?)```/g;
63
-
64
63
  const { replaced } = await runIterator({
65
64
  input: markdown,
66
- regexp: codeBlockRegex,
65
+ regexp: d2CodeBlockRegex,
67
66
  replace: true,
68
67
  fn: async ([_match, _code]) => {
69
68
  const assetDir = path.join(docsDir, "../", TMP_ASSETS_DIR, "d2");
@@ -26,6 +26,35 @@ export default async function loadConfig({ config, appUrl }) {
26
26
  // Resolve file references (@ prefixed values)
27
27
  parsedConfig = await resolveFileReferences(parsedConfig);
28
28
 
29
+ // Read DOC-SMITH.md if exists in current working directory
30
+ const docSmithVariants = ["DOC-SMITH.md", "doc-smith.md", "DocSmith.md"];
31
+ let docSmithContent = "";
32
+ for (const variant of docSmithVariants) {
33
+ const docSmithPath = path.join(process.cwd(), variant);
34
+ try {
35
+ const content = await fs.readFile(docSmithPath, "utf-8");
36
+ if (content && content.trim()) {
37
+ docSmithContent = content.trim();
38
+ console.log(`✓ Found ${chalk.cyan(variant)}, custom rules loaded`);
39
+ break; // Use the first found file
40
+ }
41
+ } catch (error) {
42
+ if (error.code !== "ENOENT") {
43
+ // File exists but can't be read (permission issue, etc.)
44
+ console.warn(`⚠️ Found ${variant} but failed to read: ${error.message}`);
45
+ }
46
+ // ENOENT means file doesn't exist, continue to next variant
47
+ }
48
+ }
49
+
50
+ // Append DOC-SMITH content to rules if found
51
+ if (docSmithContent) {
52
+ const existingRules = parsedConfig.rules || "";
53
+ parsedConfig.rules = existingRules
54
+ ? `${existingRules}\n\n${docSmithContent}`
55
+ : docSmithContent;
56
+ }
57
+
29
58
  if (appUrl) {
30
59
  parsedConfig.appUrl = appUrl.includes("://") ? appUrl : `https://${appUrl}`;
31
60
  }
@@ -0,0 +1,262 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { readFileContent } from "./docs-finder-utils.mjs";
3
+ import { debug } from "./debug.mjs";
4
+ import path from "node:path";
5
+ import fs from "fs-extra";
6
+ import { d2CodeBlockRegex, diagramImageWithPathRegex } from "./d2-utils.mjs";
7
+
8
+ /**
9
+ * Find all translation files for a document
10
+ * @param {string} docPath - Document path (e.g., "/guides/getting-started")
11
+ * @param {string} docsDir - Documentation directory
12
+ * @param {string} locale - Main language locale (e.g., "en")
13
+ * @returns {Promise<Array<{language: string, fileName: string}>>} - Array of translation file info
14
+ */
15
+ async function findTranslationFiles(docPath, docsDir, locale) {
16
+ // Convert path to flat filename format
17
+ const flatName = docPath.replace(/^\//, "").replace(/\//g, "-");
18
+
19
+ try {
20
+ const files = readdirSync(docsDir);
21
+ const translationFiles = [];
22
+ const mainFileName = locale === "en" ? `${flatName}.md` : `${flatName}.${locale}.md`;
23
+
24
+ // Filter files to find translation files matching the pattern
25
+ for (const file of files) {
26
+ if (!file.endsWith(".md")) continue;
27
+ if (file === mainFileName) continue; // Skip main language file
28
+
29
+ // Case 1: File without language suffix (xxx.md) - this is English translation when main language is not English
30
+ if (file === `${flatName}.md` && locale !== "en") {
31
+ translationFiles.push({
32
+ language: "en",
33
+ fileName: file,
34
+ });
35
+ continue;
36
+ }
37
+
38
+ // Case 2: File with language suffix (xxx.{lang}.md) - all other translations
39
+ if (file.startsWith(`${flatName}.`) && file.match(/\.\w+(-\w+)?\.md$/)) {
40
+ const langMatch = file.match(/\.(\w+(-\w+)?)\.md$/);
41
+ if (langMatch && langMatch[1] !== locale) {
42
+ translationFiles.push({
43
+ language: langMatch[1],
44
+ fileName: file,
45
+ });
46
+ }
47
+ }
48
+ }
49
+
50
+ return translationFiles;
51
+ } catch (error) {
52
+ debug(`⚠️ Could not read translation files from ${docsDir}: ${error.message}`);
53
+ return [];
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Extract diagram image paths from content
59
+ * @param {string} content - Document content
60
+ * @returns {Array<{path: string, fullMatch: string, index: number}>} - Array of diagram image info
61
+ */
62
+ function extractDiagramImagePaths(content) {
63
+ const images = [];
64
+ const matches = Array.from(content.matchAll(diagramImageWithPathRegex));
65
+
66
+ for (const match of matches) {
67
+ images.push({
68
+ path: match[1],
69
+ fullMatch: match[0],
70
+ index: match.index,
71
+ });
72
+ }
73
+
74
+ return images;
75
+ }
76
+
77
+ /**
78
+ * Replace diagram images in translation files
79
+ * @param {string} mainContent - Main document content (already updated)
80
+ * @param {string} docPath - Document path
81
+ * @param {string} docsDir - Documentation directory
82
+ * @param {string} locale - Main language locale
83
+ * @param {string} operationType - Operation type: "delete", "add", "update", or "sync" (default)
84
+ * @returns {Promise<{updated: number, skipped: number, errors: Array}>} - Sync result
85
+ */
86
+ export async function syncDiagramToTranslations(
87
+ mainContent,
88
+ docPath,
89
+ docsDir,
90
+ locale = "en",
91
+ operationType = "sync",
92
+ ) {
93
+ const result = {
94
+ updated: 0,
95
+ skipped: 0,
96
+ errors: [],
97
+ };
98
+
99
+ // Find all translation files
100
+ const translationFiles = await findTranslationFiles(docPath, docsDir, locale);
101
+
102
+ if (translationFiles.length === 0) {
103
+ debug("ℹ️ No translation files found, skipping sync");
104
+ return result;
105
+ }
106
+
107
+ // Extract diagram images from updated main content
108
+ const mainImages = extractDiagramImagePaths(mainContent);
109
+
110
+ // If no diagrams in main content and operation is not delete, skip sync
111
+ // For delete operations, we need to process translations even if main has 0 diagrams
112
+ // to remove diagrams from translation files
113
+ if (mainImages.length === 0 && operationType !== "delete") {
114
+ debug("ℹ️ No diagram images in main content, skipping sync");
115
+ return result;
116
+ }
117
+
118
+ // Process each translation file
119
+ for (const { fileName } of translationFiles) {
120
+ try {
121
+ const translationFilePath = path.join(docsDir, fileName);
122
+ const translationContent = await readFileContent(docsDir, fileName);
123
+
124
+ // Check for null or undefined (file read failure), but allow empty string (valid content)
125
+ if (translationContent === null || translationContent === undefined) {
126
+ debug(`⚠️ Could not read translation file: ${fileName}`);
127
+ result.skipped++;
128
+ continue;
129
+ }
130
+
131
+ let hasChanges = false;
132
+ let updatedContent = translationContent;
133
+
134
+ // Strategy 1: Replace D2 code blocks with AI images (if main doc switched from D2 to AI)
135
+ const translationD2Blocks = Array.from(translationContent.matchAll(d2CodeBlockRegex));
136
+ if (translationD2Blocks.length > 0 && mainImages.length > 0) {
137
+ // If main doc has AI images and translation has D2 blocks, replace them by index
138
+ for (let i = 0; i < Math.min(translationD2Blocks.length, mainImages.length); i++) {
139
+ const d2Match = translationD2Blocks[i];
140
+ const mainImage = mainImages[i];
141
+
142
+ if (d2Match && mainImage) {
143
+ // Replace D2 block with AI image from main doc
144
+ updatedContent = updatedContent.replace(d2Match[0], mainImage.fullMatch);
145
+ hasChanges = true;
146
+ debug(`🔄 Replaced D2 block with AI image in ${fileName} (index ${i})`);
147
+ }
148
+ }
149
+ }
150
+
151
+ // Strategy 2: Replace old image paths with new ones (if paths changed)
152
+ const translationImages = extractDiagramImagePaths(updatedContent); // Re-extract after D2 replacement
153
+ if (mainImages.length > 0) {
154
+ for (let i = 0; i < Math.min(translationImages.length, mainImages.length); i++) {
155
+ const translationImage = translationImages[i];
156
+ const mainImage = mainImages[i];
157
+
158
+ // If image path changed, update it
159
+ if (translationImage && mainImage && translationImage.path !== mainImage.path) {
160
+ // Replace old image path with new one (escape special regex characters)
161
+ const escapedPath = translationImage.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
162
+ const oldImagePattern = new RegExp(
163
+ `<!--\\s*DIAGRAM_IMAGE_START:[^>]+-->\\s*!\\[[^\\]]*\\]\\(${escapedPath}\\)\\s*<!--\\s*DIAGRAM_IMAGE_END\\s*-->`,
164
+ "g",
165
+ );
166
+ updatedContent = updatedContent.replace(oldImagePattern, mainImage.fullMatch);
167
+ hasChanges = true;
168
+ debug(
169
+ `🔄 Updated image path in ${fileName} (index ${i}): ${translationImage.path} -> ${mainImage.path}`,
170
+ );
171
+ }
172
+ }
173
+ }
174
+
175
+ // Strategy 3: If translation has fewer images than main, add missing ones
176
+ // (This handles cases where new diagrams were added)
177
+ let finalTranslationImages = extractDiagramImagePaths(updatedContent); // Re-extract after all replacements
178
+ if (mainImages.length > 0 && finalTranslationImages.length < mainImages.length) {
179
+ // Find the last diagram position in updated content
180
+ const lastDiagramIndex =
181
+ finalTranslationImages.length > 0
182
+ ? finalTranslationImages[finalTranslationImages.length - 1].index +
183
+ finalTranslationImages[finalTranslationImages.length - 1].fullMatch.length
184
+ : updatedContent.length;
185
+
186
+ // Add missing images after the last diagram
187
+ const missingImages = mainImages.slice(finalTranslationImages.length);
188
+ const imagesToAdd = missingImages.map((img) => img.fullMatch).join("\n\n");
189
+
190
+ updatedContent =
191
+ updatedContent.slice(0, lastDiagramIndex) +
192
+ "\n\n" +
193
+ imagesToAdd +
194
+ "\n\n" +
195
+ updatedContent.slice(lastDiagramIndex);
196
+ hasChanges = true;
197
+ debug(`➕ Added ${missingImages.length} missing diagram(s) to ${fileName}`);
198
+ // Re-extract after adding images
199
+ finalTranslationImages = extractDiagramImagePaths(updatedContent);
200
+ }
201
+
202
+ // Strategy 4: If translation has more images than main, remove excess ones
203
+ // (This handles cases where diagrams were deleted from main document, including all diagrams)
204
+ if (finalTranslationImages.length > mainImages.length) {
205
+ // Remove excess images from translation (keep only the first N images matching main)
206
+ // Process from end to start to preserve indices
207
+ const excessCount = finalTranslationImages.length - mainImages.length;
208
+ for (let i = finalTranslationImages.length - 1; i >= mainImages.length; i--) {
209
+ const imageToRemove = finalTranslationImages[i];
210
+ const before = updatedContent.substring(0, imageToRemove.index);
211
+ const after = updatedContent.substring(
212
+ imageToRemove.index + imageToRemove.fullMatch.length,
213
+ );
214
+ // Remove the image and clean up extra newlines
215
+ updatedContent = `${before.replace(/\n+$/, "")}\n${after.replace(/^\n+/, "")}`;
216
+ hasChanges = true;
217
+ }
218
+ debug(`➖ Removed ${excessCount} excess diagram(s) from ${fileName}`);
219
+ }
220
+
221
+ // Strategy 5: Remove D2 code blocks from translation if main has no diagrams
222
+ // (This handles cases where all diagrams were deleted from main document)
223
+ if (mainImages.length === 0) {
224
+ // Re-extract D2 blocks from updated content (in case some were already replaced)
225
+ const remainingD2Blocks = Array.from(updatedContent.matchAll(d2CodeBlockRegex));
226
+ if (remainingD2Blocks.length > 0) {
227
+ // Remove all D2 code blocks from translation
228
+ // Process from end to start to preserve indices
229
+ for (let i = remainingD2Blocks.length - 1; i >= 0; i--) {
230
+ const d2Match = remainingD2Blocks[i];
231
+ const before = updatedContent.substring(0, d2Match.index);
232
+ const after = updatedContent.substring(d2Match.index + d2Match[0].length);
233
+ // Remove the D2 block and clean up extra newlines
234
+ updatedContent = `${before.replace(/\n+$/, "")}\n${after.replace(/^\n+/, "")}`;
235
+ hasChanges = true;
236
+ }
237
+ // Clean up extra newlines
238
+ updatedContent = updatedContent.replace(/\n{3,}/g, "\n\n");
239
+ debug(`➖ Removed ${remainingD2Blocks.length} D2 code block(s) from ${fileName}`);
240
+ }
241
+ }
242
+
243
+ // Save updated translation file if there were changes
244
+ if (hasChanges) {
245
+ await fs.writeFile(translationFilePath, updatedContent, "utf8");
246
+ result.updated++;
247
+ debug(`✅ Synced diagram images to ${fileName}`);
248
+ } else {
249
+ result.skipped++;
250
+ debug(`⏭️ No changes needed for ${fileName}`);
251
+ }
252
+ } catch (error) {
253
+ debug(`❌ Error syncing diagram to ${fileName}: ${error.message}`);
254
+ result.errors.push({
255
+ file: fileName,
256
+ error: error.message,
257
+ });
258
+ }
259
+ }
260
+
261
+ return result;
262
+ }
package/utils/utils.mjs CHANGED
@@ -302,10 +302,18 @@ export function hasFileChangesBetweenCommits(
302
302
  return addedOrDeletedFiles.some((filePath) => {
303
303
  // Check if file matches any include pattern
304
304
  const matchesInclude = includePatterns.some((pattern) => {
305
+ // Skip empty patterns
306
+ if (!pattern || !pattern.trim()) {
307
+ return false;
308
+ }
305
309
  // First escape all regex special characters except * and ?
306
310
  const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
307
311
  // Then convert glob wildcards to regex
308
312
  const regexPattern = escapedPattern.replace(/\*/g, ".*").replace(/\?/g, ".");
313
+ // Only create regex if pattern is not empty
314
+ if (!regexPattern) {
315
+ return false;
316
+ }
309
317
  const regex = new RegExp(regexPattern);
310
318
  return regex.test(filePath);
311
319
  });
@@ -316,10 +324,18 @@ export function hasFileChangesBetweenCommits(
316
324
 
317
325
  // Check if file matches any exclude pattern
318
326
  const matchesExclude = excludePatterns.some((pattern) => {
327
+ // Skip empty patterns
328
+ if (!pattern || !pattern.trim()) {
329
+ return false;
330
+ }
319
331
  // First escape all regex special characters except * and ?
320
332
  const escapedPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
321
333
  // Then convert glob wildcards to regex
322
334
  const regexPattern = escapedPattern.replace(/\*/g, ".*").replace(/\?/g, ".");
335
+ // Only create regex if pattern is not empty
336
+ if (!regexPattern) {
337
+ return false;
338
+ }
323
339
  const regex = new RegExp(regexPattern);
324
340
  return regex.test(filePath);
325
341
  });
@@ -370,6 +386,10 @@ export async function loadConfigFromFile() {
370
386
  * @returns {string} Updated file content
371
387
  */
372
388
  function handleArrayValueUpdate(key, value, comment, fileContent) {
389
+ // Skip if key is empty to avoid "Empty regular expressions are not allowed" error
390
+ if (!key || !key.trim()) {
391
+ return fileContent;
392
+ }
373
393
  // Use yaml library to safely serialize the key-value pair
374
394
  const yamlObject = { [key]: value };
375
395
  const yamlContent = yamlStringify(yamlObject).trim();
@@ -455,6 +475,10 @@ function handleArrayValueUpdate(key, value, comment, fileContent) {
455
475
  * @returns {string} Updated file content
456
476
  */
457
477
  function handleStringValueUpdate(key, value, comment, fileContent) {
478
+ // Skip if key is empty to avoid "Empty regular expressions are not allowed" error
479
+ if (!key || !key.trim()) {
480
+ return fileContent;
481
+ }
458
482
  // Use yaml library to safely serialize the key-value pair
459
483
  const yamlObject = { [key]: value };
460
484
  const yamlContent = yamlStringify(yamlObject).trim();
@@ -1,40 +0,0 @@
1
- import { checkContent } from "../../utils/d2-utils.mjs";
2
-
3
- export default async function checkD2DiagramIsValid({ diagramSourceCode }) {
4
- try {
5
- await checkContent({ content: diagramSourceCode });
6
- return {
7
- isValid: true,
8
- };
9
- } catch (err) {
10
- return {
11
- isValid: false,
12
- diagramError: err.message,
13
- };
14
- }
15
- }
16
-
17
- checkD2DiagramIsValid.input_schema = {
18
- type: "object",
19
- properties: {
20
- diagramSourceCode: {
21
- type: "string",
22
- description: "Source code of d2 diagram",
23
- },
24
- },
25
- required: ["diagramSourceCode"],
26
- };
27
- checkD2DiagramIsValid.output_schema = {
28
- type: "object",
29
- properties: {
30
- isValid: {
31
- type: "boolean",
32
- description: "Indicates whether the provided d2 diagram source passes validation",
33
- },
34
- error: {
35
- type: "string",
36
- description: "Validation error details when the diagram does not pass",
37
- },
38
- },
39
- required: ["isValid"],
40
- };
@@ -1,27 +0,0 @@
1
- name: drawDiagram
2
- description: Generate a D2 diagram from document content.
3
- instructions:
4
- - role: system
5
- url: ../../prompts/detail/diagram/system-prompt.md
6
- - role: user
7
- url: ../../prompts/detail/diagram/user-prompt.md
8
- input_schema:
9
- type: object
10
- properties:
11
- documentContent:
12
- type: string
13
- description: The **raw text content** of the current document. (**Note:** This is the original document and **does not include** any diagram source code.)
14
- locale:
15
- type: string
16
- description: Language for diagram labels and text
17
- default: en
18
- required:
19
- - documentContent
20
- output_schema:
21
- type: object
22
- properties:
23
- diagramSourceCode:
24
- type: string
25
- description: The **diagram source code** generated from the input text.
26
- required:
27
- - diagramSourceCode
@@ -1,39 +0,0 @@
1
- name: mergeDiagramToDocument
2
- description: Merge Diagram source code into document
3
- instructions: |
4
- You are an AI assistant that helps to merge d2 diagram into document.
5
-
6
- <detail_data_source>
7
- {{ content }}
8
- </detail_data_source>
9
-
10
- <diagram_source_code>
11
- {{ diagramSourceCode }}
12
- </diagram_source_code>
13
-
14
- Given the source content of a document and the D2 diagram source code, your task is to:
15
- - **Keep the original content as soon as possible.**
16
- - D2 diagram source code should wrap by ```d2 and ``` in markdown format.
17
- - Should find proper position to insert the D2 diagram in the document, usually after the first paragraph or after the section that describes the diagram.
18
- - If there is no suitable position, append it to the end of the document.
19
- - If there is no D2 diagram source code, return the original document content without any changes.
20
- input_schema:
21
- type: object
22
- properties:
23
- content:
24
- type: string
25
- description: Source content of the document
26
- diagramSourceCode:
27
- type: string
28
- description: Source content of D2 Diagram
29
- required:
30
- - content
31
- - diagramSourceCode
32
- output_schema:
33
- type: object
34
- properties:
35
- content:
36
- type: string
37
- description: Merged content of the document with D2 diagram
38
- required:
39
- - content
@@ -1,35 +0,0 @@
1
- import { wrapCode } from "../../utils/d2-utils.mjs";
2
-
3
- export default async function wrapDiagramCode({ diagramSourceCode }) {
4
- try {
5
- const result = await wrapCode({ content: diagramSourceCode });
6
- return {
7
- diagramSourceCode: result,
8
- };
9
- } catch {
10
- return {
11
- diagramSourceCode,
12
- };
13
- }
14
- }
15
-
16
- wrapDiagramCode.input_schema = {
17
- type: "object",
18
- properties: {
19
- diagramSourceCode: {
20
- type: "string",
21
- description: "Source code of d2 diagram",
22
- },
23
- },
24
- required: ["diagramSourceCode"],
25
- };
26
- wrapDiagramCode.output_schema = {
27
- type: "object",
28
- properties: {
29
- diagramSourceCode: {
30
- type: "string",
31
- description: "Source code of d2 diagram",
32
- },
33
- },
34
- required: ["diagramSourceCode"],
35
- };