@aigne/doc-smith 0.2.9 → 0.2.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.11](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.10...v0.2.11) (2025-08-15)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * switch default model ([#43](https://github.com/AIGNE-io/aigne-doc-smith/issues/43)) ([203e280](https://github.com/AIGNE-io/aigne-doc-smith/commit/203e280b07d3856445b1877469ed4198db56f6f3))
9
+
10
+ ## [0.2.10](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.9...v0.2.10) (2025-08-14)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * fix bug bush feedback ([#41](https://github.com/AIGNE-io/aigne-doc-smith/issues/41)) ([2740d1a](https://github.com/AIGNE-io/aigne-doc-smith/commit/2740d1abef70ea36780b030917a6d54f74df4327))
16
+
3
17
  ## [0.2.9](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.8...v0.2.9) (2025-08-13)
4
18
 
5
19
 
@@ -16,7 +16,6 @@ export default async function checkDetail(
16
16
  originalStructurePlan,
17
17
  structurePlan,
18
18
  modifiedFiles,
19
- lastGitHead,
20
19
  forceRegenerate,
21
20
  ...rest
22
21
  },
@@ -80,11 +79,6 @@ export default async function checkDetail(
80
79
  }
81
80
  }
82
81
 
83
- // If lastGitHead is not set, regenerate
84
- if (!lastGitHead) {
85
- sourceFilesChanged = true;
86
- }
87
-
88
82
  // If file exists, check content validation
89
83
  let contentValidationFailed = false;
90
84
  if (detailGenerated && fileContent && structurePlan) {
@@ -1,3 +1,5 @@
1
+ import { access } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import {
2
4
  getCurrentGitHead,
3
5
  getProjectInfo,
@@ -7,7 +9,7 @@ import {
7
9
  } from "../utils/utils.mjs";
8
10
 
9
11
  export default async function checkStructurePlan(
10
- { originalStructurePlan, feedback, lastGitHead, ...rest },
12
+ { originalStructurePlan, feedback, lastGitHead, docsDir, forceRegenerate, ...rest },
11
13
  options,
12
14
  ) {
13
15
  // Check if we need to regenerate structure plan
@@ -16,9 +18,18 @@ export default async function checkStructurePlan(
16
18
 
17
19
  // If no feedback and originalStructurePlan exists, check for git changes
18
20
  if (originalStructurePlan) {
19
- // If no lastGitHead, regenerate by default
21
+ // If no lastGitHead, check if _sidebar.md exists to determine if we should regenerate
20
22
  if (!lastGitHead) {
21
- shouldRegenerate = true;
23
+ try {
24
+ // Check if _sidebar.md exists in docsDir
25
+ const sidebarPath = join(docsDir, "_sidebar.md");
26
+ await access(sidebarPath);
27
+ // If _sidebar.md exists, it means last execution was completed, need to regenerate
28
+ shouldRegenerate = true;
29
+ } catch {
30
+ // If _sidebar.md doesn't exist, it means last execution was interrupted, no need to regenerate
31
+ shouldRegenerate = false;
32
+ }
22
33
  } else {
23
34
  // Check if there are relevant file changes since last generation
24
35
  const currentGitHead = getCurrentGitHead();
@@ -43,6 +54,16 @@ export default async function checkStructurePlan(
43
54
  }
44
55
  }
45
56
 
57
+ // user requested regeneration
58
+ if (forceRegenerate) {
59
+ shouldRegenerate = true;
60
+ finalFeedback = `
61
+ ${finalFeedback || ""}
62
+
63
+ 用户请求强制重新生成结构规划,请根据最新的 Data Sources 和用户要求重生生成,**允许任何修改**。
64
+ `;
65
+ }
66
+
46
67
  // If no regeneration needed, return original structure plan
47
68
  if (originalStructurePlan && !feedback && !shouldRegenerate) {
48
69
  return {
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parse } from "yaml";
4
4
 
5
- export default async function loadConfig({ config }) {
5
+ export default async function loadConfig({ config, appUrl }) {
6
6
  const configPath = path.join(process.cwd(), config);
7
7
 
8
8
  try {
@@ -18,6 +18,11 @@ export default async function loadConfig({ config }) {
18
18
  // Read and parse YAML file
19
19
  const configContent = await fs.readFile(configPath, "utf-8");
20
20
  const parsedConfig = parse(configContent);
21
+
22
+ if (appUrl) {
23
+ parsedConfig.appUrl = appUrl;
24
+ }
25
+
21
26
  return {
22
27
  nodeName: "Section",
23
28
  locale: "en",
@@ -40,5 +45,9 @@ loadConfig.input_schema = {
40
45
  type: "string",
41
46
  default: "./.aigne/doc-smith/config.yaml",
42
47
  },
48
+ appUrl: {
49
+ type: "string",
50
+ description: "Application URL to override config",
51
+ },
43
52
  },
44
53
  };
@@ -174,8 +174,8 @@ export default async function loadSources({
174
174
  const words = source.match(/[a-zA-Z]+/g) || [];
175
175
  totalWords += words.length;
176
176
 
177
- // Count lines
178
- totalLines += source.split("\n").length;
177
+ // Count lines (excluding empty lines)
178
+ totalLines += source.split("\n").filter((line) => line.trim() !== "").length;
179
179
  }
180
180
  }
181
181
 
@@ -1,95 +1,10 @@
1
- import { existsSync, mkdirSync } from "node:fs";
2
- import { readFile, writeFile } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
1
  import { basename, join } from "node:path";
5
- import { createConnect } from "@aigne/aigne-hub";
6
2
  import { publishDocs as publishDocsFn } from "@aigne/publish-docs";
7
- import open from "open";
8
- import { joinURL } from "ufo";
9
- import { parse, stringify } from "yaml";
3
+ import { getAccessToken } from "../utils/auth-utils.mjs";
10
4
  import { loadConfigFromFile, saveValueToConfig } from "../utils/utils.mjs";
11
5
 
12
- const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
13
6
  const DEFAULT_APP_URL = "https://docsmith.aigne.io";
14
7
 
15
- /**
16
- * Get access token from environment, config file, or prompt user for authorization
17
- * @param {string} appUrl - The application URL
18
- * @returns {Promise<string>} - The access token
19
- */
20
- async function getAccessToken(appUrl) {
21
- const DOC_SMITH_ENV_FILE = join(homedir(), ".aigne", "doc-smith-connected.yaml");
22
- const { hostname } = new URL(appUrl);
23
-
24
- let accessToken = process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN;
25
-
26
- // Check if access token exists in environment or config file
27
- if (!accessToken) {
28
- try {
29
- if (existsSync(DOC_SMITH_ENV_FILE)) {
30
- const data = await readFile(DOC_SMITH_ENV_FILE, "utf8");
31
- if (data.includes("DOC_DISCUSS_KIT_ACCESS_TOKEN")) {
32
- const envs = parse(data);
33
- if (envs[hostname]?.DOC_DISCUSS_KIT_ACCESS_TOKEN) {
34
- accessToken = envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN;
35
- }
36
- }
37
- }
38
- } catch (error) {
39
- console.warn("Failed to read config file:", error.message);
40
- }
41
- }
42
-
43
- // If still no access token, prompt user to authorize
44
- if (!accessToken) {
45
- const DISCUSS_KIT_URL = appUrl;
46
- const connectUrl = joinURL(new URL(DISCUSS_KIT_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
47
-
48
- try {
49
- const result = await createConnect({
50
- connectUrl: connectUrl,
51
- connectAction: "gen-simple-access-key",
52
- source: `AIGNE DocSmith connect to Discuss Kit`,
53
- closeOnSuccess: true,
54
- appName: "AIGNE DocSmith",
55
- appLogo: "https://www.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
56
- openPage: (pageUrl) => open(pageUrl),
57
- });
58
-
59
- accessToken = result.accessKeySecret;
60
- process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN = accessToken;
61
-
62
- // Save the access token to config file
63
- const aigneDir = join(homedir(), ".aigne");
64
- if (!existsSync(aigneDir)) {
65
- mkdirSync(aigneDir, { recursive: true });
66
- }
67
-
68
- const existingConfig = existsSync(DOC_SMITH_ENV_FILE)
69
- ? parse(await readFile(DOC_SMITH_ENV_FILE, "utf8"))
70
- : {};
71
-
72
- await writeFile(
73
- DOC_SMITH_ENV_FILE,
74
- stringify({
75
- ...existingConfig,
76
- [hostname]: {
77
- DOC_DISCUSS_KIT_ACCESS_TOKEN: accessToken,
78
- DOC_DISCUSS_KIT_URL: DISCUSS_KIT_URL,
79
- },
80
- }),
81
- );
82
- } catch (error) {
83
- console.error("Failed to get access token:", error);
84
- throw new Error(
85
- "Failed to obtain access token. Please check your network connection and try again later.",
86
- );
87
- }
88
- }
89
-
90
- return accessToken;
91
- }
92
-
93
8
  export default async function publishDocs(
94
9
  { docsDir, appUrl, boardId, projectName, projectDesc, projectLogo },
95
10
  options,
@@ -124,17 +39,21 @@ export default async function publishDocs(
124
39
  });
125
40
 
126
41
  if (choice === "custom") {
127
- appUrl = await options.prompts.input({
42
+ const userInput = await options.prompts.input({
128
43
  message: "Please enter your Discuss Kit platform URL:",
129
44
  validate: (input) => {
130
45
  try {
131
- new URL(input);
46
+ // Check if input contains protocol, if not, prepend https://
47
+ const urlWithProtocol = input.includes("://") ? input : `https://${input}`;
48
+ new URL(urlWithProtocol);
132
49
  return true;
133
50
  } catch {
134
51
  return "Please enter a valid URL";
135
52
  }
136
53
  },
137
54
  });
55
+ // Ensure appUrl has protocol
56
+ appUrl = userInput.includes("://") ? userInput : `https://${userInput}`;
138
57
  }
139
58
  }
140
59
 
@@ -10,10 +10,9 @@ skills:
10
10
  skipIfExists: true
11
11
  - load-config.mjs
12
12
  - publish-docs.mjs
13
- # input_schema:
14
- # type: object
15
- # properties:
16
- # config:
17
- # type: string
18
- # description: Path to the config file
19
- # default: ./.aigne/doc-smith/config.yaml
13
+ input_schema:
14
+ type: object
15
+ properties:
16
+ appUrl:
17
+ type: string
18
+ description: target website URL where the documentation will be published
package/aigne.yaml CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  chat_model:
4
4
  provider: google
5
- # name: gemini-2.5-pro
6
- name: gemini-2.5-flash
5
+ name: gemini-2.5-pro
6
+ # name: gemini-2.5-flash
7
7
  temperature: 0.8
8
8
  agents:
9
9
  - ./agents/structure-planning.yaml
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,105 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { createConnect } from "@aigne/aigne-hub";
6
+ import open from "open";
7
+ import { joinURL } from "ufo";
8
+ import { parse, stringify } from "yaml";
9
+ import { getComponentMountPoint } from "./blocklet.mjs";
10
+ import { DISCUSS_KIT_DID } from "./constants.mjs";
11
+
12
+ const WELLKNOWN_SERVICE_PATH_PREFIX = "/.well-known/service";
13
+
14
+ /**
15
+ * Get access token from environment, config file, or prompt user for authorization
16
+ * @param {string} appUrl - The application URL
17
+ * @returns {Promise<string>} - The access token
18
+ */
19
+ export async function getAccessToken(appUrl) {
20
+ const DOC_SMITH_ENV_FILE = join(homedir(), ".aigne", "doc-smith-connected.yaml");
21
+ const { hostname } = new URL(appUrl);
22
+
23
+ let accessToken = process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN;
24
+
25
+ // Check if access token exists in environment or config file
26
+ if (!accessToken) {
27
+ try {
28
+ if (existsSync(DOC_SMITH_ENV_FILE)) {
29
+ const data = await readFile(DOC_SMITH_ENV_FILE, "utf8");
30
+ if (data.includes("DOC_DISCUSS_KIT_ACCESS_TOKEN")) {
31
+ const envs = parse(data);
32
+ if (envs[hostname]?.DOC_DISCUSS_KIT_ACCESS_TOKEN) {
33
+ accessToken = envs[hostname].DOC_DISCUSS_KIT_ACCESS_TOKEN;
34
+ }
35
+ }
36
+ }
37
+ } catch (error) {
38
+ console.warn("Failed to read config file:", error.message);
39
+ }
40
+ }
41
+
42
+ // If still no access token, prompt user to authorize
43
+ if (accessToken) {
44
+ return accessToken;
45
+ }
46
+
47
+ // Check if Discuss Kit is running at the provided URL
48
+ try {
49
+ await getComponentMountPoint(appUrl, DISCUSS_KIT_DID);
50
+ } catch {
51
+ throw new Error(
52
+ `Unable to find Discuss Kit running at the provided URL: ${appUrl}\n\n` +
53
+ "Please ensure that:\n" +
54
+ "• The URL is correct and accessible\n" +
55
+ "• Discuss Kit is properly installed and running\n" +
56
+ "If you continue to experience issues, please verify your Discuss Kit installation.",
57
+ );
58
+ }
59
+
60
+ const DISCUSS_KIT_URL = appUrl;
61
+ const connectUrl = joinURL(new URL(DISCUSS_KIT_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
62
+
63
+ try {
64
+ const result = await createConnect({
65
+ connectUrl: connectUrl,
66
+ connectAction: "gen-simple-access-key",
67
+ source: `AIGNE DocSmith connect to Discuss Kit`,
68
+ closeOnSuccess: true,
69
+ appName: "AIGNE DocSmith",
70
+ appLogo: "https://www.aigne.io/image-bin/uploads/a7910a71364ee15a27e86f869ad59009.svg",
71
+ openPage: (pageUrl) => open(pageUrl),
72
+ });
73
+
74
+ accessToken = result.accessKeySecret;
75
+ process.env.DOC_DISCUSS_KIT_ACCESS_TOKEN = accessToken;
76
+
77
+ // Save the access token to config file
78
+ const aigneDir = join(homedir(), ".aigne");
79
+ if (!existsSync(aigneDir)) {
80
+ mkdirSync(aigneDir, { recursive: true });
81
+ }
82
+
83
+ const existingConfig = existsSync(DOC_SMITH_ENV_FILE)
84
+ ? parse(await readFile(DOC_SMITH_ENV_FILE, "utf8"))
85
+ : {};
86
+
87
+ await writeFile(
88
+ DOC_SMITH_ENV_FILE,
89
+ stringify({
90
+ ...existingConfig,
91
+ [hostname]: {
92
+ DOC_DISCUSS_KIT_ACCESS_TOKEN: accessToken,
93
+ DOC_DISCUSS_KIT_URL: DISCUSS_KIT_URL,
94
+ },
95
+ }),
96
+ );
97
+ } catch (error) {
98
+ console.debug(error);
99
+ throw new Error(
100
+ "Failed to obtain access token. Please check your network connection and try again later.",
101
+ );
102
+ }
103
+
104
+ return accessToken;
105
+ }
@@ -0,0 +1,25 @@
1
+ export async function getComponentMountPoint(appUrl, did) {
2
+ const url = new URL(appUrl);
3
+ const blockletJsUrl = `${url.origin}/__blocklet__.js?type=json`;
4
+
5
+ const blockletJs = await fetch(blockletJsUrl, {
6
+ method: "GET",
7
+ headers: {
8
+ Accept: "application/json",
9
+ },
10
+ });
11
+
12
+ if (!blockletJs.ok) {
13
+ throw new Error(
14
+ `Failed to fetch blocklet json: ${blockletJs.status} ${blockletJs.statusText}, ${blockletJsUrl}`,
15
+ );
16
+ }
17
+
18
+ const config = await blockletJs.json();
19
+ const component = config.componentMountPoints.find((component) => component.did === did);
20
+ if (!component) {
21
+ throw new Error(`Component ${did} not found in blocklet: ${appUrl}`);
22
+ }
23
+
24
+ return component.mountPoint;
25
+ }
@@ -1,25 +1,89 @@
1
1
  // Default file patterns for inclusion and exclusion
2
2
  export const DEFAULT_INCLUDE_PATTERNS = [
3
+ // Python
3
4
  "*.py",
5
+ "*.pyi",
6
+ "*.pyx",
7
+ // JavaScript/TypeScript
4
8
  "*.js",
5
9
  "*.jsx",
6
10
  "*.ts",
7
11
  "*.tsx",
8
- "*.go",
9
- "*.java",
10
- "*.pyi",
11
- "*.pyx",
12
+ // C/C++
12
13
  "*.c",
13
14
  "*.cc",
14
15
  "*.cpp",
16
+ "*.cxx",
17
+ "*.c++",
15
18
  "*.h",
19
+ "*.hpp",
20
+ "*.hxx",
21
+ "*.h++",
22
+ // JVM Languages
23
+ "*.java",
24
+ "*.kt",
25
+ "*.scala",
26
+ "*.groovy",
27
+ "*.gvy",
28
+ "*.gy",
29
+ "*.gsh",
30
+ "*.clj",
31
+ "*.cljs",
32
+ "*.cljx",
33
+ // .NET Languages
34
+ "*.cs",
35
+ "*.vb",
36
+ "*.fs",
37
+ // Functional Languages
38
+ "*.f",
39
+ "*.ml",
40
+ "*.sml",
41
+ "*.lisp",
42
+ "*.lsp",
43
+ "*.cl",
44
+ // Systems Programming
45
+ "*.rs",
46
+ "*.go",
47
+ "*.nim",
48
+ "*.asm",
49
+ "*.s",
50
+ // Web Technologies
51
+ "*.html",
52
+ "*.htm",
53
+ "*.css",
54
+ "*.php",
55
+ // Scripting Languages
56
+ "*.rb",
57
+ "*.pl",
58
+ "*.ps1",
59
+ "*.lua",
60
+ "*.tcl",
61
+ // Mobile/Modern Languages
62
+ "*.swift",
63
+ "*.dart",
64
+ "*.ex",
65
+ "*.exs",
66
+ "*.erl",
67
+ "*.jl",
68
+ // Data Science
69
+ "*.r",
70
+ "*.R",
71
+ "*.m",
72
+ // Other Languages
73
+ "*.pas",
74
+ "*.cob",
75
+ "*.cbl",
76
+ "*.pro",
77
+ "*.prolog",
78
+ "*.sql",
79
+ // Documentation & Config
16
80
  "*.md",
17
81
  "*.rst",
18
82
  "*.json",
19
- "*Dockerfile",
20
- "*Makefile",
21
83
  "*.yaml",
22
84
  "*.yml",
85
+ "*Dockerfile",
86
+ "*Makefile",
23
87
  ];
24
88
 
25
89
  export const DEFAULT_EXCLUDE_PATTERNS = [
@@ -78,14 +142,14 @@ export const SUPPORTED_LANGUAGES = [
78
142
 
79
143
  // Predefined document generation styles
80
144
  export const DOCUMENT_STYLES = {
81
- developerDocs: {
82
- name: "Developer Docs",
83
- rules: "Steps-first; copy-paste examples; minimal context; active 'you'.",
84
- },
85
145
  userGuide: {
86
146
  name: "User Guide",
87
147
  rules: "Scenario-based; step-by-step; plain language; outcomes & cautions.",
88
148
  },
149
+ developerDocs: {
150
+ name: "Developer Docs",
151
+ rules: "Steps-first; copy-paste examples; minimal context; active 'you'.",
152
+ },
89
153
  apiReference: {
90
154
  name: "API Reference",
91
155
  rules: "Exact & skimmable; schema-first; clear params/errors/examples.",
@@ -98,8 +162,11 @@ export const DOCUMENT_STYLES = {
98
162
 
99
163
  // Predefined target audiences
100
164
  export const TARGET_AUDIENCES = {
165
+ generalUsers: "General Users",
101
166
  actionFirst: "Developers, Implementation Engineers, DevOps",
102
167
  conceptFirst: "Architects, Technical Leads, Developers interested in principles",
103
- generalUsers: "General Users",
104
168
  custom: "Enter your own target audience",
105
169
  };
170
+
171
+ // Component mount point ID for Discuss Kit
172
+ export const DISCUSS_KIT_DID = "z8ia1WEiBZ7hxURf6LwH21Wpg99vophFwSJdu";
@@ -1,5 +1,5 @@
1
- import { access, readFile } from "node:fs/promises";
2
1
  import { execSync } from "node:child_process";
2
+ import { access, readFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { glob } from "glob";
5
5
 
@@ -91,45 +91,34 @@ function checkDeadLinks(markdown, source, allowedLinks, errorMessages) {
91
91
  /**
92
92
  * Check code block content for indentation consistency issues
93
93
  * @param {Array} codeBlockContent - Array of {line, lineNumber} objects from the code block
94
+ * @param {number} codeBlockIndent - The indentation of the code block start marker (```)
94
95
  * @param {string} source - Source description for error reporting
95
96
  * @param {Array} errorMessages - Array to push error messages to
96
97
  */
97
- function checkCodeBlockIndentation(codeBlockContent, source, errorMessages) {
98
+ function checkCodeBlockIndentation(codeBlockContent, codeBlockIndent, source, errorMessages) {
98
99
  if (codeBlockContent.length === 0) return;
99
100
 
100
- // Filter out empty lines for base indentation calculation
101
+ // Filter out empty lines for analysis
101
102
  const nonEmptyLines = codeBlockContent.filter((item) => item.line.trim().length > 0);
102
103
  if (nonEmptyLines.length === 0) return;
103
104
 
104
- // Find the base indentation from the first meaningful line
105
- let baseCodeIndent = null;
105
+ // The expected base indentation for code block content should match the code block marker
106
+ const expectedBaseIndent = codeBlockIndent;
106
107
  const problematicLines = [];
107
108
 
108
109
  for (const item of nonEmptyLines) {
109
110
  const { line, lineNumber } = item;
110
111
  const match = line.match(/^(\s*)/);
111
112
  const currentIndent = match ? match[1].length : 0;
112
- const trimmedLine = line.trim();
113
113
 
114
- // Skip lines that are clearly comments (common pattern: # comment)
115
- if (trimmedLine.startsWith("#") && !trimmedLine.includes("=") && !trimmedLine.includes("{")) {
116
- continue;
117
- }
118
-
119
- // Establish base indentation from the first meaningful line
120
- if (baseCodeIndent === null) {
121
- baseCodeIndent = currentIndent;
122
- continue;
123
- }
124
-
125
- // Check if current line has less indentation than the base
114
+ // Check if current line has less indentation than expected
126
115
  // This indicates inconsistent indentation that may cause rendering issues
127
- if (currentIndent < baseCodeIndent && baseCodeIndent > 0) {
116
+ if (currentIndent < expectedBaseIndent && expectedBaseIndent > 0) {
128
117
  problematicLines.push({
129
118
  lineNumber,
130
119
  line: line.trimEnd(),
131
120
  currentIndent,
132
- baseIndent: baseCodeIndent,
121
+ expectedIndent: expectedBaseIndent,
133
122
  });
134
123
  }
135
124
  }
@@ -162,7 +151,7 @@ function checkCodeBlockIndentation(codeBlockContent, source, errorMessages) {
162
151
  ? `lines ${group[0].lineNumber}-${group[group.length - 1].lineNumber}`
163
152
  : `line ${firstIssue.lineNumber}`;
164
153
 
165
- const issue = `inconsistent indentation: ${firstIssue.currentIndent} spaces (base: ${firstIssue.baseIndent} spaces)`;
154
+ const issue = `insufficient indentation: ${firstIssue.currentIndent} spaces (expected: ${firstIssue.expectedIndent} spaces)`;
166
155
  errorMessages.push(
167
156
  `Found code block with inconsistent indentation in ${source} at ${lineNumbers}: ${issue}. This may cause rendering issues`,
168
157
  );
@@ -185,6 +174,7 @@ function checkContentStructure(markdown, source, errorMessages) {
185
174
  let inAnyCodeBlock = false;
186
175
  let anyCodeBlockStartLine = 0;
187
176
  let codeBlockContent = [];
177
+ let codeBlockIndent = 0;
188
178
 
189
179
  for (let i = 0; i < lines.length; i++) {
190
180
  const line = lines[i];
@@ -198,13 +188,17 @@ function checkContentStructure(markdown, source, errorMessages) {
198
188
  anyCodeBlockStartLine = lineNumber;
199
189
  inCodeBlock = true;
200
190
  codeBlockContent = [];
191
+
192
+ // Capture the indentation of the code block start marker
193
+ const match = line.match(/^(\s*)/);
194
+ codeBlockIndent = match ? match[1].length : 0;
201
195
  } else {
202
196
  // Ending the code block
203
197
  inAnyCodeBlock = false;
204
198
 
205
199
  if (inCodeBlock) {
206
200
  // Check code block content for indentation issues
207
- checkCodeBlockIndentation(codeBlockContent, source, errorMessages);
201
+ checkCodeBlockIndentation(codeBlockContent, codeBlockIndent, source, errorMessages);
208
202
  inCodeBlock = false;
209
203
  }
210
204
  }