@aigne/doc-smith 0.9.8-alpha.3 → 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.
- package/CLAUDE.md +43 -0
- package/README.md +94 -250
- package/aigne.yaml +2 -149
- package/doc-smith/SKILL.md +117 -0
- package/doc-smith/references/changeset_schema.md +118 -0
- package/doc-smith/references/document_structure_schema.md +139 -0
- package/doc-smith/references/document_update_guide.md +193 -0
- package/doc-smith/references/structure_confirmation_guide.md +133 -0
- package/doc-smith/references/structure_planning_guide.md +146 -0
- package/doc-smith/references/user_intent_guide.md +172 -0
- package/doc-smith.yaml +114 -0
- package/main-system-prompt.md +56 -0
- package/package.json +3 -69
- package/scripts/README.md +90 -0
- package/scripts/install.sh +86 -0
- package/scripts/uninstall.sh +52 -0
- package/CHANGELOG.md +0 -994
- package/LICENSE +0 -93
- package/agentic-agents/common/base-info.md +0 -53
- package/agentic-agents/common/completer.md +0 -54
- package/agentic-agents/common/planner.md +0 -168
- package/agentic-agents/common/worker.md +0 -93
- package/agentic-agents/create/index.yaml +0 -129
- package/agentic-agents/create/objective.md +0 -44
- package/agentic-agents/create/set-custom-prompt.mjs +0 -27
- package/agentic-agents/detail/index.yaml +0 -95
- package/agentic-agents/detail/objective.md +0 -9
- package/agentic-agents/detail/set-custom-prompt.mjs +0 -88
- package/agentic-agents/predict-resources/index.yaml +0 -44
- package/agentic-agents/predict-resources/instructions.md +0 -61
- package/agentic-agents/structure/design-rules.md +0 -39
- package/agentic-agents/structure/index.yaml +0 -86
- package/agentic-agents/structure/objective.md +0 -14
- package/agentic-agents/structure/review-criteria.md +0 -55
- package/agentic-agents/structure/set-custom-prompt.mjs +0 -78
- package/agentic-agents/utils/init-workspace-cache.mjs +0 -171
- package/agentic-agents/utils/load-base-sources.mjs +0 -20
- package/agentic-agents/workspace-cache-sharing-design.md +0 -671
- package/agents/chat/chat-system.md +0 -38
- package/agents/chat/index.mjs +0 -59
- package/agents/chat/skills/generate-document.yaml +0 -15
- package/agents/chat/skills/list-documents.mjs +0 -15
- package/agents/chat/skills/update-document.yaml +0 -24
- package/agents/clear/choose-contents.mjs +0 -192
- package/agents/clear/clear-auth-tokens.mjs +0 -88
- package/agents/clear/clear-deployment-config.mjs +0 -49
- package/agents/clear/clear-document-config.mjs +0 -36
- package/agents/clear/clear-document-structure.mjs +0 -102
- package/agents/clear/clear-generated-docs.mjs +0 -142
- package/agents/clear/clear-media-description.mjs +0 -129
- package/agents/clear/index.yaml +0 -26
- package/agents/create/analyze-diagram-type-llm.yaml +0 -160
- package/agents/create/analyze-diagram-type.mjs +0 -297
- package/agents/create/check-document-structure.yaml +0 -30
- package/agents/create/check-need-generate-structure.mjs +0 -105
- package/agents/create/document-structure-tools/add-document.mjs +0 -85
- package/agents/create/document-structure-tools/delete-document.mjs +0 -116
- package/agents/create/document-structure-tools/move-document.mjs +0 -109
- package/agents/create/document-structure-tools/update-document.mjs +0 -84
- package/agents/create/generate-diagram-image.yaml +0 -60
- package/agents/create/generate-structure.yaml +0 -117
- package/agents/create/index.yaml +0 -49
- package/agents/create/refine-document-structure.yaml +0 -12
- package/agents/create/replace-d2-with-image.mjs +0 -625
- package/agents/create/update-document-structure.yaml +0 -54
- package/agents/create/user-add-document/add-documents-to-structure.mjs +0 -90
- package/agents/create/user-add-document/find-documents-to-add-links.yaml +0 -47
- package/agents/create/user-add-document/index.yaml +0 -46
- package/agents/create/user-add-document/prepare-documents-to-translate.mjs +0 -22
- package/agents/create/user-add-document/print-add-document-summary.mjs +0 -63
- package/agents/create/user-add-document/review-documents-with-new-links.mjs +0 -110
- package/agents/create/user-remove-document/find-documents-with-invalid-links.mjs +0 -78
- package/agents/create/user-remove-document/index.yaml +0 -40
- package/agents/create/user-remove-document/prepare-documents-to-translate.mjs +0 -22
- package/agents/create/user-remove-document/print-remove-document-summary.mjs +0 -53
- package/agents/create/user-remove-document/remove-documents-from-structure.mjs +0 -99
- package/agents/create/user-remove-document/review-documents-with-invalid-links.mjs +0 -115
- package/agents/create/user-review-document-structure.mjs +0 -140
- package/agents/create/utils/init-current-content.mjs +0 -34
- package/agents/create/utils/merge-document-structures.mjs +0 -30
- package/agents/evaluate/code-snippet.mjs +0 -97
- package/agents/evaluate/document-structure.yaml +0 -67
- package/agents/evaluate/document.yaml +0 -82
- package/agents/evaluate/generate-report.mjs +0 -85
- package/agents/evaluate/index.yaml +0 -46
- package/agents/history/index.yaml +0 -6
- package/agents/history/view.mjs +0 -78
- package/agents/init/check.mjs +0 -16
- package/agents/init/index.mjs +0 -275
- package/agents/init/validate.mjs +0 -16
- package/agents/localize/choose-language.mjs +0 -107
- package/agents/localize/index.yaml +0 -58
- package/agents/localize/record-translation-history.mjs +0 -23
- package/agents/localize/translate-document.yaml +0 -24
- package/agents/localize/translate-multilingual.yaml +0 -51
- package/agents/media/batch-generate-media-description.yaml +0 -46
- package/agents/media/generate-media-description.yaml +0 -50
- package/agents/media/load-media-description.mjs +0 -256
- package/agents/prefs/index.mjs +0 -203
- package/agents/publish/index.yaml +0 -26
- package/agents/publish/publish-docs.mjs +0 -356
- package/agents/publish/translate-meta.mjs +0 -103
- package/agents/schema/document-structure-item.yaml +0 -26
- package/agents/schema/document-structure-refine-item.yaml +0 -23
- package/agents/schema/document-structure.yaml +0 -29
- package/agents/update/batch-generate-document.yaml +0 -27
- package/agents/update/batch-update-document.yaml +0 -7
- package/agents/update/check-diagram-flag.mjs +0 -116
- package/agents/update/check-document.mjs +0 -162
- package/agents/update/check-generate-diagram.mjs +0 -106
- package/agents/update/check-sync-image-flag.mjs +0 -55
- package/agents/update/check-update-is-single.mjs +0 -53
- package/agents/update/document-tools/update-document-content.mjs +0 -303
- package/agents/update/generate-diagram.yaml +0 -63
- package/agents/update/generate-document.yaml +0 -70
- package/agents/update/handle-document-update.yaml +0 -103
- package/agents/update/index.yaml +0 -79
- package/agents/update/pre-check-generate-diagram.yaml +0 -44
- package/agents/update/save-and-translate-document.mjs +0 -76
- package/agents/update/sync-images-and-exit.mjs +0 -148
- package/agents/update/update-document-detail.yaml +0 -71
- package/agents/update/update-single/update-single-document-detail.mjs +0 -280
- package/agents/update/update-single-document.yaml +0 -7
- package/agents/update/user-review-document.mjs +0 -272
- package/agents/utils/action-success.mjs +0 -16
- package/agents/utils/analyze-document-feedback-intent.yaml +0 -32
- package/agents/utils/analyze-feedback-intent.mjs +0 -136
- package/agents/utils/analyze-structure-feedback-intent.yaml +0 -29
- package/agents/utils/check-detail-result.mjs +0 -38
- package/agents/utils/check-feedback-refiner.mjs +0 -81
- package/agents/utils/choose-docs.mjs +0 -293
- package/agents/utils/document-icon-generate.yaml +0 -52
- package/agents/utils/document-title-streamline.yaml +0 -48
- package/agents/utils/ensure-document-icons.mjs +0 -129
- package/agents/utils/exit.mjs +0 -6
- package/agents/utils/feedback-refiner.yaml +0 -50
- package/agents/utils/find-item-by-path.mjs +0 -114
- package/agents/utils/find-user-preferences-by-path.mjs +0 -37
- package/agents/utils/format-document-structure.mjs +0 -35
- package/agents/utils/generate-document-or-skip.mjs +0 -41
- package/agents/utils/handle-diagram-operations.mjs +0 -263
- package/agents/utils/load-all-document-content.mjs +0 -30
- package/agents/utils/load-document-all-content.mjs +0 -84
- package/agents/utils/load-sources.mjs +0 -405
- package/agents/utils/map-reasoning-effort-level.mjs +0 -15
- package/agents/utils/post-generate.mjs +0 -144
- package/agents/utils/read-current-document-content.mjs +0 -46
- package/agents/utils/save-doc-translation.mjs +0 -61
- package/agents/utils/save-doc.mjs +0 -88
- package/agents/utils/save-output.mjs +0 -26
- package/agents/utils/save-sidebar.mjs +0 -51
- package/agents/utils/skip-if-content-exists.mjs +0 -27
- package/agents/utils/streamline-document-titles-if-needed.mjs +0 -88
- package/agents/utils/transform-detail-data-sources.mjs +0 -45
- package/agents/utils/update-branding.mjs +0 -84
- package/assets/report-template/report.html +0 -198
- package/docs-mcp/analyze-content-relevance.yaml +0 -50
- package/docs-mcp/analyze-docs-relevance.yaml +0 -59
- package/docs-mcp/docs-search.yaml +0 -42
- package/docs-mcp/get-docs-detail.mjs +0 -41
- package/docs-mcp/get-docs-structure.mjs +0 -16
- package/docs-mcp/read-doc-content.mjs +0 -119
- package/prompts/common/document/content-rules-core.md +0 -20
- package/prompts/common/document/markdown-syntax-rules.md +0 -65
- package/prompts/common/document/media-file-list-usage-rules.md +0 -18
- package/prompts/common/document/openapi-usage-rules.md +0 -189
- package/prompts/common/document/role-and-personality.md +0 -16
- package/prompts/common/document/user-preferences.md +0 -9
- package/prompts/common/document-structure/conflict-resolution-guidance.md +0 -16
- package/prompts/common/document-structure/document-icon-generate.md +0 -116
- package/prompts/common/document-structure/document-structure-rules.md +0 -43
- package/prompts/common/document-structure/document-title-streamline.md +0 -86
- package/prompts/common/document-structure/glossary.md +0 -7
- package/prompts/common/document-structure/intj-traits.md +0 -5
- package/prompts/common/document-structure/openapi-usage-rules.md +0 -28
- package/prompts/common/document-structure/output-constraints.md +0 -18
- package/prompts/common/document-structure/user-locale-rules.md +0 -10
- package/prompts/common/document-structure/user-preferences.md +0 -9
- package/prompts/detail/custom/admonition-usage-rules.md +0 -94
- package/prompts/detail/custom/code-block-usage-rules.md +0 -163
- package/prompts/detail/custom/custom-components/x-card-usage-rules.md +0 -63
- package/prompts/detail/custom/custom-components/x-cards-usage-rules.md +0 -83
- package/prompts/detail/custom/custom-components/x-field-desc-usage-rules.md +0 -120
- package/prompts/detail/custom/custom-components/x-field-group-usage-rules.md +0 -80
- package/prompts/detail/custom/custom-components/x-field-usage-rules.md +0 -189
- package/prompts/detail/custom/custom-components-usage-rules.md +0 -18
- package/prompts/detail/diagram/generate-image-system.md +0 -135
- package/prompts/detail/diagram/generate-image-user.md +0 -32
- package/prompts/detail/diagram/guide.md +0 -29
- package/prompts/detail/diagram/official-examples.md +0 -712
- package/prompts/detail/diagram/pre-check.md +0 -23
- package/prompts/detail/diagram/role-and-personality.md +0 -2
- package/prompts/detail/diagram/rules.md +0 -46
- package/prompts/detail/diagram/system-prompt.md +0 -1139
- package/prompts/detail/diagram/user-prompt.md +0 -43
- package/prompts/detail/generate/detail-example.md +0 -457
- package/prompts/detail/generate/document-rules.md +0 -45
- package/prompts/detail/generate/system-prompt.md +0 -61
- package/prompts/detail/generate/user-prompt.md +0 -99
- package/prompts/detail/jsx/rules.md +0 -6
- package/prompts/detail/update/system-prompt.md +0 -121
- package/prompts/detail/update/user-prompt.md +0 -41
- package/prompts/evaluate/document-structure.md +0 -93
- package/prompts/evaluate/document.md +0 -149
- package/prompts/media/media-description/system-prompt.md +0 -43
- package/prompts/media/media-description/user-prompt.md +0 -17
- package/prompts/structure/check-document-structure.md +0 -93
- package/prompts/structure/document-rules.md +0 -21
- package/prompts/structure/find-documents-to-add-links.md +0 -52
- package/prompts/structure/generate/system-prompt.md +0 -13
- package/prompts/structure/generate/user-prompt.md +0 -137
- package/prompts/structure/review/structure-review-system.md +0 -81
- package/prompts/structure/structure-example.md +0 -89
- package/prompts/structure/structure-getting-started.md +0 -10
- package/prompts/structure/update/system-prompt.md +0 -93
- package/prompts/structure/update/user-prompt.md +0 -43
- package/prompts/translate/admonition.md +0 -20
- package/prompts/translate/code-block.md +0 -33
- package/prompts/translate/glossary.md +0 -6
- package/prompts/translate/translate-document.md +0 -305
- package/prompts/utils/analyze-document-feedback-intent.md +0 -54
- package/prompts/utils/analyze-structure-feedback-intent.md +0 -43
- package/prompts/utils/feedback-refiner.md +0 -105
- package/types/document-schema.mjs +0 -55
- package/types/document-structure-schema.mjs +0 -261
- package/utils/auth-utils.mjs +0 -275
- package/utils/blocklet.mjs +0 -104
- package/utils/check-document-has-diagram.mjs +0 -95
- package/utils/conflict-detector.mjs +0 -149
- package/utils/constants/index.mjs +0 -620
- package/utils/constants/linter.mjs +0 -102
- package/utils/d2-utils.mjs +0 -198
- package/utils/debug.mjs +0 -3
- package/utils/delete-diagram-images.mjs +0 -99
- package/utils/deploy.mjs +0 -86
- package/utils/docs-finder-utils.mjs +0 -623
- package/utils/evaluate/report-utils.mjs +0 -132
- package/utils/extract-api.mjs +0 -32
- package/utils/file-utils.mjs +0 -960
- package/utils/history-utils.mjs +0 -203
- package/utils/icon-map.mjs +0 -26
- package/utils/image-compress.mjs +0 -75
- package/utils/kroki-utils.mjs +0 -173
- package/utils/linter/index.mjs +0 -50
- package/utils/load-config.mjs +0 -107
- package/utils/markdown/index.mjs +0 -26
- package/utils/markdown-checker.mjs +0 -694
- package/utils/mermaid-validator.mjs +0 -140
- package/utils/mermaid-worker-pool.mjs +0 -250
- package/utils/mermaid-worker.mjs +0 -233
- package/utils/openapi/index.mjs +0 -28
- package/utils/preferences-utils.mjs +0 -175
- package/utils/request.mjs +0 -10
- package/utils/store/index.mjs +0 -45
- package/utils/sync-diagram-to-translations.mjs +0 -262
- package/utils/upload-files.mjs +0 -231
- 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}:  - only valid media resources can be used`,
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
} catch {
|
|
262
|
-
errorMessages.push(
|
|
263
|
-
`Found invalid local image in ${source}:  - 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}:  - only valid media resources can be used`,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
} catch {
|
|
275
|
-
errorMessages.push(
|
|
276
|
-
`Found invalid local image in ${source}:  - 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}:  - 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
|
-
}
|