@aigne/doc-smith 0.2.6 → 0.2.9

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,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.9](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.8...v0.2.9) (2025-08-13)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * polish ignore check ([#25](https://github.com/AIGNE-io/aigne-doc-smith/issues/25)) ([90bc866](https://github.com/AIGNE-io/aigne-doc-smith/commit/90bc866513fef7b47047b1016e07bf38881c101c))
9
+
10
+ ## [0.2.8](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.7...v0.2.8) (2025-08-13)
11
+
12
+
13
+ ### Miscellaneous Chores
14
+
15
+ * release 0.2.8 ([da19bc0](https://github.com/AIGNE-io/aigne-doc-smith/commit/da19bc0b2c6c4e5fddaff84b4fa85c9d495b3ba0))
16
+
17
+ ## [0.2.7](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.6...v0.2.7) (2025-08-12)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * polish detail check ([#21](https://github.com/AIGNE-io/aigne-doc-smith/issues/21)) ([0268732](https://github.com/AIGNE-io/aigne-doc-smith/commit/02687329c3507b73f9cbf1aa2ff1b87921452516))
23
+
24
+
25
+ ### Miscellaneous Chores
26
+
27
+ * release 0.2.7 ([3b807fe](https://github.com/AIGNE-io/aigne-doc-smith/commit/3b807fed833a5160931747bce37aac00cf11d9ac))
28
+
3
29
  ## [0.2.6](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.5...v0.2.6) (2025-08-12)
4
30
 
5
31
 
@@ -1,9 +1,6 @@
1
1
  import { checkMarkdown } from "../utils/markdown-checker.mjs";
2
2
 
3
- export default async function checkDetailResult({
4
- structurePlan,
5
- reviewContent,
6
- }) {
3
+ export default async function checkDetailResult({ structurePlan, reviewContent }) {
7
4
  let isApproved = true;
8
5
  const detailFeedback = [];
9
6
 
@@ -35,9 +32,7 @@ export default async function checkDetailResult({
35
32
  }
36
33
  } catch (error) {
37
34
  isApproved = false;
38
- detailFeedback.push(
39
- `Found markdown validation error in result: ${error.message}`
40
- );
35
+ detailFeedback.push(`Found markdown validation error in result: ${error.message}`);
41
36
  }
42
37
 
43
38
  return {
@@ -2,8 +2,8 @@ import { access, readFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { TeamAgent } from "@aigne/core";
5
- import checkDetailResult from "./check-detail-result.mjs";
6
5
  import { hasSourceFilesChanged } from "../utils/utils.mjs";
6
+ import checkDetailResult from "./check-detail-result.mjs";
7
7
 
8
8
  // Get current script directory
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -20,7 +20,7 @@ export default async function checkDetail(
20
20
  forceRegenerate,
21
21
  ...rest
22
22
  },
23
- options
23
+ options,
24
24
  ) {
25
25
  // Check if the detail file already exists
26
26
  const flatName = path.replace(/^\//, "").replace(/\//g, "-");
@@ -41,11 +41,9 @@ export default async function checkDetail(
41
41
  let sourceIdsChanged = false;
42
42
  if (originalStructurePlan && sourceIds) {
43
43
  // Find the original node in the structure plan
44
- const originalNode = originalStructurePlan.find(
45
- (node) => node.path === path
46
- );
44
+ const originalNode = originalStructurePlan.find((node) => node.path === path);
47
45
 
48
- if (originalNode && originalNode.sourceIds) {
46
+ if (originalNode?.sourceIds) {
49
47
  const originalSourceIds = originalNode.sourceIds;
50
48
  const currentSourceIds = sourceIds;
51
49
 
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  getCurrentGitHead,
3
+ getProjectInfo,
3
4
  hasFileChangesBetweenCommits,
4
5
  loadConfigFromFile,
5
6
  saveValueToConfig,
6
- getProjectInfo,
7
7
  } from "../utils/utils.mjs";
8
8
 
9
9
  export default async function checkStructurePlan(
10
10
  { originalStructurePlan, feedback, lastGitHead, ...rest },
11
- options
11
+ options,
12
12
  ) {
13
13
  // Check if we need to regenerate structure plan
14
14
  let shouldRegenerate = false;
@@ -23,10 +23,7 @@ export default async function checkStructurePlan(
23
23
  // Check if there are relevant file changes since last generation
24
24
  const currentGitHead = getCurrentGitHead();
25
25
  if (currentGitHead && currentGitHead !== lastGitHead) {
26
- const hasChanges = hasFileChangesBetweenCommits(
27
- lastGitHead,
28
- currentGitHead
29
- );
26
+ const hasChanges = hasFileChangesBetweenCommits(lastGitHead, currentGitHead);
30
27
  if (hasChanges) {
31
28
  shouldRegenerate = true;
32
29
  }
@@ -71,11 +68,9 @@ export default async function checkStructurePlan(
71
68
 
72
69
  // Check if user has modified project information
73
70
  const userModifiedProjectName =
74
- currentConfig?.projectName &&
75
- currentConfig.projectName !== projectInfo.name;
71
+ currentConfig?.projectName && currentConfig.projectName !== projectInfo.name;
76
72
  const userModifiedProjectDesc =
77
- currentConfig?.projectDesc &&
78
- currentConfig.projectDesc !== projectInfo.description;
73
+ currentConfig?.projectDesc && currentConfig.projectDesc !== projectInfo.description;
79
74
 
80
75
  // If user hasn't modified project info and it's not from GitHub, save AI output
81
76
  if (!userModifiedProjectName && !userModifiedProjectDesc) {
@@ -8,15 +8,8 @@ function getActionText(isTranslate, baseText) {
8
8
  }
9
9
 
10
10
  export default async function findItemByPath(
11
- {
12
- "doc-path": docPath,
13
- structurePlanResult,
14
- boardId,
15
- docsDir,
16
- isTranslate,
17
- feedback,
18
- },
19
- options
11
+ { "doc-path": docPath, structurePlanResult, boardId, docsDir, isTranslate, feedback },
12
+ options,
20
13
  ) {
21
14
  let foundItem = null;
22
15
  let selectedFileContent = null;
@@ -30,9 +23,7 @@ export default async function findItemByPath(
30
23
  // Filter for main language .md files (exclude _sidebar.md and language-specific files)
31
24
  const mainLanguageFiles = files.filter(
32
25
  (file) =>
33
- file.endsWith(".md") &&
34
- file !== "_sidebar.md" &&
35
- !file.match(/\.\w+(-\w+)?\.md$/) // Exclude language-specific files like .en.md, .zh-CN.md, etc.
26
+ file.endsWith(".md") && file !== "_sidebar.md" && !file.match(/\.\w+(-\w+)?\.md$/), // Exclude language-specific files like .en.md, .zh-CN.md, etc.
36
27
  );
37
28
 
38
29
  if (mainLanguageFiles.length === 0) {
@@ -42,7 +33,7 @@ export default async function findItemByPath(
42
33
  // Let user select a file
43
34
  const selectedFile = await options.prompts.search({
44
35
  message: getActionText(isTranslate, "Select a document to {action}:"),
45
- source: async (input, { signal }) => {
36
+ source: async (input) => {
46
37
  if (!input || input.trim() === "") {
47
38
  return mainLanguageFiles.map((file) => ({
48
39
  name: file,
@@ -52,7 +43,7 @@ export default async function findItemByPath(
52
43
 
53
44
  const searchTerm = input.trim().toLowerCase();
54
45
  const filteredFiles = mainLanguageFiles.filter((file) =>
55
- file.toLowerCase().includes(searchTerm)
46
+ file.toLowerCase().includes(searchTerm),
56
47
  );
57
48
 
58
49
  return filteredFiles.map((file) => ({
@@ -71,10 +62,7 @@ export default async function findItemByPath(
71
62
  const selectedFilePath = join(docsDir, selectedFile);
72
63
  selectedFileContent = await readFile(selectedFilePath, "utf-8");
73
64
  } catch (readError) {
74
- console.warn(
75
- `⚠️ Could not read content from ${selectedFile}:`,
76
- readError.message
77
- );
65
+ console.warn(`⚠️ Could not read content from ${selectedFile}:`, readError.message);
78
66
  selectedFileContent = null;
79
67
  }
80
68
 
@@ -87,9 +75,7 @@ export default async function findItemByPath(
87
75
 
88
76
  // First try without boardId prefix
89
77
  foundItemByFile = structurePlanResult.find((item) => {
90
- const itemFlattenedPath = item.path
91
- .replace(/^\//, "")
92
- .replace(/\//g, "-");
78
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
93
79
  return itemFlattenedPath === flatName;
94
80
  });
95
81
  if (!foundItemByFile) {
@@ -102,8 +88,8 @@ export default async function findItemByPath(
102
88
  throw new Error(
103
89
  getActionText(
104
90
  isTranslate,
105
- "Please provide a doc-path parameter to specify which document to {action}"
106
- )
91
+ "Please provide a doc-path parameter to specify which document to {action}",
92
+ ),
107
93
  );
108
94
  }
109
95
  }
@@ -121,18 +107,14 @@ export default async function findItemByPath(
121
107
  // Find item by comparing flattened paths
122
108
  foundItem = structurePlanResult.find((item) => {
123
109
  // Convert item.path to flattened format (replace / with -)
124
- const itemFlattenedPath = item.path
125
- .replace(/^\//, "")
126
- .replace(/\//g, "-");
110
+ const itemFlattenedPath = item.path.replace(/^\//, "").replace(/\//g, "-");
127
111
  return itemFlattenedPath === flattenedPath;
128
112
  });
129
113
  }
130
114
  }
131
115
 
132
116
  if (!foundItem) {
133
- throw new Error(
134
- `Item with path "${docPath}" not found in structurePlanResult`
135
- );
117
+ throw new Error(`Item with path "${docPath}" not found in structurePlanResult`);
136
118
  }
137
119
 
138
120
  // Prompt for feedback if not provided
@@ -140,7 +122,7 @@ export default async function findItemByPath(
140
122
  if (!userFeedback) {
141
123
  const feedbackMessage = getActionText(
142
124
  isTranslate,
143
- "Please provide feedback for the {action} (press Enter to skip):"
125
+ "Please provide feedback for the {action} (press Enter to skip):",
144
126
  );
145
127
 
146
128
  userFeedback = await options.prompts.input({
@@ -159,7 +141,7 @@ export default async function findItemByPath(
159
141
  }
160
142
 
161
143
  // Add feedback to result if provided
162
- if (userFeedback && userFeedback.trim()) {
144
+ if (userFeedback?.trim()) {
163
145
  result.feedback = userFeedback.trim();
164
146
  }
165
147
 
@@ -1,18 +1,11 @@
1
- import { writeFile, mkdir, readFile } from "node:fs/promises";
2
- import { join, dirname } from "node:path";
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
3
  import chalk from "chalk";
4
- import {
5
- validatePath,
6
- getAvailablePaths,
7
- getProjectInfo,
8
- } from "../utils/utils.mjs";
9
- import {
10
- SUPPORTED_LANGUAGES,
11
- DOCUMENT_STYLES,
12
- TARGET_AUDIENCES,
13
- } from "../utils/constants.mjs";
4
+ import { DOCUMENT_STYLES, SUPPORTED_LANGUAGES, TARGET_AUDIENCES } from "../utils/constants.mjs";
5
+ import { getAvailablePaths, getProjectInfo, validatePath } from "../utils/utils.mjs";
6
+
14
7
  // UI constants
15
- const PRESS_ENTER_TO_FINISH = "Press Enter to finish";
8
+ const _PRESS_ENTER_TO_FINISH = "Press Enter to finish";
16
9
 
17
10
  /**
18
11
  * Guide users through multi-turn dialogue to collect information and generate YAML configuration
@@ -22,12 +15,8 @@ const PRESS_ENTER_TO_FINISH = "Press Enter to finish";
22
15
  * @returns {Promise<Object>}
23
16
  */
24
17
  export default async function init(
25
- {
26
- outputPath = ".aigne/doc-smith",
27
- fileName = "config.yaml",
28
- skipIfExists = false,
29
- },
30
- options
18
+ { outputPath = ".aigne/doc-smith", fileName = "config.yaml", skipIfExists = false },
19
+ options,
31
20
  ) {
32
21
  if (skipIfExists) {
33
22
  const filePath = join(outputPath, fileName);
@@ -103,7 +92,7 @@ export default async function init(
103
92
  // 4. Translation languages
104
93
  // Filter out the primary language from available choices
105
94
  const availableTranslationLanguages = SUPPORTED_LANGUAGES.filter(
106
- (lang) => lang.code !== primaryLanguageChoice
95
+ (lang) => lang.code !== primaryLanguageChoice,
107
96
  );
108
97
 
109
98
  const translateLanguageChoices = await options.prompts.checkbox({
@@ -132,7 +121,7 @@ export default async function init(
132
121
  while (true) {
133
122
  const selectedPath = await options.prompts.search({
134
123
  message: "Path:",
135
- source: async (input, { signal }) => {
124
+ source: async (input) => {
136
125
  if (!input || input.trim() === "") {
137
126
  return [
138
127
  {
@@ -153,11 +142,7 @@ export default async function init(
153
142
  });
154
143
 
155
144
  // Check if user chose to exit
156
- if (
157
- !selectedPath ||
158
- selectedPath.trim() === "" ||
159
- selectedPath === "Press Enter to finish"
160
- ) {
145
+ if (!selectedPath || selectedPath.trim() === "" || selectedPath === "Press Enter to finish") {
161
146
  break;
162
147
  }
163
148
 
@@ -206,13 +191,9 @@ export default async function init(
206
191
  console.log(chalk.cyan("---"));
207
192
  console.log(chalk.cyan(yamlContent));
208
193
  console.log(chalk.cyan("---"));
194
+ console.log("💡 You can edit the configuration file anytime to modify settings.\n");
209
195
  console.log(
210
- "💡 You can edit the configuration file anytime to modify settings.\n"
211
- );
212
- console.log(
213
- `🚀 Run ${chalk.cyan(
214
- "'aigne doc generate'"
215
- )} to start documentation generation!\n`
196
+ `🚀 Run ${chalk.cyan("'aigne doc generate'")} to start documentation generation!\n`,
216
197
  );
217
198
 
218
199
  return {};
@@ -242,7 +223,7 @@ function generateYAML(input) {
242
223
 
243
224
  // Add rules (required field)
244
225
  yaml += `rules: |\n`;
245
- if (input.rules && input.rules.trim()) {
226
+ if (input.rules?.trim()) {
246
227
  yaml += ` ${input.rules.split("\n").join("\n ")}\n\n`;
247
228
  } else {
248
229
  yaml += ` \n\n`;
@@ -283,5 +264,4 @@ function generateYAML(input) {
283
264
  return yaml;
284
265
  }
285
266
 
286
- init.description =
287
- "Generate a configuration file for the documentation generation process";
267
+ init.description = "Generate a configuration file for the documentation generation process";
@@ -8,10 +8,7 @@ import { SUPPORTED_LANGUAGES } from "../utils/constants.mjs";
8
8
  * @param {Object} options - Options object with prompts
9
9
  * @returns {Promise<Object>} Selected languages
10
10
  */
11
- export default async function languageSelector(
12
- { languages, translateLanguages },
13
- options
14
- ) {
11
+ export default async function languageSelector({ languages, translateLanguages }, options) {
15
12
  let selectedLanguages = [];
16
13
 
17
14
  // Check if translateLanguages is available from config
@@ -21,24 +18,19 @@ export default async function languageSelector(
21
18
  translateLanguages.length === 0
22
19
  ) {
23
20
  throw new Error(
24
- "No translation languages configured in config.yaml. Please add translateLanguages to your configuration."
21
+ "No translation languages configured in config.yaml. Please add translateLanguages to your configuration.",
25
22
  );
26
23
  }
27
24
 
28
25
  // If languages are provided as parameter, validate against configured languages
29
26
  if (languages && Array.isArray(languages) && languages.length > 0) {
30
- const validLanguages = languages.filter((lang) =>
31
- translateLanguages.includes(lang)
32
- );
27
+ const validLanguages = languages.filter((lang) => translateLanguages.includes(lang));
33
28
 
34
29
  if (validLanguages.length > 0) {
35
30
  selectedLanguages = validLanguages;
36
31
  } else {
37
32
  console.log(`⚠️ Invalid languages provided: ${languages.join(", ")}`);
38
- console.log(
39
- "Available configured languages:",
40
- translateLanguages.join(", ")
41
- );
33
+ console.log("Available configured languages:", translateLanguages.join(", "));
42
34
  }
43
35
  }
44
36
 
@@ -46,13 +38,9 @@ export default async function languageSelector(
46
38
  if (selectedLanguages.length === 0) {
47
39
  // Create choices from configured languages with labels
48
40
  const choices = translateLanguages.map((langCode) => {
49
- const supportedLang = SUPPORTED_LANGUAGES.find(
50
- (l) => l.code === langCode
51
- );
41
+ const supportedLang = SUPPORTED_LANGUAGES.find((l) => l.code === langCode);
52
42
  return {
53
- name: supportedLang
54
- ? `${supportedLang.label} (${supportedLang.sample})`
55
- : langCode,
43
+ name: supportedLang ? `${supportedLang.label} (${supportedLang.sample})` : langCode,
56
44
  value: langCode,
57
45
  short: langCode,
58
46
  };
@@ -1,5 +1,5 @@
1
- import path from "node:path";
2
1
  import fs from "node:fs/promises";
2
+ import path from "node:path";
3
3
  import { parse } from "yaml";
4
4
 
5
5
  export default async function loadConfig({ config }) {
@@ -8,7 +8,7 @@ export default async function loadConfig({ config }) {
8
8
  try {
9
9
  // Check if config file exists
10
10
  await fs.access(configPath);
11
- } catch (error) {
11
+ } catch (_error) {
12
12
  console.log(`Config file not found: ${configPath}`);
13
13
  console.log("Please run 'aigne doc init' to create the config file.");
14
14
  throw new Error(`Config file not found: ${configPath}`);
@@ -1,99 +1,8 @@
1
1
  import { access, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { glob } from "glob";
4
- import {
5
- getCurrentGitHead,
6
- getModifiedFilesBetweenCommits,
7
- } from "../utils/utils.mjs";
8
- import {
9
- DEFAULT_INCLUDE_PATTERNS,
10
- DEFAULT_EXCLUDE_PATTERNS,
11
- } from "../utils/constants.mjs";
12
-
13
- /**
14
- * Load .gitignore patterns from a directory
15
- * @param {string} dir - Directory path
16
- * @returns {object|null} Ignore instance or null if no .gitignore found
17
- */
18
- async function loadGitignore(dir) {
19
- const gitignorePath = path.join(dir, ".gitignore");
20
- try {
21
- await access(gitignorePath);
22
- const gitignoreContent = await readFile(gitignorePath, "utf8");
23
- // Create ignore patterns from .gitignore content
24
- const ignorePatterns = gitignoreContent
25
- .split("\n")
26
- .map((line) => line.trim())
27
- .filter((line) => line && !line.startsWith("#"))
28
- .map((line) => line.replace(/^\//, "")); // Remove leading slash
29
-
30
- return ignorePatterns.length > 0 ? ignorePatterns : null;
31
- } catch {
32
- // .gitignore file doesn't exist
33
- return null;
34
- }
35
- }
36
-
37
- /**
38
- * Get files using glob patterns
39
- * @param {string} dir - Directory to search
40
- * @param {string[]} includePatterns - Include patterns
41
- * @param {string[]} excludePatterns - Exclude patterns
42
- * @param {string[]} gitignorePatterns - .gitignore patterns
43
- * @returns {Promise<string[]>} Array of file paths
44
- */
45
- async function getFilesWithGlob(
46
- dir,
47
- includePatterns,
48
- excludePatterns,
49
- gitignorePatterns
50
- ) {
51
- // Prepare all ignore patterns
52
- const allIgnorePatterns = [];
53
-
54
- if (excludePatterns) {
55
- allIgnorePatterns.push(...excludePatterns);
56
- }
57
-
58
- if (gitignorePatterns) {
59
- allIgnorePatterns.push(...gitignorePatterns);
60
- }
61
-
62
- // Add default exclusions if not already present
63
- const defaultExclusions = ["node_modules/**", "test/**", "temp/**"];
64
- for (const exclusion of defaultExclusions) {
65
- if (!allIgnorePatterns.includes(exclusion)) {
66
- allIgnorePatterns.push(exclusion);
67
- }
68
- }
69
-
70
- // Convert patterns to be relative to the directory
71
- const patterns = includePatterns.map((pattern) => {
72
- // If pattern doesn't start with / or **, make it relative to dir
73
- if (!pattern.startsWith("/") && !pattern.startsWith("**")) {
74
- return `**/${pattern}`; // Use ** to search recursively
75
- }
76
- return pattern;
77
- });
78
-
79
- try {
80
- const files = await glob(patterns, {
81
- cwd: dir,
82
- ignore: allIgnorePatterns.length > 0 ? allIgnorePatterns : undefined,
83
- absolute: true,
84
- nodir: true, // Only return files, not directories
85
- dot: false, // Don't include dot files by default
86
- gitignore: true, // Enable .gitignore support
87
- });
88
-
89
- return files;
90
- } catch (error) {
91
- console.warn(
92
- `Warning: Error during glob search in ${dir}: ${error.message}`
93
- );
94
- return [];
95
- }
96
- }
3
+ import { DEFAULT_EXCLUDE_PATTERNS, DEFAULT_INCLUDE_PATTERNS } from "../utils/constants.mjs";
4
+ import { getFilesWithGlob, loadGitignore } from "../utils/file-utils.mjs";
5
+ import { getCurrentGitHead, getModifiedFilesBetweenCommits } from "../utils/utils.mjs";
97
6
 
98
7
  export default async function loadSources({
99
8
  sources = [],
@@ -143,14 +52,8 @@ export default async function loadSources({
143
52
  : [excludePatterns]
144
53
  : [];
145
54
 
146
- finalIncludePatterns = [
147
- ...DEFAULT_INCLUDE_PATTERNS,
148
- ...userInclude,
149
- ];
150
- finalExcludePatterns = [
151
- ...DEFAULT_EXCLUDE_PATTERNS,
152
- ...userExclude,
153
- ];
55
+ finalIncludePatterns = [...DEFAULT_INCLUDE_PATTERNS, ...userInclude];
56
+ finalExcludePatterns = [...DEFAULT_EXCLUDE_PATTERNS, ...userExclude];
154
57
  } else {
155
58
  // Use only user patterns
156
59
  if (includePatterns) {
@@ -170,7 +73,7 @@ export default async function loadSources({
170
73
  dir,
171
74
  finalIncludePatterns,
172
75
  finalExcludePatterns,
173
- gitignorePatterns
76
+ gitignorePatterns,
174
77
  );
175
78
  allFiles = allFiles.concat(filesInDir);
176
79
  }
@@ -194,7 +97,7 @@ export default async function loadSources({
194
97
  sourceId: relativePath,
195
98
  content,
196
99
  };
197
- })
100
+ }),
198
101
  );
199
102
 
200
103
  // Get the last structure plan result
@@ -253,19 +156,29 @@ export default async function loadSources({
253
156
  try {
254
157
  currentGitHead = getCurrentGitHead();
255
158
  if (currentGitHead && currentGitHead !== lastGitHead) {
256
- modifiedFiles = getModifiedFilesBetweenCommits(
257
- lastGitHead,
258
- currentGitHead
259
- );
260
- console.log(
261
- `Detected ${modifiedFiles.length} modified files since last generation`
262
- );
159
+ modifiedFiles = getModifiedFilesBetweenCommits(lastGitHead, currentGitHead);
160
+ console.log(`Detected ${modifiedFiles.length} modified files since last generation`);
263
161
  }
264
162
  } catch (error) {
265
163
  console.warn("Failed to detect git changes:", error.message);
266
164
  }
267
165
  }
268
166
 
167
+ // Count words and lines in allSources
168
+ let totalWords = 0;
169
+ let totalLines = 0;
170
+
171
+ for (const source of Object.values(allSources)) {
172
+ if (typeof source === "string") {
173
+ // Count English words (simple regex for words containing a-zA-Z)
174
+ const words = source.match(/[a-zA-Z]+/g) || [];
175
+ totalWords += words.length;
176
+
177
+ // Count lines
178
+ totalLines += source.split("\n").length;
179
+ }
180
+ }
181
+
269
182
  return {
270
183
  datasourcesList: sourceFiles,
271
184
  datasources: allSources,
@@ -273,6 +186,8 @@ export default async function loadSources({
273
186
  originalStructurePlan,
274
187
  files,
275
188
  modifiedFiles,
189
+ totalWords,
190
+ totalLines,
276
191
  };
277
192
  }
278
193
 
@@ -290,18 +205,15 @@ loadSources.input_schema = {
290
205
  },
291
206
  includePatterns: {
292
207
  anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
293
- description:
294
- "Glob patterns to filter files by path or filename. If not set, include all.",
208
+ description: "Glob patterns to filter files by path or filename. If not set, include all.",
295
209
  },
296
210
  excludePatterns: {
297
211
  anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
298
- description:
299
- "Glob patterns to exclude files by path or filename. If not set, exclude none.",
212
+ description: "Glob patterns to exclude files by path or filename. If not set, exclude none.",
300
213
  },
301
214
  useDefaultPatterns: {
302
215
  type: "boolean",
303
- description:
304
- "Whether to use default include/exclude patterns. Defaults to true.",
216
+ description: "Whether to use default include/exclude patterns. Defaults to true.",
305
217
  },
306
218
  "doc-path": {
307
219
  type: "string",