@aigne/doc-smith 0.9.8-alpha.2 → 0.9.8-alpha.4

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 (256) hide show
  1. package/CLAUDE.md +43 -0
  2. package/README.md +94 -250
  3. package/aigne.yaml +2 -149
  4. package/doc-smith/SKILL.md +117 -0
  5. package/doc-smith/references/changeset_schema.md +118 -0
  6. package/doc-smith/references/document_structure_schema.md +139 -0
  7. package/doc-smith/references/document_update_guide.md +193 -0
  8. package/doc-smith/references/structure_confirmation_guide.md +133 -0
  9. package/doc-smith/references/structure_planning_guide.md +146 -0
  10. package/doc-smith/references/user_intent_guide.md +172 -0
  11. package/doc-smith.yaml +114 -0
  12. package/main-system-prompt.md +56 -0
  13. package/package.json +3 -69
  14. package/scripts/README.md +90 -0
  15. package/scripts/install.sh +86 -0
  16. package/scripts/uninstall.sh +52 -0
  17. package/CHANGELOG.md +0 -994
  18. package/LICENSE +0 -93
  19. package/agentic-agents/common/base-info.md +0 -53
  20. package/agentic-agents/common/planner.md +0 -168
  21. package/agentic-agents/common/worker.md +0 -93
  22. package/agentic-agents/create/index.yaml +0 -118
  23. package/agentic-agents/create/objective.md +0 -44
  24. package/agentic-agents/create/set-custom-prompt.mjs +0 -27
  25. package/agentic-agents/detail/index.yaml +0 -95
  26. package/agentic-agents/detail/objective.md +0 -9
  27. package/agentic-agents/detail/set-custom-prompt.mjs +0 -88
  28. package/agentic-agents/predict-resources/index.yaml +0 -44
  29. package/agentic-agents/predict-resources/instructions.md +0 -61
  30. package/agentic-agents/structure/design-rules.md +0 -39
  31. package/agentic-agents/structure/index.yaml +0 -86
  32. package/agentic-agents/structure/objective.md +0 -14
  33. package/agentic-agents/structure/review-criteria.md +0 -55
  34. package/agentic-agents/structure/set-custom-prompt.mjs +0 -78
  35. package/agentic-agents/utils/init-workspace-cache.mjs +0 -171
  36. package/agentic-agents/utils/load-base-sources.mjs +0 -20
  37. package/agentic-agents/workspace-cache-sharing-design.md +0 -671
  38. package/agents/chat/chat-system.md +0 -38
  39. package/agents/chat/index.mjs +0 -59
  40. package/agents/chat/skills/generate-document.yaml +0 -15
  41. package/agents/chat/skills/list-documents.mjs +0 -15
  42. package/agents/chat/skills/update-document.yaml +0 -24
  43. package/agents/clear/choose-contents.mjs +0 -192
  44. package/agents/clear/clear-auth-tokens.mjs +0 -88
  45. package/agents/clear/clear-deployment-config.mjs +0 -49
  46. package/agents/clear/clear-document-config.mjs +0 -36
  47. package/agents/clear/clear-document-structure.mjs +0 -102
  48. package/agents/clear/clear-generated-docs.mjs +0 -142
  49. package/agents/clear/clear-media-description.mjs +0 -129
  50. package/agents/clear/index.yaml +0 -26
  51. package/agents/create/analyze-diagram-type-llm.yaml +0 -160
  52. package/agents/create/analyze-diagram-type.mjs +0 -297
  53. package/agents/create/check-document-structure.yaml +0 -30
  54. package/agents/create/check-need-generate-structure.mjs +0 -105
  55. package/agents/create/document-structure-tools/add-document.mjs +0 -85
  56. package/agents/create/document-structure-tools/delete-document.mjs +0 -116
  57. package/agents/create/document-structure-tools/move-document.mjs +0 -109
  58. package/agents/create/document-structure-tools/update-document.mjs +0 -84
  59. package/agents/create/generate-diagram-image.yaml +0 -60
  60. package/agents/create/generate-structure.yaml +0 -117
  61. package/agents/create/index.yaml +0 -49
  62. package/agents/create/refine-document-structure.yaml +0 -12
  63. package/agents/create/replace-d2-with-image.mjs +0 -625
  64. package/agents/create/update-document-structure.yaml +0 -54
  65. package/agents/create/user-add-document/add-documents-to-structure.mjs +0 -90
  66. package/agents/create/user-add-document/find-documents-to-add-links.yaml +0 -47
  67. package/agents/create/user-add-document/index.yaml +0 -46
  68. package/agents/create/user-add-document/prepare-documents-to-translate.mjs +0 -22
  69. package/agents/create/user-add-document/print-add-document-summary.mjs +0 -63
  70. package/agents/create/user-add-document/review-documents-with-new-links.mjs +0 -110
  71. package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +0 -78
  72. package/agents/create/user-remove-document/index.yaml +0 -40
  73. package/agents/create/user-remove-document/prepare-documents-to-translate.mjs +0 -22
  74. package/agents/create/user-remove-document/print-remove-document-summary.mjs +0 -53
  75. package/agents/create/user-remove-document/remove-documents-from-structure.mjs +0 -99
  76. package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +0 -115
  77. package/agents/create/user-review-document-structure.mjs +0 -140
  78. package/agents/create/utils/init-current-content.mjs +0 -34
  79. package/agents/create/utils/merge-document-structures.mjs +0 -30
  80. package/agents/evaluate/code-snippet.mjs +0 -97
  81. package/agents/evaluate/document-structure.yaml +0 -67
  82. package/agents/evaluate/document.yaml +0 -82
  83. package/agents/evaluate/generate-report.mjs +0 -85
  84. package/agents/evaluate/index.yaml +0 -46
  85. package/agents/history/index.yaml +0 -6
  86. package/agents/history/view.mjs +0 -78
  87. package/agents/init/check.mjs +0 -16
  88. package/agents/init/index.mjs +0 -275
  89. package/agents/init/validate.mjs +0 -16
  90. package/agents/localize/choose-language.mjs +0 -107
  91. package/agents/localize/index.yaml +0 -58
  92. package/agents/localize/record-translation-history.mjs +0 -23
  93. package/agents/localize/translate-document.yaml +0 -24
  94. package/agents/localize/translate-multilingual.yaml +0 -51
  95. package/agents/media/batch-generate-media-description.yaml +0 -46
  96. package/agents/media/generate-media-description.yaml +0 -50
  97. package/agents/media/load-media-description.mjs +0 -256
  98. package/agents/prefs/index.mjs +0 -203
  99. package/agents/publish/index.yaml +0 -26
  100. package/agents/publish/publish-docs.mjs +0 -356
  101. package/agents/publish/translate-meta.mjs +0 -103
  102. package/agents/schema/document-structure-item.yaml +0 -26
  103. package/agents/schema/document-structure-refine-item.yaml +0 -23
  104. package/agents/schema/document-structure.yaml +0 -29
  105. package/agents/update/batch-generate-document.yaml +0 -27
  106. package/agents/update/batch-update-document.yaml +0 -7
  107. package/agents/update/check-diagram-flag.mjs +0 -116
  108. package/agents/update/check-document.mjs +0 -162
  109. package/agents/update/check-generate-diagram.mjs +0 -106
  110. package/agents/update/check-sync-image-flag.mjs +0 -55
  111. package/agents/update/check-update-is-single.mjs +0 -53
  112. package/agents/update/document-tools/update-document-content.mjs +0 -303
  113. package/agents/update/generate-diagram.yaml +0 -63
  114. package/agents/update/generate-document.yaml +0 -70
  115. package/agents/update/handle-document-update.yaml +0 -103
  116. package/agents/update/index.yaml +0 -79
  117. package/agents/update/pre-check-generate-diagram.yaml +0 -44
  118. package/agents/update/save-and-translate-document.mjs +0 -76
  119. package/agents/update/sync-images-and-exit.mjs +0 -148
  120. package/agents/update/update-document-detail.yaml +0 -71
  121. package/agents/update/update-single/update-single-document-detail.mjs +0 -280
  122. package/agents/update/update-single-document.yaml +0 -7
  123. package/agents/update/user-review-document.mjs +0 -272
  124. package/agents/utils/action-success.mjs +0 -16
  125. package/agents/utils/analyze-document-feedback-intent.yaml +0 -32
  126. package/agents/utils/analyze-feedback-intent.mjs +0 -136
  127. package/agents/utils/analyze-structure-feedback-intent.yaml +0 -29
  128. package/agents/utils/check-detail-result.mjs +0 -38
  129. package/agents/utils/check-feedback-refiner.mjs +0 -81
  130. package/agents/utils/choose-docs.mjs +0 -293
  131. package/agents/utils/document-icon-generate.yaml +0 -52
  132. package/agents/utils/document-title-streamline.yaml +0 -48
  133. package/agents/utils/ensure-document-icons.mjs +0 -129
  134. package/agents/utils/exit.mjs +0 -6
  135. package/agents/utils/feedback-refiner.yaml +0 -50
  136. package/agents/utils/find-item-by-path.mjs +0 -114
  137. package/agents/utils/find-user-preferences-by-path.mjs +0 -37
  138. package/agents/utils/format-document-structure.mjs +0 -35
  139. package/agents/utils/generate-document-or-skip.mjs +0 -41
  140. package/agents/utils/handle-diagram-operations.mjs +0 -263
  141. package/agents/utils/load-all-document-content.mjs +0 -30
  142. package/agents/utils/load-document-all-content.mjs +0 -84
  143. package/agents/utils/load-sources.mjs +0 -405
  144. package/agents/utils/map-reasoning-effort-level.mjs +0 -15
  145. package/agents/utils/post-generate.mjs +0 -144
  146. package/agents/utils/read-current-document-content.mjs +0 -46
  147. package/agents/utils/save-doc-translation.mjs +0 -61
  148. package/agents/utils/save-doc.mjs +0 -88
  149. package/agents/utils/save-output.mjs +0 -26
  150. package/agents/utils/save-sidebar.mjs +0 -51
  151. package/agents/utils/skip-if-content-exists.mjs +0 -27
  152. package/agents/utils/streamline-document-titles-if-needed.mjs +0 -88
  153. package/agents/utils/transform-detail-data-sources.mjs +0 -45
  154. package/agents/utils/update-branding.mjs +0 -84
  155. package/assets/report-template/report.html +0 -198
  156. package/docs-mcp/analyze-content-relevance.yaml +0 -50
  157. package/docs-mcp/analyze-docs-relevance.yaml +0 -59
  158. package/docs-mcp/docs-search.yaml +0 -42
  159. package/docs-mcp/get-docs-detail.mjs +0 -41
  160. package/docs-mcp/get-docs-structure.mjs +0 -16
  161. package/docs-mcp/read-doc-content.mjs +0 -119
  162. package/prompts/common/document/content-rules-core.md +0 -20
  163. package/prompts/common/document/markdown-syntax-rules.md +0 -65
  164. package/prompts/common/document/media-file-list-usage-rules.md +0 -18
  165. package/prompts/common/document/openapi-usage-rules.md +0 -189
  166. package/prompts/common/document/role-and-personality.md +0 -16
  167. package/prompts/common/document/user-preferences.md +0 -9
  168. package/prompts/common/document-structure/conflict-resolution-guidance.md +0 -16
  169. package/prompts/common/document-structure/document-icon-generate.md +0 -116
  170. package/prompts/common/document-structure/document-structure-rules.md +0 -43
  171. package/prompts/common/document-structure/document-title-streamline.md +0 -86
  172. package/prompts/common/document-structure/glossary.md +0 -7
  173. package/prompts/common/document-structure/intj-traits.md +0 -5
  174. package/prompts/common/document-structure/openapi-usage-rules.md +0 -28
  175. package/prompts/common/document-structure/output-constraints.md +0 -18
  176. package/prompts/common/document-structure/user-locale-rules.md +0 -10
  177. package/prompts/common/document-structure/user-preferences.md +0 -9
  178. package/prompts/detail/custom/admonition-usage-rules.md +0 -94
  179. package/prompts/detail/custom/code-block-usage-rules.md +0 -163
  180. package/prompts/detail/custom/custom-components/x-card-usage-rules.md +0 -63
  181. package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +0 -83
  182. package/prompts/detail/custom/custom-components/x-field-desc-usage-rules.md +0 -120
  183. package/prompts/detail/custom/custom-components/x-field-group-usage-rules.md +0 -80
  184. package/prompts/detail/custom/custom-components/x-field-usage-rules.md +0 -189
  185. package/prompts/detail/custom/custom-components-usage-rules.md +0 -18
  186. package/prompts/detail/diagram/generate-image-system.md +0 -135
  187. package/prompts/detail/diagram/generate-image-user.md +0 -32
  188. package/prompts/detail/diagram/guide.md +0 -29
  189. package/prompts/detail/diagram/official-examples.md +0 -712
  190. package/prompts/detail/diagram/pre-check.md +0 -23
  191. package/prompts/detail/diagram/role-and-personality.md +0 -2
  192. package/prompts/detail/diagram/rules.md +0 -46
  193. package/prompts/detail/diagram/system-prompt.md +0 -1139
  194. package/prompts/detail/diagram/user-prompt.md +0 -43
  195. package/prompts/detail/generate/detail-example.md +0 -457
  196. package/prompts/detail/generate/document-rules.md +0 -45
  197. package/prompts/detail/generate/system-prompt.md +0 -61
  198. package/prompts/detail/generate/user-prompt.md +0 -99
  199. package/prompts/detail/jsx/rules.md +0 -6
  200. package/prompts/detail/update/system-prompt.md +0 -121
  201. package/prompts/detail/update/user-prompt.md +0 -41
  202. package/prompts/evaluate/document-structure.md +0 -93
  203. package/prompts/evaluate/document.md +0 -149
  204. package/prompts/media/media-description/system-prompt.md +0 -43
  205. package/prompts/media/media-description/user-prompt.md +0 -17
  206. package/prompts/structure/check-document-structure.md +0 -93
  207. package/prompts/structure/document-rules.md +0 -21
  208. package/prompts/structure/find-documents-to-add-links.md +0 -52
  209. package/prompts/structure/generate/system-prompt.md +0 -13
  210. package/prompts/structure/generate/user-prompt.md +0 -137
  211. package/prompts/structure/review/structure-review-system.md +0 -81
  212. package/prompts/structure/structure-example.md +0 -89
  213. package/prompts/structure/structure-getting-started.md +0 -10
  214. package/prompts/structure/update/system-prompt.md +0 -93
  215. package/prompts/structure/update/user-prompt.md +0 -43
  216. package/prompts/translate/admonition.md +0 -20
  217. package/prompts/translate/code-block.md +0 -33
  218. package/prompts/translate/glossary.md +0 -6
  219. package/prompts/translate/translate-document.md +0 -305
  220. package/prompts/utils/analyze-document-feedback-intent.md +0 -54
  221. package/prompts/utils/analyze-structure-feedback-intent.md +0 -43
  222. package/prompts/utils/feedback-refiner.md +0 -105
  223. package/types/document-schema.mjs +0 -55
  224. package/types/document-structure-schema.mjs +0 -261
  225. package/utils/auth-utils.mjs +0 -275
  226. package/utils/blocklet.mjs +0 -104
  227. package/utils/check-document-has-diagram.mjs +0 -95
  228. package/utils/conflict-detector.mjs +0 -149
  229. package/utils/constants/index.mjs +0 -620
  230. package/utils/constants/linter.mjs +0 -102
  231. package/utils/d2-utils.mjs +0 -198
  232. package/utils/debug.mjs +0 -3
  233. package/utils/delete-diagram-images.mjs +0 -99
  234. package/utils/deploy.mjs +0 -86
  235. package/utils/docs-finder-utils.mjs +0 -623
  236. package/utils/evaluate/report-utils.mjs +0 -132
  237. package/utils/extract-api.mjs +0 -32
  238. package/utils/file-utils.mjs +0 -960
  239. package/utils/history-utils.mjs +0 -203
  240. package/utils/icon-map.mjs +0 -26
  241. package/utils/image-compress.mjs +0 -75
  242. package/utils/kroki-utils.mjs +0 -173
  243. package/utils/linter/index.mjs +0 -50
  244. package/utils/load-config.mjs +0 -107
  245. package/utils/markdown/index.mjs +0 -26
  246. package/utils/markdown-checker.mjs +0 -694
  247. package/utils/mermaid-validator.mjs +0 -140
  248. package/utils/mermaid-worker-pool.mjs +0 -250
  249. package/utils/mermaid-worker.mjs +0 -233
  250. package/utils/openapi/index.mjs +0 -28
  251. package/utils/preferences-utils.mjs +0 -175
  252. package/utils/request.mjs +0 -10
  253. package/utils/store/index.mjs +0 -45
  254. package/utils/sync-diagram-to-translations.mjs +0 -262
  255. package/utils/upload-files.mjs +0 -231
  256. package/utils/utils.mjs +0 -1354
@@ -1,694 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import remarkGfm from "remark-gfm";
4
- import remarkLint from "remark-lint";
5
- import remarkParse from "remark-parse";
6
- import { isRelative } from "ufo";
7
- import { unified } from "unified";
8
- import { visit } from "unist-util-visit";
9
- import { VFile } from "vfile";
10
-
11
- import { isRemoteFile, isRemoteFileAvailable } from "./file-utils.mjs";
12
- import { validateMermaidSyntax } from "./mermaid-validator.mjs";
13
-
14
- /**
15
- * Parse table row and count actual columns
16
- * Properly handles content within cells, including pipes that are part of content
17
- * @param {string} line - The table row line to analyze
18
- * @returns {number} - Number of actual table columns
19
- */
20
- function countTableColumns(line) {
21
- const trimmed = line.trim();
22
-
23
- // Remove leading and trailing pipes if present
24
- const content = trimmed.startsWith("|") && trimmed.endsWith("|") ? trimmed.slice(1, -1) : trimmed;
25
-
26
- if (!content.trim()) {
27
- return 0;
28
- }
29
-
30
- const columns = [];
31
- let currentColumn = "";
32
- let i = 0;
33
- let inCode = false;
34
-
35
- while (i < content.length) {
36
- const char = content[i];
37
- const prevChar = i > 0 ? content[i - 1] : "";
38
-
39
- if (char === "`") {
40
- // Toggle code span state
41
- inCode = !inCode;
42
- currentColumn += char;
43
- } else if (char === "|" && !inCode && prevChar !== "\\") {
44
- // This is a column separator (not escaped and not in code)
45
- columns.push(currentColumn.trim());
46
- currentColumn = "";
47
- } else {
48
- currentColumn += char;
49
- }
50
-
51
- i++;
52
- }
53
-
54
- // Add the last column
55
- if (currentColumn.length > 0 || content.endsWith("|")) {
56
- columns.push(currentColumn.trim());
57
- }
58
-
59
- return columns.length;
60
- }
61
-
62
- const linkPattern = /(?<!!)\[([^\]]+)\]\(([^)]+)\)/g;
63
- const hrefPattern = /<x-card[^>]*\s+data-href\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/gi;
64
-
65
- /**
66
- * Check for dead links in markdown content
67
- * @param {string} markdown - The markdown content
68
- * @param {string} source - Source description for error reporting
69
- * @param {Set} allowedLinks - Set of allowed links
70
- * @param {Array} errorMessages - Array to push error messages to
71
- */
72
- function checkDeadLinks(markdown, source, allowedLinks, errorMessages) {
73
- const links = [];
74
-
75
- // Collect Markdown format links: [text](link)
76
- const linkRegex = new RegExp(linkPattern.source, linkPattern.flags);
77
- let match;
78
- while (true) {
79
- match = linkRegex.exec(markdown);
80
- if (match === null) break;
81
- links.push({ trimLink: match[2].trim(), display: `[${match[1]}](${match[2].trim()})` });
82
- }
83
-
84
- // Collect data-href attribute values from <x-card> elements
85
- const hrefRegex = new RegExp(hrefPattern.source, hrefPattern.flags);
86
- let attrMatch;
87
- while (true) {
88
- attrMatch = hrefRegex.exec(markdown);
89
- if (attrMatch === null) break;
90
- const attrValue = (attrMatch[1] || attrMatch[2] || attrMatch[3] || "").trim();
91
- if (attrValue) {
92
- // Preserve original format with quotes if present
93
- const originalMatch = attrMatch[0];
94
- links.push({ trimLink: attrValue, display: originalMatch });
95
- }
96
- }
97
-
98
- // Process all collected links with the same logic
99
- for (const { trimLink, display } of links) {
100
- // Only check links that processContent would process
101
- // Exclude external links and mailto
102
- if (/^(https?:\/\/|mailto:)/.test(trimLink)) continue;
103
-
104
- // Preserve anchors
105
- const [path, _hash] = trimLink.split("#");
106
-
107
- // Only process relative paths or paths starting with /
108
- if (!path) continue;
109
-
110
- // Check if this link is in the allowed links set
111
- if (!allowedLinks.has(path)) {
112
- errorMessages.push(
113
- `Found a dead link in ${source}: ${display}, ensure the link exists in the documentation structure path`,
114
- );
115
- }
116
- }
117
- }
118
-
119
- /**
120
- * Extract link from error message
121
- * @param {string} error - The error message
122
- * @returns {string} - The link
123
- */
124
- export function getLinkFromError(error) {
125
- if (!error || !error.includes("Found a dead link in")) {
126
- return "";
127
- }
128
-
129
- const linkRegex = new RegExp(linkPattern.source, linkPattern.flags);
130
- let match = linkRegex.exec(error);
131
- if (match) {
132
- return match[2].trim();
133
- }
134
-
135
- const hrefRegex = new RegExp(hrefPattern.source, hrefPattern.flags);
136
- match = hrefRegex.exec(error);
137
- if (match) {
138
- return (match[1] || match[2] || match[3] || "").trim();
139
- }
140
-
141
- return "";
142
- }
143
-
144
- /**
145
- * Check code block content for indentation consistency issues
146
- * @param {Array} codeBlockContent - Array of {line, lineNumber} objects from the code block
147
- * @param {number} codeBlockIndent - The indentation of the code block start marker (```)
148
- * @param {string} source - Source description for error reporting
149
- * @param {Array} errorMessages - Array to push error messages to
150
- */
151
- function checkCodeBlockIndentation(codeBlockContent, codeBlockIndent, source, errorMessages) {
152
- if (codeBlockContent.length === 0) return;
153
-
154
- // Filter out empty lines for analysis
155
- const nonEmptyLines = codeBlockContent.filter((item) => item.line.trim().length > 0);
156
- if (nonEmptyLines.length === 0) return;
157
-
158
- // The expected base indentation for code block content should match the code block marker
159
- const expectedBaseIndent = codeBlockIndent;
160
- const problematicLines = [];
161
-
162
- for (const item of nonEmptyLines) {
163
- const { line, lineNumber } = item;
164
- const match = line.match(/^(\s*)/);
165
- const currentIndent = match ? match[1].length : 0;
166
-
167
- // Check if current line has less indentation than expected
168
- // This indicates inconsistent indentation that may cause rendering issues
169
- if (currentIndent < expectedBaseIndent && expectedBaseIndent > 0) {
170
- problematicLines.push({
171
- lineNumber,
172
- line: line.trimEnd(),
173
- currentIndent,
174
- expectedIndent: expectedBaseIndent,
175
- });
176
- }
177
- }
178
-
179
- // Report indentation issues if found
180
- if (problematicLines.length > 0) {
181
- // Group consecutive issues to avoid spam
182
- const groupedIssues = [];
183
- let currentGroup = [problematicLines[0]];
184
-
185
- for (let i = 1; i < problematicLines.length; i++) {
186
- const current = problematicLines[i];
187
- const previous = problematicLines[i - 1];
188
-
189
- if (current.lineNumber - previous.lineNumber <= 2) {
190
- currentGroup.push(current);
191
- } else {
192
- groupedIssues.push(currentGroup);
193
- currentGroup = [current];
194
- }
195
- }
196
- groupedIssues.push(currentGroup);
197
-
198
- // Report the most significant groups
199
- for (const group of groupedIssues.slice(0, 2)) {
200
- // Limit to avoid spam
201
- const firstIssue = group[0];
202
- const lineNumbers =
203
- group.length > 1
204
- ? `lines ${group[0].lineNumber}-${group[group.length - 1].lineNumber}`
205
- : `line ${firstIssue.lineNumber}`;
206
-
207
- const issue = `insufficient indentation: ${firstIssue.currentIndent} spaces (expected: ${firstIssue.expectedIndent} spaces)`;
208
- errorMessages.push(
209
- `Found code block with inconsistent indentation in ${source} at ${lineNumbers}: ${issue}. This may cause rendering issues`,
210
- );
211
- }
212
- }
213
- }
214
-
215
- /**
216
- * Check for local images and verify their existence
217
- * @param {string} markdown - The markdown content
218
- * @param {string} source - Source description for error reporting
219
- * @param {Array} errorMessages - Array to push error messages to
220
- * @param {string} [markdownFilePath] - Path to the markdown file for resolving relative paths
221
- * @param {string} [baseDir] - Base directory for resolving relative paths (alternative to markdownFilePath)
222
- */
223
- function checkLocalImages(markdown, source, errorMessages, markdownFilePath, baseDir) {
224
- const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
225
- let match;
226
-
227
- while (true) {
228
- match = imageRegex.exec(markdown);
229
- if (match === null) break;
230
- const imagePath = match[2].trim();
231
- const altText = match[1];
232
-
233
- // Skip external URLs (http/https)
234
- if (/^https?:\/\//.test(imagePath)) continue;
235
-
236
- // Skip data URLs
237
- if (/^data:/.test(imagePath)) continue;
238
-
239
- // Check if it's a local path
240
- if (!imagePath.startsWith("/") && !imagePath.includes("://")) {
241
- // It's a relative local path, check if file exists
242
- try {
243
- let resolvedPath;
244
- if (markdownFilePath) {
245
- // Resolve relative to the markdown file's directory
246
- const markdownDir = path.dirname(markdownFilePath);
247
- resolvedPath = path.resolve(markdownDir, imagePath);
248
- } else if (baseDir) {
249
- // Resolve relative to the provided base directory
250
- resolvedPath = path.resolve(baseDir, imagePath);
251
- } else {
252
- // Fallback to current working directory
253
- resolvedPath = path.resolve(imagePath);
254
- }
255
-
256
- if (!fs.existsSync(resolvedPath)) {
257
- errorMessages.push(
258
- `Found invalid local image in ${source}: ![${altText}](${imagePath}) - only valid media resources can be used`,
259
- );
260
- }
261
- } catch {
262
- errorMessages.push(
263
- `Found invalid local image in ${source}: ![${altText}](${imagePath}) - only valid media resources can be used`,
264
- );
265
- }
266
- } else if (imagePath.startsWith("/")) {
267
- // Absolute local path
268
- try {
269
- if (!fs.existsSync(imagePath)) {
270
- errorMessages.push(
271
- `Found invalid local image in ${source}: ![${altText}](${imagePath}) - only valid media resources can be used`,
272
- );
273
- }
274
- } catch {
275
- errorMessages.push(
276
- `Found invalid local image in ${source}: ![${altText}](${imagePath}) - only valid media resources can be used`,
277
- );
278
- }
279
- }
280
- }
281
- }
282
-
283
- async function checkRemoteImages(markdown, source, errorMessages) {
284
- const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g;
285
- let match;
286
-
287
- while (true) {
288
- match = imageRegex.exec(markdown);
289
- if (match === null) break;
290
- const imagePath = match[2].trim();
291
- const altText = match[1];
292
-
293
- if (isRelative(imagePath)) continue;
294
- if (imagePath.startsWith("/")) continue;
295
-
296
- // Skip data URLs
297
- if (/^data:/.test(imagePath)) continue;
298
-
299
- if (isRemoteFile(imagePath)) {
300
- const isAvailable = await isRemoteFileAvailable(imagePath);
301
- if (isAvailable) continue;
302
- else {
303
- errorMessages.push(
304
- `Found invalid remote image in ${source}: ![${altText}](${imagePath}) - only valid media resources can be used`,
305
- );
306
- }
307
- }
308
- }
309
- }
310
-
311
- async function checkXCardImage(markdown, source, errorMessages, markdownFilePath, baseDir) {
312
- const imageRegex = /data-image="([^"]*)"/g;
313
- let match;
314
-
315
- while (true) {
316
- match = imageRegex.exec(markdown);
317
- if (match === null) break;
318
- const imagePath = match[1].trim();
319
-
320
- // Skip data URLs
321
- if (/^data:/.test(imagePath)) continue;
322
-
323
- if (isRelative(imagePath)) {
324
- try {
325
- let resolvedPath;
326
- if (markdownFilePath) {
327
- // Resolve relative to the markdown file's directory
328
- const markdownDir = path.dirname(markdownFilePath);
329
- resolvedPath = path.resolve(markdownDir, imagePath);
330
- } else if (baseDir) {
331
- // Resolve relative to the provided base directory
332
- resolvedPath = path.resolve(baseDir, imagePath);
333
- } else {
334
- // Fallback to current working directory
335
- resolvedPath = path.resolve(imagePath);
336
- }
337
-
338
- if (!fs.existsSync(resolvedPath)) {
339
- errorMessages.push(
340
- `Found invalid local image in ${source}: ${imagePath} - only valid media resources can be used`,
341
- );
342
- }
343
- } catch {
344
- errorMessages.push(
345
- `Found invalid local image in ${source}: ${imagePath} - only valid media resources can be used`,
346
- );
347
- }
348
- } else if (imagePath.startsWith("/")) {
349
- try {
350
- if (!fs.existsSync(imagePath)) {
351
- errorMessages.push(
352
- `Found invalid local image in ${source}: ${imagePath} - only valid media resources can be used`,
353
- );
354
- }
355
- } catch {
356
- errorMessages.push(
357
- `Found invalid local image in ${source}: ${imagePath} - only valid media resources can be used`,
358
- );
359
- }
360
- } else if (isRemoteFile(imagePath)) {
361
- const isAvailable = await isRemoteFileAvailable(imagePath);
362
- if (isAvailable) continue;
363
- else {
364
- errorMessages.push(
365
- `Found invalid remote image in ${source}: ${imagePath} - only valid media resources can be used`,
366
- );
367
- }
368
- }
369
- }
370
- }
371
-
372
- /**
373
- * Check content structure and formatting issues
374
- * @param {string} markdown - The markdown content
375
- * @param {string} source - Source description for error reporting
376
- * @param {Array} errorMessages - Array to push error messages to
377
- */
378
- function checkContentStructure(markdown, source, errorMessages) {
379
- const lines = markdown.split("\n");
380
- const allCodeBlockRegex = /^\s*```.*$/;
381
-
382
- // State variables for different checks
383
- let inCodeBlock = false;
384
- let inAnyCodeBlock = false;
385
- let anyCodeBlockStartLine = 0;
386
- let codeBlockContent = [];
387
- let codeBlockIndent = 0;
388
-
389
- for (let i = 0; i < lines.length; i++) {
390
- const line = lines[i];
391
- const lineNumber = i + 1;
392
-
393
- // Check for any code block markers (for incomplete code block detection)
394
- if (allCodeBlockRegex.test(line)) {
395
- if (!inAnyCodeBlock) {
396
- // Starting a new code block
397
- inAnyCodeBlock = true;
398
- anyCodeBlockStartLine = lineNumber;
399
- inCodeBlock = true;
400
- codeBlockContent = [];
401
-
402
- // Capture the indentation of the code block start marker
403
- const match = line.match(/^(\s*)/);
404
- codeBlockIndent = match ? match[1].length : 0;
405
- } else {
406
- // Ending the code block
407
- inAnyCodeBlock = false;
408
-
409
- if (inCodeBlock) {
410
- // Check code block content for indentation issues
411
- checkCodeBlockIndentation(codeBlockContent, codeBlockIndent, source, errorMessages);
412
- inCodeBlock = false;
413
- }
414
- }
415
- } else if (inCodeBlock) {
416
- // Collect code block content for indentation analysis
417
- codeBlockContent.push({ line, lineNumber });
418
- }
419
- }
420
-
421
- // Check for incomplete code blocks (started but not closed)
422
- if (inAnyCodeBlock) {
423
- errorMessages.push(
424
- `Found incomplete code block in ${source} starting at line ${anyCodeBlockStartLine}: code block opened with \`\`\` but never closed. Please return the complete content`,
425
- );
426
- }
427
-
428
- // Check single line content (this needs to be done after the loop)
429
- const newlineCount = (markdown.match(/\n/g) || []).length;
430
- if (newlineCount === 0 && markdown.trim().length > 0) {
431
- errorMessages.push(
432
- `Found single line content in ${source}: content appears to be on only one line, check for missing line breaks`,
433
- );
434
- }
435
-
436
- // Check if content ends with proper punctuation (indicating completeness)
437
- const validEndingPunctuation = [".", "。", ")", "|", "*", ">", "`", "!", ")"];
438
- const trimmedText = markdown.trim();
439
- const hasValidEnding = validEndingPunctuation.some((punct) => trimmedText.endsWith(punct));
440
-
441
- if (trimmedText.length > 0 && !hasValidEnding) {
442
- errorMessages.push(
443
- `Found incomplete content in ${source}: content does not end with proper punctuation [${validEndingPunctuation.join(", ")}]. Please return the complete content`,
444
- );
445
- }
446
- }
447
-
448
- /**
449
- * Check markdown content for formatting issues and mermaid syntax errors
450
- * @param {string} markdown - The markdown content to check
451
- * @param {string} [source] - Source description for error reporting (e.g., "result")
452
- * @param {Object} [options] - Additional options for validation
453
- * @param {Array} [options.allowedLinks] - Set of allowed links for link validation
454
- * @param {string} [options.filePath] - Path to the markdown file for resolving relative image paths
455
- * @param {string} [options.baseDir] - Base directory for resolving relative image paths (alternative to filePath)
456
- * @returns {Promise<Array<string>>} - Array of error messages in check-detail-result format
457
- */
458
- export async function checkMarkdown(markdown, source = "content", options = {}) {
459
- const file = new VFile({ value: markdown, path: source });
460
- const errorMessages = [];
461
-
462
- try {
463
- // Extract allowed links, file path, and base directory from options
464
- const { allowedLinks, filePath, baseDir } = options;
465
-
466
- // Create unified processor with markdown parsing and linting
467
- // Use individual rules instead of presets to have better control
468
- const processor = unified()
469
- .use(remarkParse)
470
- .use(remarkGfm)
471
- .use(remarkLint)
472
- // Add specific useful rules, avoiding overly strict formatting ones
473
- .use(remarkLint, [
474
- // Content quality rules (keep these)
475
- "no-duplicate-headings",
476
- "no-duplicate-definitions",
477
- "no-unused-definitions",
478
- "no-undefined-references",
479
-
480
- // Structural rules (keep these)
481
- "no-heading-content-indent",
482
- "no-heading-indent",
483
- "no-multiple-toplevel-headings",
484
-
485
- // Link rules (keep these)
486
- "no-reference-like-url",
487
- "no-unneeded-full-reference-image",
488
- "no-unneeded-full-reference-link",
489
- "code-block-style",
490
-
491
- // Skip overly strict formatting rules that don't affect rendering:
492
- // - final-newline (missing newline at end)
493
- // - list-item-indent (flexible list spacing)
494
- // - table-cell-padding (flexible table spacing)
495
- // - emphasis-marker (allow both * and _)
496
- // - strong-marker (allow both ** and __)
497
- ]);
498
-
499
- // Parse markdown content to AST
500
- const ast = processor.parse(file);
501
-
502
- // 1. Check dead links if allowedLinks is provided
503
- if (allowedLinks) {
504
- checkDeadLinks(markdown, source, allowedLinks, errorMessages);
505
- }
506
-
507
- // 2. Check local images existence
508
- checkLocalImages(markdown, source, errorMessages, filePath, baseDir);
509
-
510
- // 3. Check remote images existence
511
- await checkRemoteImages(markdown, source, errorMessages);
512
-
513
- await checkXCardImage(markdown, source, errorMessages, filePath, baseDir);
514
-
515
- // 4. Check content structure and formatting issues
516
- checkContentStructure(markdown, source, errorMessages);
517
-
518
- // Check mermaid code blocks and other custom validations
519
- const mermaidChecks = [];
520
- visit(ast, "code", (node) => {
521
- if (node.lang) {
522
- const line = node.position?.start?.line || "unknown";
523
-
524
- if (node.lang.toLowerCase() === "mermaid") {
525
- // Check for mermaid syntax errors
526
- mermaidChecks.push(
527
- validateMermaidSyntax(node.value).catch((error) => {
528
- const errorMessage =
529
- error?.message || String(error) || "Unknown mermaid syntax error";
530
-
531
- // Format mermaid error in check-detail-result style
532
- errorMessages.push(
533
- `Found Mermaid syntax error in ${source} at line ${line}: ${errorMessage}`,
534
- );
535
- }),
536
- );
537
-
538
- // Check for specific mermaid rendering issues
539
- const mermaidContent = node.value;
540
-
541
- // Check for backticks in node labels
542
- const nodeLabelRegex = /[A-Za-z0-9_]+\["([^"]*`[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*`[^}]*)"}/g;
543
- let match;
544
- match = nodeLabelRegex.exec(mermaidContent);
545
- while (match !== null) {
546
- const label = match[1] || match[2];
547
- errorMessages.push(
548
- `Found backticks in Mermaid node label in ${source} at line ${line}: "${label}" - backticks in node labels cause rendering issues in Mermaid diagrams`,
549
- );
550
- match = nodeLabelRegex.exec(mermaidContent);
551
- }
552
-
553
- // Check for numbered list format in edge descriptions
554
- const edgeDescriptionRegex = /--\s*"([^"]*)"\s*-->/g;
555
- let edgeMatch;
556
- edgeMatch = edgeDescriptionRegex.exec(mermaidContent);
557
- while (edgeMatch !== null) {
558
- const description = edgeMatch[1];
559
- if (/^\d+\.\s/.test(description)) {
560
- errorMessages.push(
561
- `Unsupported markdown: list - Found numbered list format in Mermaid edge description in ${source} at line ${line}: "${description}" - numbered lists in edge descriptions are not supported`,
562
- );
563
- }
564
- edgeMatch = edgeDescriptionRegex.exec(mermaidContent);
565
- }
566
-
567
- // Check for numbered list format in node labels (for both [] and {} syntax)
568
- const nodeLabelWithNumberRegex =
569
- /[A-Za-z0-9_]+\["([^"]*\d+\.\s[^"]*)"\]|[A-Za-z0-9_]+{"([^}]*\d+\.\s[^}]*)"}/g;
570
- let numberMatch;
571
- numberMatch = nodeLabelWithNumberRegex.exec(mermaidContent);
572
- while (numberMatch !== null) {
573
- const label = numberMatch[1] || numberMatch[2];
574
- // Check if the label contains numbered list format
575
- if (/\d+\.\s/.test(label)) {
576
- errorMessages.push(
577
- `Unsupported markdown: list - Found numbered list format in Mermaid node label in ${source} at line ${line}: "${label}" - numbered lists in node labels cause rendering issues`,
578
- );
579
- }
580
- numberMatch = nodeLabelWithNumberRegex.exec(mermaidContent);
581
- }
582
-
583
- // Check for special characters in node labels that should be quoted
584
- const nodeWithSpecialCharsRegex = /([A-Za-z0-9_]+)\[([^\]]*[(){}:;,\-\s.][^\]]*)\]/g;
585
- let specialCharMatch;
586
- specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
587
- while (specialCharMatch !== null) {
588
- const nodeId = specialCharMatch[1];
589
- const label = specialCharMatch[2];
590
-
591
- // Check if label contains special characters but is not quoted
592
- if (!/^".*"$/.test(label)) {
593
- // List of characters that typically need quoting
594
- const specialChars = ["(", ")", "{", "}", ":", ";", ",", "-", "."];
595
- const foundSpecialChars = specialChars.filter((char) => label.includes(char));
596
-
597
- if (foundSpecialChars.length > 0) {
598
- errorMessages.push(
599
- `Found unquoted special characters in Mermaid node label in ${source} at line ${line}: "${label}" contains ${foundSpecialChars.join(
600
- ", ",
601
- )} - node labels with special characters should be quoted like ${nodeId}["${label}"]`,
602
- );
603
- }
604
- }
605
- specialCharMatch = nodeWithSpecialCharsRegex.exec(mermaidContent);
606
- }
607
- }
608
- }
609
- });
610
-
611
- // Check table separators in original text (since AST normalizes them)
612
- const lines = markdown.split("\n");
613
- for (let i = 0; i < lines.length; i++) {
614
- const line = lines[i];
615
-
616
- // Check for table separator lines (lines with | and -)
617
- if (/^\s*\|.*-.*\|\s*$/.test(line)) {
618
- // Count separator columns
619
- const separatorColumns = countTableColumns(line);
620
-
621
- // Check if previous line looks like a table header
622
- if (i > 0) {
623
- const prevLine = lines[i - 1];
624
- if (/^\s*\|.*\|\s*$/.test(prevLine)) {
625
- // Count header columns
626
- const headerColumns = countTableColumns(prevLine);
627
-
628
- // Check for column count mismatch
629
- if (separatorColumns !== headerColumns) {
630
- errorMessages.push(
631
- `Found table separator with mismatched column count in ${source} at line ${
632
- i + 1
633
- }: separator has ${separatorColumns} columns but header has ${headerColumns} columns - this causes table rendering issues`,
634
- );
635
- }
636
-
637
- // Also check if next line exists and has different column count
638
- if (i + 1 < lines.length) {
639
- const nextLine = lines[i + 1];
640
- if (/^\s*\|.*\|\s*$/.test(nextLine)) {
641
- const dataColumns = countTableColumns(nextLine);
642
- if (separatorColumns !== dataColumns) {
643
- errorMessages.push(
644
- `Found table data row with mismatched column count in ${source} at line ${
645
- i + 2
646
- }: data row has ${dataColumns} columns but separator defines ${separatorColumns} columns - this causes table rendering issues`,
647
- );
648
- }
649
- }
650
- }
651
- }
652
- }
653
- }
654
- }
655
-
656
- // Wait for all mermaid checks to complete
657
- await Promise.all(mermaidChecks);
658
-
659
- // Run markdown linting rules
660
- await processor.run(ast, file);
661
-
662
- // Format messages in check-detail-result style
663
- file.messages.forEach((message) => {
664
- const line = message.line || "unknown";
665
- const reason = message.reason || "Unknown markdown issue";
666
- const ruleId = message.ruleId || message.source || "markdown";
667
-
668
- // Categorize different types of issues
669
- let errorType = "markdown formatting";
670
- if (ruleId.includes("table")) {
671
- errorType = "table";
672
- } else if (ruleId.includes("code")) {
673
- errorType = "code block";
674
- } else if (ruleId.includes("link")) {
675
- errorType = "link";
676
- }
677
-
678
- // Format error message similar to check-detail-result style
679
- if (line !== "unknown") {
680
- errorMessages.push(
681
- `Found ${errorType} issue in ${source} at line ${line}: ${reason} (${ruleId})`,
682
- );
683
- } else {
684
- errorMessages.push(`Found ${errorType} issue in ${source}: ${reason} (${ruleId})`);
685
- }
686
- });
687
-
688
- return errorMessages;
689
- } catch (error) {
690
- // Handle any unexpected errors during processing
691
- errorMessages.push(`Found markdown processing error in ${source}: ${error.message}`);
692
- return errorMessages;
693
- }
694
- }