@aigne/doc-smith 0.9.6 → 0.9.7-beta.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 (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/agents/clear/clear-generated-docs.mjs +2 -2
  3. package/agents/create/index.yaml +0 -17
  4. package/agents/create/merge-diagram.yaml +1 -2
  5. package/agents/create/user-add-document/add-documents-to-structure.mjs +2 -8
  6. package/agents/create/user-add-document/review-documents-with-new-links.mjs +3 -3
  7. package/agents/create/user-remove-document/index.yaml +0 -1
  8. package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +5 -9
  9. package/agents/init/check.mjs +2 -2
  10. package/agents/init/index.mjs +12 -3
  11. package/agents/localize/choose-language.mjs +4 -7
  12. package/agents/localize/index.yaml +1 -9
  13. package/agents/update/batch-generate-document.yaml +10 -2
  14. package/agents/update/check-document.mjs +4 -3
  15. package/agents/update/check-generate-diagram.mjs +5 -2
  16. package/agents/update/index.yaml +1 -9
  17. package/agents/update/save-and-translate-document.mjs +0 -1
  18. package/agents/update/update-single/update-single-document-detail.mjs +11 -4
  19. package/agents/utils/choose-docs.mjs +7 -19
  20. package/agents/utils/find-item-by-path.mjs +5 -15
  21. package/agents/utils/post-generate.mjs +1 -1
  22. package/aigne.yaml +0 -1
  23. package/package.json +11 -9
  24. package/prompts/common/document/markdown-syntax-rules.md +65 -0
  25. package/prompts/detail/custom/admonition-usage-rules.md +94 -0
  26. package/prompts/detail/custom/custom-components/x-card-usage-rules.md +1 -1
  27. package/prompts/detail/custom/custom-components/x-field-desc-usage-rules.md +34 -0
  28. package/prompts/detail/custom/custom-components/x-field-group-usage-rules.md +0 -15
  29. package/prompts/detail/custom/custom-components/x-field-usage-rules.md +1 -1
  30. package/prompts/detail/generate/document-rules.md +2 -7
  31. package/prompts/detail/generate/system-prompt.md +2 -0
  32. package/prompts/detail/update/system-prompt.md +2 -0
  33. package/prompts/translate/admonition.md +20 -0
  34. package/prompts/translate/translate-document.md +2 -0
  35. package/utils/constants/index.mjs +8 -21
  36. package/utils/d2-utils.mjs +40 -1
  37. package/utils/docs-finder-utils.mjs +19 -25
  38. package/utils/file-utils.mjs +1 -1
  39. package/agents/schema/document-execution-structure.yaml +0 -32
  40. package/agents/utils/add-translates-to-structure.mjs +0 -29
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.7-beta.1](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.7-beta...v0.9.7-beta.1) (2025-11-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** ensure pipeline fails on test failures ([#328](https://github.com/AIGNE-io/aigne-doc-smith/issues/328)) ([56fb4d5](https://github.com/AIGNE-io/aigne-doc-smith/commit/56fb4d5259a21a6cf76f67c6ac5a43a7c1403b99))
9
+ * supports admonition syntax ([#338](https://github.com/AIGNE-io/aigne-doc-smith/issues/338)) ([db19661](https://github.com/AIGNE-io/aigne-doc-smith/commit/db19661e1c2d654181d932e0834e7e011a3ad8a5))
10
+
11
+ ## [0.9.7-beta](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6...v0.9.7-beta) (2025-11-25)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * documentExecutionStructure might be an object ([#332](https://github.com/AIGNE-io/aigne-doc-smith/issues/332)) ([337b350](https://github.com/AIGNE-io/aigne-doc-smith/commit/337b350d5fa045edb7b2db84f0892151aa8518ab))
17
+ * update default exclusion patterns, keeping only the necessary items ([#334](https://github.com/AIGNE-io/aigne-doc-smith/issues/334)) ([f1431cf](https://github.com/AIGNE-io/aigne-doc-smith/commit/f1431cf3d600c77c05c8e346712f79aa689ffe27))
18
+
3
19
  ## [0.9.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6-beta.2...v0.9.6) (2025-11-21)
4
20
 
5
21
  ## [0.9.6-beta.2](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.9.6-beta.1...v0.9.6-beta.2) (2025-11-20)
@@ -29,12 +29,12 @@ export default async function clearGeneratedDocs(input = {}, options = {}) {
29
29
  };
30
30
  }
31
31
 
32
- const documentExecutionStructure = (await loadDocumentStructure(outputDir)) || [];
32
+ const documentStructure = (await loadDocumentStructure(outputDir)) || [];
33
33
  // select documents interactively
34
34
  const chooseResult = await chooseDocs(
35
35
  {
36
36
  docs: [], // Empty to trigger interactive selection
37
- documentExecutionStructure,
37
+ documentStructure,
38
38
  docsDir: generatedDocsPath,
39
39
  locale: locale || "en",
40
40
  isTranslate: false,
@@ -25,23 +25,6 @@ skills:
25
25
  fileName: structure-plan.json
26
26
  - ../utils/save-sidebar.mjs
27
27
  - ../utils/ensure-document-icons.mjs
28
- - type: transform
29
- name: transformData
30
- task_render_mode: hide
31
- jsonata: |
32
- $merge([
33
- $,
34
- {
35
- 'documentExecutionStructure': $map(documentStructure, function($item) {
36
- $merge([
37
- $item,
38
- {
39
- 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
40
- }
41
- ])
42
- })
43
- }
44
- ])
45
28
  - ../utils/format-document-structure.mjs
46
29
  - ../media/load-media-description.mjs
47
30
  - ../update/batch-generate-document.yaml
@@ -36,5 +36,4 @@ output_schema:
36
36
  type: string
37
37
  description: Merged content of the document with D2 diagram
38
38
  required:
39
- - content
40
-
39
+ - content
@@ -1,9 +1,8 @@
1
1
  import { getActiveRulesForScope } from "../../../utils/preferences-utils.mjs";
2
2
  import { printDocumentStructure } from "../../../utils/docs-finder-utils.mjs";
3
- import addTranslatesToStructure from "../../utils/add-translates-to-structure.mjs";
4
3
 
5
4
  export default async function addDocumentsToStructure(input = {}, options = {}) {
6
- const { originalDocumentStructure = [], translateLanguages = [] } = input;
5
+ const { originalDocumentStructure = [] } = input;
7
6
  const analyzeIntent = options.context?.agents?.["analyzeStructureFeedbackIntent"];
8
7
  const updateDocumentStructure = options.context?.agents?.["updateDocumentStructure"];
9
8
  const allFeedback = [];
@@ -76,16 +75,11 @@ export default async function addDocumentsToStructure(input = {}, options = {})
76
75
 
77
76
  if (currentStructure.length > originalDocumentStructure.length) {
78
77
  const originalPaths = new Set(originalDocumentStructure.map((doc) => doc.path));
79
- const { documentExecutionStructure } = addTranslatesToStructure({
80
- originalDocumentStructure: currentStructure,
81
- translateLanguages,
82
- });
83
- const newDocuments = documentExecutionStructure.filter((doc) => !originalPaths.has(doc.path));
78
+ const newDocuments = currentStructure.filter((doc) => !originalPaths.has(doc.path));
84
79
 
85
80
  return {
86
81
  originalDocumentStructure: currentStructure,
87
82
  documentStructure: JSON.parse(JSON.stringify(currentStructure)),
88
- documentExecutionStructure,
89
83
  newDocuments,
90
84
  allFeedback,
91
85
  };
@@ -8,7 +8,7 @@ import { pathExists } from "../../../utils/file-utils.mjs";
8
8
  * Review documentsWithNewLinks and let user select which documents should be updated
9
9
  */
10
10
  export default async function reviewDocumentsWithNewLinks(
11
- { documentsWithNewLinks = [], documentExecutionStructure = [], locale = "en", docsDir },
11
+ { documentsWithNewLinks = [], documentStructure = [], locale = "en", docsDir },
12
12
  options,
13
13
  ) {
14
14
  // If no documents to review, return empty array
@@ -22,7 +22,7 @@ export default async function reviewDocumentsWithNewLinks(
22
22
  documentsWithNewLinks.map((document, index) =>
23
23
  limit(async () => {
24
24
  // Find corresponding document in documentStructure to get title
25
- const structureDoc = documentExecutionStructure.find((item) => item.path === document.path);
25
+ const structureDoc = documentStructure.find((item) => item.path === document.path);
26
26
  const title = structureDoc?.title || document.path;
27
27
 
28
28
  // Generate filename from document path
@@ -86,7 +86,7 @@ export default async function reviewDocumentsWithNewLinks(
86
86
  if (!doc.path) continue;
87
87
 
88
88
  // Find corresponding document in documentStructure to get additional fields
89
- const structureDoc = documentExecutionStructure.find((item) => item.path === doc.path);
89
+ const structureDoc = documentStructure.find((item) => item.path === doc.path);
90
90
 
91
91
  // Generate feedback message for adding new links
92
92
  const newLinksList = doc.newLinks.join(", ");
@@ -15,7 +15,6 @@ skills:
15
15
  fileName: structure-plan.json
16
16
  - ../../utils/save-sidebar.mjs
17
17
  - ./find-documents-with-invalid-links.mjs
18
- - ../../utils/add-translates-to-structure.mjs
19
18
  - ./review-documents-with-invalid-links.mjs
20
19
  - ../../utils/format-document-structure.mjs
21
20
  - ../../media/load-media-description.mjs
@@ -7,11 +7,11 @@ import {
7
7
  /**
8
8
  * Generate feedback message for fixing invalid links in a document
9
9
  */
10
- function generateInvalidLinksFeedback(invalidLinks, documentPath, documentExecutionStructure) {
10
+ function generateInvalidLinksFeedback(invalidLinks, documentPath, documentStructure) {
11
11
  const invalidLinksList = invalidLinks.map((link) => `- ${link}`).join("\n");
12
12
 
13
13
  // Build allowed links from document structure for replacement suggestions
14
- const allowedLinks = buildAllowedLinksFromStructure(documentExecutionStructure);
14
+ const allowedLinks = buildAllowedLinksFromStructure(documentStructure);
15
15
  const allowedLinksArray = Array.from(allowedLinks)
16
16
  .filter((link) => link !== documentPath) // Exclude current document path
17
17
  .sort();
@@ -48,7 +48,7 @@ ${allowedLinksList}
48
48
  }
49
49
 
50
50
  export default async function reviewDocumentsWithInvalidLinks(input = {}, options = {}) {
51
- const { documentsWithInvalidLinks = [], documentExecutionStructure = [], locale = "en" } = input;
51
+ const { documentsWithInvalidLinks = [], documentStructure = [], locale = "en" } = input;
52
52
 
53
53
  // If no documents with invalid links, return empty array
54
54
  if (!Array.isArray(documentsWithInvalidLinks) || documentsWithInvalidLinks.length === 0) {
@@ -96,14 +96,10 @@ export default async function reviewDocumentsWithInvalidLinks(input = {}, option
96
96
  if (!doc.path) continue;
97
97
 
98
98
  // Find corresponding document in documentStructure to get additional fields
99
- const structureDoc = documentExecutionStructure.find((item) => item.path === doc.path);
99
+ const structureDoc = documentStructure.find((item) => item.path === doc.path);
100
100
 
101
101
  // Generate feedback message for fixing invalid links
102
- const feedback = generateInvalidLinksFeedback(
103
- doc.invalidLinks,
104
- doc.path,
105
- documentExecutionStructure,
106
- );
102
+ const feedback = generateInvalidLinksFeedback(doc.invalidLinks, doc.path, documentStructure);
107
103
 
108
104
  preparedDocs.push({
109
105
  ...structureDoc,
@@ -1,8 +1,8 @@
1
1
  import chalk from "chalk";
2
2
  import { getMainLanguageFiles } from "../../utils/docs-finder-utils.mjs";
3
3
 
4
- export default async function checkNeedGenerate({ docsDir, locale, documentExecutionStructure }) {
5
- const mainLanguageFiles = await getMainLanguageFiles(docsDir, locale, documentExecutionStructure);
4
+ export default async function checkNeedGenerate({ docsDir, locale, documentStructure }) {
5
+ const mainLanguageFiles = await getMainLanguageFiles(docsDir, locale, documentStructure);
6
6
 
7
7
  if (mainLanguageFiles.length === 0) {
8
8
  console.log(
@@ -48,7 +48,14 @@ export default async function init(input, options) {
48
48
  options,
49
49
  )?.reasoningEffort;
50
50
 
51
- return config;
51
+ // for translation agent
52
+ if (config.translateLanguages) {
53
+ config.translates = config.translateLanguages.map((lang) => ({ language: lang }));
54
+ }
55
+
56
+ return {
57
+ ...config,
58
+ };
52
59
  }
53
60
 
54
61
  async function _init(
@@ -284,7 +291,9 @@ async function _init(
284
291
  ` 1. Use paths like ${chalk.green("./src")}, ${chalk.green("./README.md")} or ${chalk.green("!./src/private")}.`,
285
292
  );
286
293
  console.log(
287
- ` 2. Use globs like ${chalk.green("src/**/*.js")} or ${chalk.green("!private/**/*.js")} for more specific file matching.`,
294
+ ` 2. Use globs like ${chalk.green("src/**/*.js")} or ${chalk.green(
295
+ "!private/**/*.js",
296
+ )} for more specific file matching.`,
288
297
  );
289
298
  console.log(` 3. Use URLs like ${chalk.green("https://example.com/openapi.yaml")}.`);
290
299
  console.log("💡 If you leave this empty, we will scan the entire directory.");
@@ -601,7 +610,7 @@ ${modelSection}
601
610
 
602
611
  // Directory and source path configurations - safely serialize
603
612
  const docsDirSection = yamlStringify({ docsDir: config.docsDir }).trim();
604
- yaml += `${docsDirSection.replace(/^docsDir:/, "docsDir:")} # The directory where the generated documentation will be saved.\n`;
613
+ yaml += `${docsDirSection} # The directory where the generated documentation will be saved.\n`;
605
614
 
606
615
  const sourcesPathSection = yamlStringify({ sourcesPath: config.sourcesPath }).trim();
607
616
  yaml += `${sourcesPathSection.replace(/^sourcesPath:/, "sourcesPath: # The source code paths to analyze.")}\n`;
@@ -76,16 +76,13 @@ export default async function chooseLanguage({ langs, locale, selectedDocs }, op
76
76
  await saveValueToConfig("translateLanguages", updatedTranslateLanguages);
77
77
  }
78
78
 
79
- const newSelectedDocs = selectedDocs.map((doc) => {
80
- return {
81
- ...doc,
82
- translates: selectedLanguages.map((lang) => ({ language: lang })),
83
- };
84
- });
79
+ // Convert selectedLanguages to translates format
80
+ const translates = selectedLanguages.map((lang) => ({ language: lang }));
85
81
 
86
82
  return {
87
83
  selectedLanguages,
88
- selectedDocs: newSelectedDocs,
84
+ selectedDocs,
85
+ translates,
89
86
  };
90
87
  }
91
88
 
@@ -16,15 +16,7 @@ skills:
16
16
  $merge([
17
17
  $,
18
18
  {
19
- 'documentStructure': originalDocumentStructure,
20
- 'documentExecutionStructure': $map(originalDocumentStructure, function($item) {
21
- $merge([
22
- $item,
23
- {
24
- 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
25
- }
26
- ])
27
- })
19
+ 'documentStructure': originalDocumentStructure
28
20
  }
29
21
  ])
30
22
  - url: ../utils/choose-docs.mjs
@@ -9,11 +9,19 @@ input_schema:
9
9
  detailDataSource:
10
10
  type: string
11
11
  description: Context for documentation structure generation, used to assist generate documentation structure
12
- documentExecutionStructure: ../schema/document-execution-structure.yaml
12
+ documentStructure: ../schema/document-structure.yaml
13
+ translates:
14
+ type: array
15
+ items:
16
+ type: object
17
+ properties:
18
+ language:
19
+ type: string
20
+ description: List of languages to translate documents to
13
21
  modifiedFiles:
14
22
  type: array
15
23
  items: { type: string }
16
24
  description: Array of modified files since last generation
17
- iterate_on: documentExecutionStructure
25
+ iterate_on: documentStructure
18
26
  concurrency: 5
19
27
  mode: sequential
@@ -21,7 +21,7 @@ export default async function checkDocument(
21
21
  modifiedFiles,
22
22
  forceRegenerate,
23
23
  locale,
24
- translates,
24
+ translates = [],
25
25
  ...rest
26
26
  },
27
27
  options,
@@ -87,7 +87,8 @@ export default async function checkDocument(
87
87
  contentValidationFailed = true;
88
88
  }
89
89
  }
90
- const languages = translates.map((x) => x.language);
90
+ const translateList = Array.isArray(translates) ? translates : [];
91
+ const languages = translateList.map((x) => x.language);
91
92
  const lackLanguages = new Set(languages);
92
93
  const skills = [];
93
94
 
@@ -140,7 +141,7 @@ export default async function checkDocument(
140
141
 
141
142
  const result = await options.context.invoke(teamAgent, {
142
143
  ...rest,
143
- translates: translates.filter((x) => lackLanguages.has(x.language)),
144
+ translates: translateList.filter((x) => lackLanguages.has(x.language)),
144
145
  locale,
145
146
  docsDir,
146
147
  path,
@@ -1,4 +1,4 @@
1
- import { DIAGRAM_PLACEHOLDER } from "../../utils/d2-utils.mjs";
1
+ import { DIAGRAM_PLACEHOLDER, replacePlaceholderWithD2 } from "../../utils/d2-utils.mjs";
2
2
 
3
3
  const DEFAULT_DIAGRAMMING_EFFORT = 5;
4
4
  const MIN_DIAGRAMMING_EFFORT = 0;
@@ -68,7 +68,10 @@ export default async function checkGenerateDiagram(
68
68
 
69
69
  if (diagramSourceCode && !skipGenerateDiagram) {
70
70
  if (content.includes(DIAGRAM_PLACEHOLDER)) {
71
- content = content.replace(DIAGRAM_PLACEHOLDER, diagramSourceCode);
71
+ content = replacePlaceholderWithD2({
72
+ content,
73
+ diagramSourceCode,
74
+ });
72
75
  } else {
73
76
  const mergeAgent = options.context?.agents?.["mergeDiagramToDocument"];
74
77
  ({ content } = await options.context.invoke(mergeAgent, {
@@ -16,15 +16,7 @@ skills:
16
16
  $merge([
17
17
  $,
18
18
  {
19
- 'documentStructure': originalDocumentStructure,
20
- 'documentExecutionStructure': $map(originalDocumentStructure, function($item) {
21
- $merge([
22
- $item,
23
- {
24
- 'translates': [$map(translateLanguages, function($lang) { {"language": $lang} })]
25
- }
26
- ])
27
- })
19
+ 'documentStructure': originalDocumentStructure
28
20
  }
29
21
  ])
30
22
  - url: ../utils/choose-docs.mjs
@@ -61,7 +61,6 @@ export default async function saveAndTranslateDocument(input, options) {
61
61
  await options.context.invoke(translateAgent, {
62
62
  ...input, // context is required
63
63
  content: doc.content,
64
- translates: doc.translates,
65
64
  title: doc.title,
66
65
  path: doc.path,
67
66
  docsDir,
@@ -1,7 +1,11 @@
1
1
  import { AIAgent } from "@aigne/core";
2
2
  import { pick } from "@aigne/core/utils/type-utils.js";
3
3
  import z from "zod";
4
- import { DIAGRAM_PLACEHOLDER, replacePlaceholder } from "../../../utils/d2-utils.mjs";
4
+ import {
5
+ DIAGRAM_PLACEHOLDER,
6
+ replaceD2WithPlaceholder,
7
+ replacePlaceholderWithD2,
8
+ } from "../../../utils/d2-utils.mjs";
5
9
  import { userContextAt } from "../../../utils/utils.mjs";
6
10
 
7
11
  async function getIntentType(input, options) {
@@ -64,7 +68,7 @@ async function addDiagram(input, options) {
64
68
  async function updateDiagram(input, options) {
65
69
  const contentContext = userContextAt(options, `currentContents.${input.path}`);
66
70
  const currentContent = contentContext.get();
67
- let [content, previousDiagramContent] = replacePlaceholder({
71
+ let [content, previousDiagramContent] = replaceD2WithPlaceholder({
68
72
  content: currentContent,
69
73
  });
70
74
  const generateAgent = options.context?.agents?.["generateDiagram"];
@@ -74,7 +78,10 @@ async function updateDiagram(input, options) {
74
78
  previousDiagramContent,
75
79
  feedback: input.feedback,
76
80
  });
77
- content = content.replace(DIAGRAM_PLACEHOLDER, diagramSourceCode);
81
+ content = replacePlaceholderWithD2({
82
+ content,
83
+ diagramSourceCode,
84
+ });
78
85
  contentContext.set(content);
79
86
  await saveDoc(input, options, { content });
80
87
  return { content };
@@ -83,7 +90,7 @@ async function updateDiagram(input, options) {
83
90
  async function deleteDiagram(input, options) {
84
91
  const contentContext = userContextAt(options, `currentContents.${input.path}`);
85
92
  const currentContent = contentContext.get();
86
- const [documentContent] = replacePlaceholder({
93
+ const [documentContent] = replaceD2WithPlaceholder({
87
94
  content: currentContent,
88
95
  });
89
96
  const instructions = `<role>
@@ -18,7 +18,7 @@ function getFeedbackMessage(action) {
18
18
  export default async function chooseDocs(
19
19
  {
20
20
  docs,
21
- documentExecutionStructure,
21
+ documentStructure,
22
22
  boardId,
23
23
  docsDir,
24
24
  isTranslate,
@@ -38,11 +38,7 @@ export default async function chooseDocs(
38
38
  if (!docs || docs.length === 0) {
39
39
  try {
40
40
  // Get all main language .md files in docsDir
41
- const mainLanguageFiles = await getMainLanguageFiles(
42
- docsDir,
43
- locale,
44
- documentExecutionStructure,
45
- );
41
+ const mainLanguageFiles = await getMainLanguageFiles(docsDir, locale, documentStructure);
46
42
 
47
43
  if (mainLanguageFiles.length === 0) {
48
44
  throw new Error(
@@ -56,7 +52,7 @@ export default async function chooseDocs(
56
52
  const choices = mainLanguageFiles.map((file) => {
57
53
  // Convert filename to flat path to find corresponding documentation structure item
58
54
  const flatName = file.replace(/\.md$/, "").replace(/\.\w+(-\w+)?$/, "");
59
- const docItem = documentExecutionStructure.find((item) => {
55
+ const docItem = documentStructure.find((item) => {
60
56
  const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
61
57
  return itemFlattenedPath === flatName;
62
58
  });
@@ -96,7 +92,7 @@ export default async function chooseDocs(
96
92
  }
97
93
 
98
94
  // Process selected files and convert to found items
99
- foundItems = await processSelectedFiles(selectedFiles, documentExecutionStructure, docsDir);
95
+ foundItems = await processSelectedFiles(selectedFiles, documentStructure, docsDir);
100
96
  } catch (error) {
101
97
  console.log(
102
98
  getActionText(`\nFailed to select documents to {action}: ${error.message}`, docAction),
@@ -106,16 +102,10 @@ export default async function chooseDocs(
106
102
  } else {
107
103
  // Process the provided docs array
108
104
  for (const docPath of docs) {
109
- const foundItem = await findItemByPath(
110
- documentExecutionStructure,
111
- docPath,
112
- boardId,
113
- docsDir,
114
- locale,
115
- );
105
+ const foundItem = await findItemByPath(documentStructure, docPath, boardId, docsDir, locale);
116
106
 
117
107
  if (!foundItem) {
118
- console.warn(`⚠️ Item with path "${docPath}" not found in documentExecutionStructure`);
108
+ console.warn(`⚠️ Item with path "${docPath}" not found in documentStructure`);
119
109
  continue;
120
110
  }
121
111
 
@@ -125,9 +115,7 @@ export default async function chooseDocs(
125
115
  }
126
116
 
127
117
  if (foundItems.length === 0) {
128
- throw new Error(
129
- "None of the specified document paths were found in documentExecutionStructure",
130
- );
118
+ throw new Error("None of the specified document paths were found in documentStructure");
131
119
  }
132
120
  }
133
121
 
@@ -9,7 +9,7 @@ import {
9
9
  } from "../../utils/docs-finder-utils.mjs";
10
10
 
11
11
  export default async function findItemByPath(
12
- { doc, documentExecutionStructure, boardId, docsDir, isTranslate, feedback, locale },
12
+ { doc, documentStructure, boardId, docsDir, isTranslate, feedback, locale },
13
13
  options,
14
14
  ) {
15
15
  let foundItem = null;
@@ -21,11 +21,7 @@ export default async function findItemByPath(
21
21
  if (!docPath) {
22
22
  try {
23
23
  // Get all main language .md files in docsDir
24
- const mainLanguageFiles = await getMainLanguageFiles(
25
- docsDir,
26
- locale,
27
- documentExecutionStructure,
28
- );
24
+ const mainLanguageFiles = await getMainLanguageFiles(docsDir, locale, documentStructure);
29
25
 
30
26
  if (mainLanguageFiles.length === 0) {
31
27
  throw new Error("No documents found in the docs directory");
@@ -65,7 +61,7 @@ export default async function findItemByPath(
65
61
  const flatName = fileNameToFlatPath(selectedFile);
66
62
 
67
63
  // Try to find matching item by comparing flattened paths
68
- const foundItemByFile = findItemByFlatName(documentExecutionStructure, flatName);
64
+ const foundItemByFile = findItemByFlatName(documentStructure, flatName);
69
65
 
70
66
  if (!foundItemByFile) {
71
67
  throw new Error("No document found");
@@ -84,16 +80,10 @@ export default async function findItemByPath(
84
80
  }
85
81
 
86
82
  // Use the utility function to find item and read content
87
- foundItem = await findItemByPathUtil(
88
- documentExecutionStructure,
89
- docPath,
90
- boardId,
91
- docsDir,
92
- locale,
93
- );
83
+ foundItem = await findItemByPathUtil(documentStructure, docPath, boardId, docsDir, locale);
94
84
 
95
85
  if (!foundItem) {
96
- throw new Error(`Item with path "${docPath}" not found in documentExecutionStructure`);
86
+ throw new Error(`Item with path "${docPath}" not found in documentStructure`);
97
87
  }
98
88
 
99
89
  // Prompt for feedback if not provided
@@ -11,7 +11,7 @@ import { getCurrentGitHead, saveGitHeadToConfig } from "../../utils/utils.mjs";
11
11
  * @returns {Promise<Array<{ path: string, success: boolean, error?: string }>>}
12
12
  */
13
13
  export default async function postGenerate({
14
- documentExecutionStructure: documentStructure,
14
+ documentStructure,
15
15
  docsDir,
16
16
  translateLanguages = [],
17
17
  locale,
package/aigne.yaml CHANGED
@@ -68,7 +68,6 @@ agents:
68
68
  - ./agents/clear/clear-media-description.mjs
69
69
 
70
70
  # Utilities
71
- - ./agents/utils/add-translates-to-structure.mjs
72
71
  - ./agents/utils/load-sources.mjs
73
72
  - ./agents/utils/post-generate.mjs
74
73
  - ./agents/utils/save-sidebar.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.9.6",
3
+ "version": "0.9.7-beta.1",
4
4
  "description": "AI-driven documentation generation tool built on the AIGNE Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -24,10 +24,10 @@
24
24
  "author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
25
25
  "license": "Elastic-2.0",
26
26
  "dependencies": {
27
- "@aigne/cli": "^1.54.1",
28
- "@aigne/core": "^1.67.0",
29
- "@aigne/publish-docs": "^0.13.1",
30
- "@blocklet/payment-broker-client": "^1.22.12",
27
+ "@aigne/cli": "^1.56.0",
28
+ "@aigne/core": "^1.69.0",
29
+ "@aigne/publish-docs": "^0.14.1",
30
+ "@blocklet/payment-broker-client": "^1.22.24",
31
31
  "@terrastruct/d2": "^0.1.33",
32
32
  "chalk": "^5.5.0",
33
33
  "debug": "^4.4.1",
@@ -66,10 +66,12 @@
66
66
  "@biomejs/biome": "^2.2.4"
67
67
  },
68
68
  "scripts": {
69
- "test": "bun test",
70
- "test:coverage2": "bun test --coverage",
71
- "test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
72
- "test:watch": "bun test --watch",
69
+ "test": "bun test --preload ./tests/setup/mock-process-exit.mjs",
70
+ "test:coverage2": "bun test --preload ./tests/setup/mock-process-exit.mjs --coverage",
71
+ "test:coverage": "bun test --preload ./tests/setup/mock-process-exit.mjs --coverage --coverage-reporter=lcov --coverage-reporter=text",
72
+ "test:deploy": "bun test --preload ./tests/setup/mock-process-exit.mjs tests/utils/deploy.test.mjs --coverage --coverage-reporter=text",
73
+ "test:user-review": "bun test --preload ./tests/setup/mock-process-exit.mjs tests/agents/update/user-review-document.test.mjs --coverage --coverage-reporter=lcov --coverage-reporter=text",
74
+ "test:watch": "bun test --preload ./tests/setup/mock-process-exit.mjs --watch",
73
75
  "lint": "biome lint && biome format",
74
76
  "update:deps": "npx -y taze major -r -w -f -n '/@abtnode|@aigne|@arcblock|@blocklet|@did-connect|@did-pay|@did-space|@nft-store|@nft-studio|@ocap/' && pnpm install && pnpm run deduplicate",
75
77
  "deduplicate": "pnpm dedupe",
@@ -0,0 +1,65 @@
1
+ <markdown_syntax_rules>
2
+
3
+ ## Markdown Syntax Standard
4
+
5
+ ### Allowed Syntax
6
+
7
+ **Inline** (used within text):
8
+
9
+ - Emphasis: `**bold**`, `*italic*`, `~~strikethrough~~`
10
+ - Links: `[text](url)`
11
+ - Images: `![alt](url)`
12
+ - Inline Code: `` `code` ``
13
+
14
+ **Block** (standalone blocks):
15
+
16
+ - Headings: `#`, `##`, `###`, etc.
17
+ - Lists: `- item`, `1. item`
18
+ - Task Lists: `- [ ]`, `- [x]`
19
+ - Blockquotes: `> quote`
20
+ - Code Block: ` ```language ... ``` `
21
+ - Tables: `| col | col |` with `|---|---|` separator
22
+ - Horizontal Rule: `---`
23
+ - Admonition: `:::severity ... :::`
24
+
25
+ ### Prohibited Syntax
26
+
27
+ The following are **strictly forbidden**:
28
+
29
+ - Footnotes: `[^1]: note text`
30
+ - Math/LaTeX: `$inline$`, `$$ block $$`
31
+ - Highlight: `==highlighted==`
32
+ - Subscript/Superscript: `H~2~O`, `X^2^`
33
+ - Abbreviations: `*[HTML]: Hyper Text Markup Language`
34
+
35
+ ### Link Rules
36
+
37
+ - Links must reference valid external URLs or paths from the document structure
38
+ - Use absolute paths from the documentation structure for internal links
39
+
40
+ ### Table Formatting Rules
41
+
42
+ - Separator row (`|---|---|---|`) must match the exact column count of header row
43
+ - Each row must have the same number of columns
44
+ - Use tables for predefined values (e.g., status types, options) or term definitions
45
+ - Validate table structure before output
46
+
47
+ ### Code Block Rules
48
+
49
+ - Ensure code blocks are properly closed
50
+ - Generate complete, syntactically correct code (JSON, etc.)
51
+ - Perform self-validation to ensure all code blocks, lists, and tables are properly closed without truncation
52
+
53
+ ### Block-Level Elements
54
+
55
+ Block-level elements are standalone content blocks that must be visually separated from surrounding content.
56
+
57
+ Block-Level Element List:
58
+
59
+ - Admonition: `:::severity ... :::`
60
+ - Code Block: ` ```language ... ``` `
61
+ - Custom Components: `<x-cards>`, `<x-card>`, `<x-field-group>`, etc.
62
+
63
+ **Spacing Rule:** Always insert a blank line before and after any block-level element when it is **adjacent to** other Markdown content (headings, paragraphs, lists, etc.).
64
+
65
+ </markdown_syntax_rules>
@@ -0,0 +1,94 @@
1
+ <admonition_syntax_rules>
2
+
3
+ ## Admonition Syntax Rules
4
+
5
+ Admonition is a Markdown block extension used to highlight important information. Use it sparingly.
6
+
7
+ ### Syntax Structure
8
+
9
+ ```
10
+ :::severity
11
+ content
12
+ :::
13
+ ```
14
+
15
+ ### Syntax Rules
16
+
17
+ The `severity` is **required** and must be one of the following:
18
+
19
+ - `success`: Positive outcome or best practice
20
+ - `info`: General tips
21
+ - `warning`: Cautions or potential issues
22
+ - `error`: Critical risks or breaking operations
23
+
24
+ The `content` is **required** and MUST strictly comply with the rules below:
25
+
26
+ - The `content` MUST be plain text only
27
+ - The `content` MUST be a single paragraph (no line breaks).
28
+ - Nesting any blocks or Admonitions is forbidden.
29
+ - Recommended length: within 200 characters.
30
+
31
+ ### Usage Guidelines
32
+
33
+ - Use sparingly, only for messages that truly require user attention
34
+ - Do not use Admonition if the content needs any Markdown syntax from <markdown_syntax_rules> — use regular paragraphs instead
35
+ - Keep the text short, clear, and actionable
36
+ - Choose the severity level according to the importance of the message
37
+
38
+ ### Good Examples
39
+
40
+ 1. All four severity types:
41
+
42
+ ```md
43
+ :::success
44
+ Your configuration is complete.
45
+ :::
46
+
47
+ :::info
48
+ Environment variables can override this setting.
49
+ :::
50
+
51
+ :::warning
52
+ This API will be removed in v3.0.
53
+ :::
54
+
55
+ :::error
56
+ Never commit API keys to version control.
57
+ :::
58
+ ```
59
+
60
+ ### Bad Examples
61
+ 1. Contains Markdown Syntax:
62
+
63
+ ```md
64
+ :::info
65
+ No **bold**, *italic*, or `inline code` allowed.
66
+ :::
67
+
68
+ :::warning
69
+ No [links](https://example.com) allowed.
70
+ :::
71
+
72
+ :::info
73
+ - No lists
74
+ - Or bullet points
75
+ :::
76
+
77
+ :::error
78
+ ```sh
79
+ npm i
80
+ ```
81
+ :::
82
+ ```
83
+
84
+ 2. Multi-paragraph:
85
+
86
+ ```md
87
+ :::warning
88
+ No multi-paragraph.
89
+
90
+ Like this.
91
+ :::
92
+ ```
93
+
94
+ </admonition_syntax_rules>
@@ -18,7 +18,7 @@ XCard is individual link display card, suitable for displaying individual links
18
18
  ### Children
19
19
 
20
20
  - Must be written within `<x-card>...</x-card>` children.
21
- - **Plain Text Only**: All markdown formatting is prohibited, including inline formats like `code`, **bold**, _italic_, [links](), and block-level formats like headers (# ##), lists (- \*), code blocks (```), tables (|), and any other markdown syntax. Only plain text content is allowed.
21
+ - Plain Text Only: Do not use any Markdown syntax (see `<markdown_syntax_rules>` for the full list).
22
22
 
23
23
  ### Good Examples
24
24
 
@@ -17,6 +17,11 @@ XFieldDesc is rich field description. Used to provide rich text descriptions for
17
17
  ### Usage Rules
18
18
 
19
19
  - **Parent Requirement**: Must be child of `<x-field>`: `<x-field-desc>` can only appear as a child element of `<x-field>` components
20
+ - **Avoid Redundant Information**: Do not repeat information in `<x-field-desc>` that is already expressed by the parent `<x-field>` attributes. Specifically:
21
+ - **Required Status**: Do not mention "required" or "optional" in descriptions since `data-required` attribute already indicates this
22
+ - **Default Values**: Do not repeat default values in descriptions since `data-default` attribute already shows this
23
+ - **Deprecated Status**: Do not mention "deprecated" in descriptions since `data-deprecated` attribute already indicates this
24
+ - Focus descriptions on the field's purpose, format, constraints, example values, and usage guidance instead
20
25
 
21
26
  ### Good Examples
22
27
 
@@ -83,4 +88,33 @@ XFieldDesc is rich field description. Used to provide rich text descriptions for
83
88
  </x-field-group>
84
89
  ```
85
90
 
91
+ - Example 7: Redundant required information in description (violates "Avoid Redundant Information" rule)
92
+ ```md
93
+ <x-field-group>
94
+ <x-field data-name="api_key" data-type="string" data-required="true">
95
+ <x-field-desc markdown>Your **API key** for authentication. **This field is required.**</x-field-desc>
96
+ </x-field>
97
+ <x-field data-name="timeout" data-type="number" data-required="false" data-default="5000">
98
+ <x-field-desc markdown>Request timeout in milliseconds. **Optional**, defaults to `5000`.</x-field-desc>
99
+ </x-field>
100
+ <x-field data-name="old_api" data-type="string" data-deprecated="true">
101
+ <x-field-desc markdown>Old API endpoint. **This field is deprecated.**</x-field-desc>
102
+ </x-field>
103
+ </x-field-group>
104
+ ```
105
+ **Correct approach:**
106
+ ```md
107
+ <x-field-group>
108
+ <x-field data-name="api_key" data-type="string" data-required="true">
109
+ <x-field-desc markdown>Your **API key** for authentication. Generate one from the `Settings > API Keys` section.</x-field-desc>
110
+ </x-field>
111
+ <x-field data-name="timeout" data-type="number" data-required="false" data-default="5000">
112
+ <x-field-desc markdown>Request timeout in milliseconds.</x-field-desc>
113
+ </x-field>
114
+ <x-field data-name="old_api" data-type="string" data-deprecated="true">
115
+ <x-field-desc markdown>Old API endpoint. Use the new endpoint instead.</x-field-desc>
116
+ </x-field>
117
+ </x-field-group>
118
+ ```
119
+
86
120
  </x-field-desc-usage-rules>
@@ -15,7 +15,6 @@ XFieldGroup is `<x-field>` grouping container. Used to group multiple related `<
15
15
 
16
16
  - **Top-Level Only**: Used only at the top level for grouping related `<x-field>` elements. Cannot be nested inside other `<x-field>` or `<x-field-group>` elements
17
17
  - **Structured Data Only**: Use `<x-field-group>` for fields **other than simple types** (`string`, `number`, `boolean`, `symbol`), e.g., Properties, Context, Parameters, Return values. For simple-type fields, use plain Markdown text.
18
- - **Spacing Around**: Always insert a blank line before and after `<x-field-group>` when it’s adjacent to Markdown content.
19
18
 
20
19
  ### Good Examples
21
20
 
@@ -78,18 +77,4 @@ XFieldGroup is `<x-field>` grouping container. Used to group multiple related `<
78
77
  </x-field-group>
79
78
  ```
80
79
 
81
- - Example 6: Missing blank line before x-field-group (violates "Spacing Around" rule)
82
- ```md
83
- **Parameters**
84
- <x-field-group>
85
- <x-field data-name="initialState" data-type="any" data-required="false">
86
- <x-field-desc markdown>The initial state value.</x-field-desc>
87
- </x-field>
88
- </x-field-group>
89
-
90
- `useReducer` returns an array with two items:
91
- <x-field-group>
92
- <x-field data-name="dispatch" data-type="function" data-desc="A function that you can call with an action to update the state."></x-field>
93
- </x-field-group>
94
- ```
95
80
  </x-field-group-usage-rules>
@@ -10,7 +10,7 @@ XField is structured data field, suitable for displaying API parameters, return
10
10
  - `data-default` (optional): Default value for the field
11
11
  - `data-required` (optional): Whether the field is required ("true" or "false")
12
12
  - `data-deprecated` (optional): Whether the field is deprecated ("true" or "false")
13
- - `data-desc` (optional): Simple description of the field (plain text only)
13
+ - `data-desc` (optional): Simple description of the field. Do not use any Markdown syntax (see `<markdown_syntax_rules>` for the full list).
14
14
 
15
15
  ### Children
16
16
 
@@ -1,6 +1,8 @@
1
1
 
2
2
  <document_rules>
3
3
 
4
+ {% include "../../common/document/markdown-syntax-rules.md" %}
5
+
4
6
  Documentation Generation Rules:
5
7
  - **Opening Hook Requirement:** The document must begin with a compelling, relaxed, and concise introductory paragraph (The "Hook").
6
8
  - **Hook Content:** This paragraph must clearly state the specific outcome, knowledge, or skill the reader will gain upon completing the document (Preferably 50 words or less).
@@ -13,9 +15,6 @@ Documentation Generation Rules:
13
15
  - Since API names are already specified in document titles, avoid repeating them in subheadings—use sub-API names directly
14
16
  - Include links to related documents in the introduction using Markdown format to help users navigate to relevant content
15
17
  - Add links to further reading materials in the summary section using Markdown format
16
- - **Markdown Syntax Constraint**: Use only GitHub Flavored Markdown (GFM) syntax by default. Prohibited extensions include: custom blocks `:::`, footnotes `[^1]: notes`, math formulas `$$ LaTeX`, highlighted text `==code==`, and other non-GFM syntax unless explicitly defined in custom component rules
17
- - Use proper Markdown link syntax, for example: [Next Chapter Title](next_chapter_path)
18
- - **Ensure next_chapter_path references either external URLs or valid paths from the documentation structure**—use absolute paths from the documentation structure
19
18
  - When detailDataSource includes third-party links, incorporate them appropriately throughout the document
20
19
  - Structure each section with: title, introduction, code examples, response data samples, and explanatory notes. Place explanations directly after code examples without separate "Example Description" subheadings
21
20
  - Maintain content completeness and logical flow so users can follow the documentation seamlessly
@@ -23,10 +22,6 @@ Documentation Generation Rules:
23
22
  - All interface and method documentation must include **response data examples**
24
23
  - **Use `<x-field-group>` for all structured data**: Represent objects with nested `<x-field>` elements, and expand each structure to the **deepest relevant level**.
25
24
  - **Enhance field descriptions with example values**: For structured data defined using `<x-field-group>`, extract example values from type definitions, comments, or test cases to make documentation more practical and user-friendly.
26
- - **Use Markdown tables** for predefined values (e.g., status types, options) or term definitions to improve clarity and allow side-by-side comparison.
27
- - Validate output Markdown for completeness, ensuring tables are properly formatted
28
- - **Content Integrity**: Generate complete, syntactically correct code blocks (JSON, etc.). Perform self-validation to ensure all code blocks, lists, and tables are properly closed without truncation
29
- - **Markdown Syntax Validation**: Ensure correct Markdown formatting, particularly table separators (e.g., `|---|---|---|`) that match column counts
30
25
  - Use README files for reference only—extract the most current and comprehensive information directly from source code
31
26
  - Omit tag information from document headers as it's processed programmatically
32
27
  - Parse `jsx` syntax correctly when present in code samples
@@ -42,6 +42,8 @@ Documentation content generation rules:
42
42
 
43
43
  {% include "../custom/code-block-usage-rules.md" %}
44
44
 
45
+ {% include "../custom/admonition-usage-rules.md" %}
46
+
45
47
  {% include "../../common/document/media-file-list-usage-rules.md" %}
46
48
 
47
49
  {% include "../../common/document/openapi-usage-rules.md" %}
@@ -68,6 +68,8 @@ Documentation content optimization rules:
68
68
 
69
69
  {% include "../custom/code-block-usage-rules.md" %}
70
70
 
71
+ {% include "../custom/admonition-usage-rules.md" %}
72
+
71
73
  {% include "../../common/document/media-file-list-usage-rules.md" %}
72
74
 
73
75
  {% include "../../common/document/openapi-usage-rules.md" %}
@@ -0,0 +1,20 @@
1
+ <admonition_rules>
2
+
3
+ Admonition blocks use the following format:
4
+
5
+ ```
6
+ :::severity
7
+
8
+ text
9
+
10
+ :::
11
+ ```
12
+
13
+ Admonition Translation Rules:
14
+
15
+ - **Translate** the text content only
16
+ - **Do not translate** the severity keyword (success, info, warning, error)
17
+ - **Preserve** the `:::` syntax and block structure
18
+
19
+ </admonition_rules>
20
+
@@ -24,6 +24,8 @@ Translation Requirements:
24
24
  - Translate Descriptions Only in <x-field>: All `<x-field>` component attributes must maintain the original format. Only translate the description content within `data-desc` attribute or `<x-field-desc>` elements.
25
25
 
26
26
  {% include "./code-block.md" %}
27
+
28
+ {% include "./admonition.md" %}
27
29
  </translation_rules>
28
30
 
29
31
  {% if feedback %}
@@ -1,5 +1,5 @@
1
1
  // Default file patterns for inclusion and exclusion
2
- export const DEFAULT_INCLUDE_PATTERNS = [
2
+ export const DEFAULT_INCLUDE_PATTERNS = Object.freeze([
3
3
  // Python
4
4
  "*.py",
5
5
  "*.pyi",
@@ -100,42 +100,29 @@ export const DEFAULT_INCLUDE_PATTERNS = [
100
100
  "*.mkv",
101
101
  "*.webm",
102
102
  "*.m4v",
103
- ];
103
+ ]);
104
104
 
105
- export const DEFAULT_EXCLUDE_PATTERNS = [
106
- "**/aigne-docs/**",
105
+ export const DEFAULT_EXCLUDE_PATTERNS = Object.freeze([
107
106
  "**/doc-smith/**",
108
107
  "**/.aigne/**",
109
- "**/data/**",
110
- "**/public/**",
111
- "**/static/**",
112
108
  "**/vendor/**",
113
109
  "**/temp/**",
114
- "**/*docs/**",
115
- "**/*doc/**",
116
110
  "**/*venv/**",
117
111
  "*.venv/**",
118
- "*test*",
119
- "**/*test/**",
120
- "**/*tests/**",
121
- "**/*examples/**",
122
- "**/playgrounds/**",
123
- "v1/**",
124
112
  "**/dist/**",
125
113
  "**/*build/**",
126
114
  "**/*experimental/**",
127
115
  "**/*deprecated/**",
128
- "**/*misc/**",
129
- "**/*legacy/**",
116
+ "**/misc/**",
117
+ "**/legacy/**",
130
118
  ".git/**",
131
119
  ".github/**",
132
120
  ".next/**",
133
121
  ".vscode/**",
134
- "**/*obj/**",
135
- "**/*bin/**",
122
+ "**/obj/**",
123
+ "**/bin/**",
136
124
  "**/*node_modules/**",
137
125
  "*.log",
138
- "**/*test.*",
139
126
  "**/pnpm-lock.yaml",
140
127
  "**/yarn.lock",
141
128
  "**/package-lock.json",
@@ -143,7 +130,7 @@ export const DEFAULT_EXCLUDE_PATTERNS = [
143
130
  "**/bun.lockb",
144
131
  "**/bun.lock",
145
132
  "**/bun.lockb",
146
- ];
133
+ ]);
147
134
 
148
135
  // Supported languages for documentation
149
136
  export const SUPPORTED_LANGUAGES = [
@@ -206,7 +206,12 @@ export function wrapCode({ content }) {
206
206
  return `\`\`\`d2\n${content}\n\`\`\``;
207
207
  }
208
208
 
209
- export function replacePlaceholder({ content }) {
209
+ /**
210
+ * Replaces D2 code block with DIAGRAM_PLACEHOLDER.
211
+ * @param {string} content - Document content containing D2 code block
212
+ * @returns {Array} - [contentWithPlaceholder, originalCodeBlock]
213
+ */
214
+ export function replaceD2WithPlaceholder({ content }) {
210
215
  const [firstMatch] = Array.from(content.matchAll(codeBlockRegex));
211
216
  if (firstMatch) {
212
217
  const matchContent = firstMatch[0];
@@ -216,3 +221,37 @@ export function replacePlaceholder({ content }) {
216
221
 
217
222
  return [content, ""];
218
223
  }
224
+
225
+ /**
226
+ * Replaces DIAGRAM_PLACEHOLDER with D2 code block, ensuring proper spacing.
227
+ * @param {string} content - Document content containing DIAGRAM_PLACEHOLDER
228
+ * @param {string} diagramSourceCode - D2 diagram source code (without markdown wrapper)
229
+ * @returns {string} - Content with placeholder replaced by code block
230
+ */
231
+ export function replacePlaceholderWithD2({ content, diagramSourceCode }) {
232
+ if (!content || !diagramSourceCode) {
233
+ return content;
234
+ }
235
+
236
+ const placeholderIndex = content.indexOf(DIAGRAM_PLACEHOLDER);
237
+ if (placeholderIndex === -1) {
238
+ return content;
239
+ }
240
+
241
+ // Check if placeholder has newlines around it
242
+ const beforePlaceholder = content.substring(0, placeholderIndex);
243
+ const afterPlaceholder = content.substring(placeholderIndex + DIAGRAM_PLACEHOLDER.length);
244
+
245
+ const codeBlock = wrapCode({ content: diagramSourceCode });
246
+
247
+ // Add newlines if missing
248
+ let replacement = codeBlock;
249
+ if (beforePlaceholder && !beforePlaceholder.endsWith("\n")) {
250
+ replacement = `\n${replacement}`;
251
+ }
252
+ if (afterPlaceholder && !afterPlaceholder.startsWith("\n")) {
253
+ replacement = `${replacement}\n`;
254
+ }
255
+
256
+ return content.replace(DIAGRAM_PLACEHOLDER, replacement);
257
+ }
@@ -36,20 +36,14 @@ export function generateFileName(flatName, locale) {
36
36
 
37
37
  /**
38
38
  * Find a single item by path in documentation structure result and read its content
39
- * @param {Array} documentExecutionStructure - Array of documentation structure items
39
+ * @param {Array} documentStructure - Array of documentation structure items
40
40
  * @param {string} docPath - Document path to find (supports .md filenames)
41
41
  * @param {string} boardId - Board ID for fallback matching
42
42
  * @param {string} docsDir - Docs directory path for reading content
43
43
  * @param {string} locale - Main language locale (e.g., 'en', 'zh', 'fr')
44
44
  * @returns {Promise<Object|null>} Found item with content or null
45
45
  */
46
- export async function findItemByPath(
47
- documentExecutionStructure,
48
- docPath,
49
- boardId,
50
- docsDir,
51
- locale = "en",
52
- ) {
46
+ export async function findItemByPath(documentStructure, docPath, boardId, docsDir, locale = "en") {
53
47
  let foundItem = null;
54
48
  let fileName = null;
55
49
 
@@ -57,10 +51,10 @@ export async function findItemByPath(
57
51
  if (docPath.endsWith(".md")) {
58
52
  fileName = docPath;
59
53
  const flatName = fileNameToFlatPath(docPath);
60
- foundItem = findItemByFlatName(documentExecutionStructure, flatName);
54
+ foundItem = findItemByFlatName(documentStructure, flatName);
61
55
  } else {
62
56
  // First try direct path matching
63
- foundItem = documentExecutionStructure.find((item) => item.path === docPath);
57
+ foundItem = documentStructure.find((item) => item.path === docPath);
64
58
 
65
59
  // If not found and boardId is provided, try boardId-flattenedPath format matching
66
60
  if (!foundItem && boardId) {
@@ -70,7 +64,7 @@ export async function findItemByPath(
70
64
  const flattenedPath = docPath.substring(boardId.length + 1);
71
65
 
72
66
  // Find item by comparing flattened paths
73
- foundItem = documentExecutionStructure.find((item) => {
67
+ foundItem = documentStructure.find((item) => {
74
68
  // Convert item.path to flattened format (replace / with -)
75
69
  const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
76
70
  return itemFlattenedPath === flattenedPath;
@@ -127,10 +121,10 @@ export async function readFileContent(docsDir, fileName) {
127
121
  * Get main language markdown files from docs directory
128
122
  * @param {string} docsDir - Docs directory path
129
123
  * @param {string} locale - Main language locale (e.g., 'en', 'zh', 'fr')
130
- * @param {Array} documentExecutionStructure - Array of documentation structure items to determine file order
131
- * @returns {Promise<string[]>} Array of main language .md files ordered by documentExecutionStructure
124
+ * @param {Array} documentStructure - Array of documentation structure items to determine file order
125
+ * @returns {Promise<string[]>} Array of main language .md files ordered by documentStructure
132
126
  */
133
- export async function getMainLanguageFiles(docsDir, locale, documentExecutionStructure = null) {
127
+ export async function getMainLanguageFiles(docsDir, locale, documentStructure = null) {
134
128
  // Check if docsDir exists
135
129
  try {
136
130
  await access(docsDir);
@@ -162,17 +156,17 @@ export async function getMainLanguageFiles(docsDir, locale, documentExecutionStr
162
156
  }
163
157
  });
164
158
 
165
- // If documentExecutionStructure is provided, sort files according to the order in documentExecutionStructure
166
- if (documentExecutionStructure && Array.isArray(documentExecutionStructure)) {
159
+ // If documentStructure is provided, sort files according to the order in documentStructure
160
+ if (documentStructure && Array.isArray(documentStructure)) {
167
161
  // Create a map from flat file name to documentation structure order
168
162
  const orderMap = new Map();
169
- documentExecutionStructure.forEach((item, index) => {
163
+ documentStructure.forEach((item, index) => {
170
164
  const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
171
165
  const expectedFileName = generateFileName(itemFlattenedPath, locale);
172
166
  orderMap.set(expectedFileName, index);
173
167
  });
174
168
 
175
- // Sort filtered files based on their order in documentExecutionStructure
169
+ // Sort filtered files based on their order in documentStructure
176
170
  return filteredFiles.sort((a, b) => {
177
171
  const orderA = orderMap.get(a);
178
172
  const orderB = orderMap.get(b);
@@ -191,7 +185,7 @@ export async function getMainLanguageFiles(docsDir, locale, documentExecutionStr
191
185
  });
192
186
  }
193
187
 
194
- // If no documentExecutionStructure provided, return files in alphabetical order
188
+ // If no documentStructure provided, return files in alphabetical order
195
189
  return filteredFiles.sort();
196
190
  }
197
191
 
@@ -212,12 +206,12 @@ export function fileNameToFlatPath(fileName) {
212
206
 
213
207
  /**
214
208
  * Find documentation structure item by flattened file name
215
- * @param {Array} documentExecutionStructure - Array of documentation structure items
209
+ * @param {Array} documentStructure - Array of documentation structure items
216
210
  * @param {string} flatName - Flattened file name
217
211
  * @returns {Object|null} Found item or null
218
212
  */
219
- export function findItemByFlatName(documentExecutionStructure, flatName) {
220
- return documentExecutionStructure.find((item) => {
213
+ export function findItemByFlatName(documentStructure, flatName) {
214
+ return documentStructure.find((item) => {
221
215
  const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
222
216
  return itemFlattenedPath === flatName;
223
217
  });
@@ -226,11 +220,11 @@ export function findItemByFlatName(documentExecutionStructure, flatName) {
226
220
  /**
227
221
  * Process selected files and convert to found items with content
228
222
  * @param {string[]} selectedFiles - Array of selected file names
229
- * @param {Array} documentExecutionStructure - Array of documentation structure items
223
+ * @param {Array} documentStructure - Array of documentation structure items
230
224
  * @param {string} docsDir - Docs directory path
231
225
  * @returns {Promise<Object[]>} Array of found items with content
232
226
  */
233
- export async function processSelectedFiles(selectedFiles, documentExecutionStructure, docsDir) {
227
+ export async function processSelectedFiles(selectedFiles, documentStructure, docsDir) {
234
228
  const foundItems = [];
235
229
 
236
230
  for (const selectedFile of selectedFiles) {
@@ -241,7 +235,7 @@ export async function processSelectedFiles(selectedFiles, documentExecutionStruc
241
235
  const flatName = fileNameToFlatPath(selectedFile);
242
236
 
243
237
  // Try to find matching item by comparing flattened paths
244
- const foundItemByFile = findItemByFlatName(documentExecutionStructure, flatName);
238
+ const foundItemByFile = findItemByFlatName(documentStructure, flatName);
245
239
 
246
240
  if (foundItemByFile) {
247
241
  const result = {
@@ -188,7 +188,7 @@ export async function getFilesWithGlob(dir, includePatterns, excludePatterns, gi
188
188
  }
189
189
 
190
190
  // Add default exclusions if not already present
191
- const defaultExclusions = ["node_modules/**", "test/**", "temp/**"];
191
+ const defaultExclusions = ["node_modules/**", "temp/**"];
192
192
  for (const exclusion of defaultExclusions) {
193
193
  if (!allIgnorePatterns.includes(exclusion)) {
194
194
  allIgnorePatterns.push(exclusion);
@@ -1,32 +0,0 @@
1
- type: array
2
- items:
3
- type: object
4
- properties:
5
- title:
6
- type: string
7
- description:
8
- type: string
9
- path:
10
- type: string
11
- description: Path in URL format, cannot be empty, must start with /, no need to include language level, e.g., /zh/about should return /about
12
- parentId:
13
- type:
14
- - string
15
- - "null"
16
- translates:
17
- type: array
18
- items:
19
- type: object
20
- properties:
21
- language:
22
- type: string
23
- description: Language code, such as zh, en, ja, etc.
24
- sourceIds:
25
- type: array
26
- items:
27
- type: string
28
- description: Associated sourceId from `<data_sources>` for subsequent translation and content generation, must come from sourceId in `<data_sources>`, cannot have fake ids, cannot be empty
29
- required:
30
- - title
31
- - description
32
- - path
@@ -1,29 +0,0 @@
1
- export default function addTranslatesToStructure({
2
- originalDocumentStructure = [],
3
- translateLanguages = [],
4
- }) {
5
- const documentExecutionStructure = (originalDocumentStructure || []).map((item) => ({
6
- ...item,
7
- translates: (translateLanguages || []).map((lang) => ({ language: lang })),
8
- }));
9
-
10
- return {
11
- documentExecutionStructure,
12
- };
13
- }
14
-
15
- addTranslatesToStructure.inputSchema = {
16
- type: "object",
17
- properties: {
18
- originalDocumentStructure: { type: "array", items: { type: "object" } },
19
- translateLanguages: { type: "array", items: { type: "string" } },
20
- },
21
- required: ["originalDocumentStructure", "translateLanguages"],
22
- };
23
- addTranslatesToStructure.outputSchema = {
24
- type: "object",
25
- properties: {
26
- documentExecutionStructure: { type: "array" },
27
- },
28
- };
29
- addTranslatesToStructure.task_render_mode = "hide";