@aigne/doc-smith 0.2.4 → 0.2.5

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.5](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.4...v0.2.5) (2025-08-08)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * polish cli process ([#17](https://github.com/AIGNE-io/aigne-doc-smith/issues/17)) ([4c94263](https://github.com/AIGNE-io/aigne-doc-smith/commit/4c9426378dff9ca3270bd0e455aa6fb1045f6abb))
9
+
3
10
  ## [0.2.4](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.3...v0.2.4) (2025-08-07)
4
11
 
5
12
 
@@ -128,6 +128,22 @@ export default async function checkDetailResult({
128
128
  `Found backticks in Mermaid node label in ${source} at line ${lineNumber}: "${label}" - backticks in node labels cause rendering issues in Mermaid diagrams`
129
129
  );
130
130
  }
131
+
132
+ // Check for edge descriptions with numbered list format
133
+ // Pattern: -- "1. description" --> or similar variants
134
+ const edgeDescriptionRegex = /--\s*"([^"]*)"\s*-->/g;
135
+ let edgeMatch;
136
+
137
+ while ((edgeMatch = edgeDescriptionRegex.exec(line)) !== null) {
138
+ const description = edgeMatch[1];
139
+ // Check if description starts with number followed by period
140
+ if (/^\d+\.\s/.test(description)) {
141
+ isApproved = false;
142
+ detailFeedback.push(
143
+ `Unsupported markdown: list - Found numbered list format in Mermaid edge description in ${source} at line ${lineNumber}: "${description}" - numbered lists in edge descriptions are not supported`
144
+ );
145
+ }
146
+ }
131
147
  }
132
148
  }
133
149
 
@@ -31,20 +31,10 @@ skills:
31
31
  input_schema:
32
32
  type: object
33
33
  properties:
34
- config:
35
- type: string
36
- description: Path to the config file
37
- default: ./doc-smith/config.yaml
38
- # nodeName:
39
- # type: string
40
- # description: Name of the section to generate documentation for
41
- # default: Section
42
- # locale:
43
- # type: string
44
- # description: Primary language for documentation (e.g., zh, en)
45
- # targetAudience:
34
+ # config:
46
35
  # type: string
47
- # description: Target audience for the documentation
36
+ # description: Path to the config file
37
+ # default: ./.aigne/doc-smith/config.yaml
48
38
  glossary:
49
39
  type: string
50
40
  description: Glossary of terms for consistent terminology
@@ -54,49 +44,6 @@ input_schema:
54
44
  feedback:
55
45
  type: string
56
46
  description: Feedback for content improvement
57
- # sources:
58
- # type: array
59
- # items:
60
- # type: string
61
- # description: Source code files for documentation generation
62
- # sourcesPath:
63
- # type: array
64
- # description: Source code paths
65
- # items:
66
- # type: string
67
- # default:
68
- # - ./
69
- # includePatterns:
70
- # type: array
71
- # description: File patterns to include
72
- # items:
73
- # type: string
74
- # excludePatterns:
75
- # type: array
76
- # description: File patterns to exclude
77
- # items:
78
- # type: string
79
- # outputDir:
80
- # type: string
81
- # description: Output directory for intermediate files
82
- # default: ./doc-smith/output
83
- # docsDir:
84
- # type: string
85
- # description: Directory to save generated documentation
86
- # default: ./doc-smith/docs
87
- # additionalInformation:
88
- # type: string
89
- # description: Additional context or information for documentation
90
- # translateLanguages:
91
- # type: array
92
- # items:
93
- # type: string
94
- # description: Target languages for translation (e.g., zh, en)
95
- # labels:
96
- # type: array
97
- # items:
98
- # type: string
99
- # description: Tags or labels for categorization
100
47
  output_schema:
101
48
  type: object
102
49
  properties:
@@ -40,76 +40,19 @@ skills:
40
40
  input_schema:
41
41
  type: object
42
42
  properties:
43
- config:
44
- type: string
45
- description: Path to the config file
46
- default: ./doc-smith/config.yaml
47
- # nodeName:
48
- # type: string
49
- # description: Name of the section to generate documentation for
50
- # default: Section
51
- # rules:
52
- # type: string
53
- # description: Documentation generation requirements and rules
54
- # locale:
55
- # type: string
56
- # description: Primary language for documentation (e.g., zh, en)
57
- # sources:
58
- # type: array
59
- # items:
60
- # type: string
61
- # description: Source code files for documentation generation
62
- # sourcesPath:
63
- # type: array
64
- # description: Source code paths
65
- # items:
66
- # type: string
67
- # default:
68
- # - ./
69
- # includePatterns:
70
- # type: array
71
- # description: File patterns to include
72
- # items:
73
- # type: string
74
- # excludePatterns:
75
- # type: array
76
- # description: File patterns to exclude
77
- # items:
78
- # type: string
79
- # docsDir:
43
+ # config:
80
44
  # type: string
81
- # description: Directory to save generated documentation
82
- # default: ./doc-smith/docs
83
- # outputDir:
84
- # type: string
85
- # description: Output directory for intermediate files
86
- # default: ./doc-smith/output
87
- # translateLanguages:
88
- # type: array
89
- # items:
90
- # type: string
91
- # description: Target languages for translation (e.g., zh, en)
45
+ # description: Path to the config file
46
+ # default: ./.aigne/doc-smith/config.yaml
92
47
  glossary:
93
48
  type: string
94
49
  description: Glossary of terms for consistent terminology
95
- # additionalInformation:
96
- # type: string
97
- # description: Additional context or information for documentation
98
- # docsType:
99
- # type: string
100
- # description: Type of documentation (general, getting-started, reference, faq)
101
- # default: general
102
50
  feedback:
103
51
  type: string
104
52
  description: Feedback for structure planning adjustments
105
53
  forceRegenerate:
106
54
  type: boolean
107
55
  description: Force regenerate all documentation
108
- # labels:
109
- # type: array
110
- # items:
111
- # type: string
112
- # description: Tags or labels for categorization
113
56
  required:
114
57
  - config
115
58
  mode: sequential
@@ -20,7 +20,7 @@ const PRESS_ENTER_TO_FINISH = "Press Enter to finish";
20
20
  */
21
21
  export default async function init(
22
22
  {
23
- outputPath = "./doc-smith",
23
+ outputPath = ".aigne/doc-smith",
24
24
  fileName = "config.yaml",
25
25
  skipIfExists = false,
26
26
  },
@@ -40,11 +40,9 @@ export default async function init(
40
40
  const input = {};
41
41
 
42
42
  // 1. Document generation rules with style selection
43
- console.log("📝 Step 1/6: Document Generation Rules");
44
-
45
43
  // Let user select a document style
46
44
  const styleChoice = await options.prompts.select({
47
- message: "Choose your documentation style:",
45
+ message: "📝 Step 1/6: Choose your documentation style:",
48
46
  choices: Object.entries(DOCUMENT_STYLES).map(([key, style]) => ({
49
47
  name: `${style.name} - ${style.rules}`,
50
48
  value: key,
@@ -65,11 +63,9 @@ export default async function init(
65
63
  input.rules = rules.trim();
66
64
 
67
65
  // 2. Target audience selection
68
- console.log("\n👥 Step 2/6: Target Audience");
69
-
70
66
  // Let user select target audience
71
67
  const audienceChoice = await options.prompts.select({
72
- message: "Who is your target audience?",
68
+ message: "👥 Step 2/6: Who is your target audience?",
73
69
  choices: Object.entries(TARGET_AUDIENCES).map(([key, audience]) => ({
74
70
  name: audience,
75
71
  value: key,
@@ -90,11 +86,9 @@ export default async function init(
90
86
  input.targetAudience = targetAudience.trim();
91
87
 
92
88
  // 3. Language settings
93
- console.log("\n🌐 Step 3/6: Primary Language");
94
-
95
89
  // Let user select primary language from supported list
96
90
  const primaryLanguageChoice = await options.prompts.select({
97
- message: "Choose primary documentation language:",
91
+ message: "🌐 Step 3/6: Choose primary documentation language:",
98
92
  choices: SUPPORTED_LANGUAGES.map((lang) => ({
99
93
  name: `${lang.label} - ${lang.sample}`,
100
94
  value: lang.code,
@@ -104,15 +98,13 @@ export default async function init(
104
98
  input.locale = primaryLanguageChoice;
105
99
 
106
100
  // 4. Translation languages
107
- console.log("\n🔄 Step 4/6: Translation Languages");
108
-
109
101
  // Filter out the primary language from available choices
110
102
  const availableTranslationLanguages = SUPPORTED_LANGUAGES.filter(
111
103
  (lang) => lang.code !== primaryLanguageChoice
112
104
  );
113
105
 
114
106
  const translateLanguageChoices = await options.prompts.checkbox({
115
- message: "Select translation languages:",
107
+ message: "🔄 Step 4/6: Select translation languages:",
116
108
  choices: availableTranslationLanguages.map((lang) => ({
117
109
  name: `${lang.label} - ${lang.sample}`,
118
110
  value: lang.code,
@@ -122,9 +114,8 @@ export default async function init(
122
114
  input.translateLanguages = translateLanguageChoices;
123
115
 
124
116
  // 5. Documentation directory
125
- console.log("\n📁 Step 5/6: Output Directory");
126
117
  const docsDirInput = await options.prompts.input({
127
- message: `Where to save generated docs:`,
118
+ message: `📁 Step 5/6: Where to save generated docs:`,
128
119
  default: `${outputPath}/docs`,
129
120
  });
130
121
  input.docsDir = docsDirInput.trim() || `${outputPath}/docs`;
@@ -22,8 +22,8 @@ export default async function loadConfig({ config }) {
22
22
  nodeName: "Section",
23
23
  locale: "en",
24
24
  sourcesPath: ["./"],
25
- docDir: "./doc-smith/docs",
26
- outputDir: "./doc-smith/output",
25
+ docsDir: "./.aigne/doc-smith/docs",
26
+ outputDir: "./.aigne/doc-smith/output",
27
27
  lastGitHead: parsedConfig.lastGitHead || "",
28
28
  ...parsedConfig,
29
29
  };
@@ -38,7 +38,7 @@ loadConfig.input_schema = {
38
38
  properties: {
39
39
  config: {
40
40
  type: "string",
41
- default: "./doc-smith/config.yaml",
41
+ default: "./.aigne/doc-smith/config.yaml",
42
42
  },
43
43
  },
44
44
  };
@@ -1,4 +1,4 @@
1
- import { access, readFile } from "node:fs/promises";
1
+ import { access, readFile, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { glob } from "glob";
4
4
  import {
@@ -115,50 +115,65 @@ export default async function loadSources({
115
115
 
116
116
  for (const dir of paths) {
117
117
  try {
118
- // Load .gitignore for this directory
119
- const gitignorePatterns = await loadGitignore(dir);
118
+ // Check if the path is a file or directory
119
+ const stats = await stat(dir);
120
120
 
121
- // Prepare patterns
122
- let finalIncludePatterns = null;
123
- let finalExcludePatterns = null;
121
+ if (stats.isFile()) {
122
+ // If it's a file, add it directly without filtering
123
+ allFiles.push(dir);
124
+ } else if (stats.isDirectory()) {
125
+ // If it's a directory, use the existing glob logic
126
+ // Load .gitignore for this directory
127
+ const gitignorePatterns = await loadGitignore(dir);
124
128
 
125
- if (useDefaultPatterns) {
126
- // Merge with default patterns
127
- const userInclude = includePatterns
128
- ? Array.isArray(includePatterns)
129
- ? includePatterns
130
- : [includePatterns]
131
- : [];
132
- const userExclude = excludePatterns
133
- ? Array.isArray(excludePatterns)
134
- ? excludePatterns
135
- : [excludePatterns]
136
- : [];
129
+ // Prepare patterns
130
+ let finalIncludePatterns = null;
131
+ let finalExcludePatterns = null;
137
132
 
138
- finalIncludePatterns = [...DEFAULT_INCLUDE_PATTERNS, ...userInclude];
139
- finalExcludePatterns = [...DEFAULT_EXCLUDE_PATTERNS, ...userExclude];
140
- } else {
141
- // Use only user patterns
142
- if (includePatterns) {
143
- finalIncludePatterns = Array.isArray(includePatterns)
144
- ? includePatterns
145
- : [includePatterns];
146
- }
147
- if (excludePatterns) {
148
- finalExcludePatterns = Array.isArray(excludePatterns)
149
- ? excludePatterns
150
- : [excludePatterns];
133
+ if (useDefaultPatterns) {
134
+ // Merge with default patterns
135
+ const userInclude = includePatterns
136
+ ? Array.isArray(includePatterns)
137
+ ? includePatterns
138
+ : [includePatterns]
139
+ : [];
140
+ const userExclude = excludePatterns
141
+ ? Array.isArray(excludePatterns)
142
+ ? excludePatterns
143
+ : [excludePatterns]
144
+ : [];
145
+
146
+ finalIncludePatterns = [
147
+ ...DEFAULT_INCLUDE_PATTERNS,
148
+ ...userInclude,
149
+ ];
150
+ finalExcludePatterns = [
151
+ ...DEFAULT_EXCLUDE_PATTERNS,
152
+ ...userExclude,
153
+ ];
154
+ } else {
155
+ // Use only user patterns
156
+ if (includePatterns) {
157
+ finalIncludePatterns = Array.isArray(includePatterns)
158
+ ? includePatterns
159
+ : [includePatterns];
160
+ }
161
+ if (excludePatterns) {
162
+ finalExcludePatterns = Array.isArray(excludePatterns)
163
+ ? excludePatterns
164
+ : [excludePatterns];
165
+ }
151
166
  }
152
- }
153
167
 
154
- // Get files using glob
155
- const filesInDir = await getFilesWithGlob(
156
- dir,
157
- finalIncludePatterns,
158
- finalExcludePatterns,
159
- gitignorePatterns
160
- );
161
- allFiles = allFiles.concat(filesInDir);
168
+ // Get files using glob
169
+ const filesInDir = await getFilesWithGlob(
170
+ dir,
171
+ finalIncludePatterns,
172
+ finalExcludePatterns,
173
+ gitignorePatterns
174
+ );
175
+ allFiles = allFiles.concat(filesInDir);
176
+ }
162
177
  } catch (err) {
163
178
  if (err.code !== "ENOENT") throw err;
164
179
  }
@@ -1,8 +1,12 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- export default async function saveOutput({ savePath, fileName, saveKey, ...rest }) {
5
- console.log("saveOutput", savePath, fileName, saveKey, rest);
4
+ export default async function saveOutput({
5
+ savePath,
6
+ fileName,
7
+ saveKey,
8
+ ...rest
9
+ }) {
6
10
  if (!(saveKey in rest)) {
7
11
  console.warn(`saveKey "${saveKey}" not found in input, skip saving.`);
8
12
  return {
@@ -13,7 +17,9 @@ export default async function saveOutput({ savePath, fileName, saveKey, ...rest
13
17
 
14
18
  const value = rest[saveKey];
15
19
  const content =
16
- typeof value === "object" && value !== null ? JSON.stringify(value, null, 2) : String(value);
20
+ typeof value === "object" && value !== null
21
+ ? JSON.stringify(value, null, 2)
22
+ : String(value);
17
23
  await fs.mkdir(savePath, { recursive: true });
18
24
  const filePath = join(savePath, fileName);
19
25
  await fs.writeFile(filePath, content, "utf8");
@@ -10,10 +10,10 @@ 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: ./doc-smith/config.yaml
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
@@ -18,7 +18,7 @@ input_schema:
18
18
  config:
19
19
  type: string
20
20
  description: Path to the config file
21
- default: ./doc-smith/config.yaml
21
+ default: ./.aigne/doc-smith/config.yaml
22
22
  output_schema:
23
23
  type: object
24
24
  properties:
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
- const docsDir = path.join(process.cwd(), "doc-smith", "docs");
4
+ const docsDir = path.join(process.cwd(), "./.aigne/doc-smith", "docs");
5
5
 
6
6
  export default async function getDocDetail({ path: docPath }) {
7
7
  try {
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
 
4
4
  const structureDir = path.join(
5
5
  process.cwd(),
6
- "doc-smith",
6
+ "./.aigne/doc-smith",
7
7
  "output",
8
8
  "structure-plan.json"
9
9
  );
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
 
4
- const docsDir = path.join(process.cwd(), "doc-smith", "docs");
4
+ const docsDir = path.join(process.cwd(), "./.aigne/doc-smith", "docs");
5
5
 
6
6
  export default async function readDocContent({
7
7
  relevantDocPaths,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/doc-smith",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -25,6 +25,7 @@ export const DEFAULT_INCLUDE_PATTERNS = [
25
25
  export const DEFAULT_EXCLUDE_PATTERNS = [
26
26
  "aigne-docs/**",
27
27
  "doc-smith/**",
28
+ ".aigne/**",
28
29
  "assets/**",
29
30
  "data/**",
30
31
  "images/**",
@@ -62,14 +63,14 @@ export const DEFAULT_EXCLUDE_PATTERNS = [
62
63
  // Supported languages for documentation
63
64
  export const SUPPORTED_LANGUAGES = [
64
65
  { code: "en", label: "English (en)", sample: "Hello" },
65
- { code: "zh-CN", label: "简体中文 (zh-CN)", sample: "你好" },
66
+ { code: "zh", label: "简体中文 (zh)", sample: "你好" },
66
67
  { code: "zh-TW", label: "繁體中文 (zh-TW)", sample: "你好" },
67
68
  { code: "ja", label: "日本語 (ja)", sample: "こんにちは" },
68
69
  { code: "ko", label: "한국어 (ko)", sample: "안녕하세요" },
69
70
  { code: "es", label: "Español (es)", sample: "Hola" },
70
71
  { code: "fr", label: "Français (fr)", sample: "Bonjour" },
71
72
  { code: "de", label: "Deutsch (de)", sample: "Hallo" },
72
- { code: "pt-BR", label: "Português (pt-BR)", sample: "Olá" },
73
+ { code: "pt", label: "Português (pt)", sample: "Olá" },
73
74
  { code: "ru", label: "Русский (ru)", sample: "Привет" },
74
75
  { code: "it", label: "Italiano (it)", sample: "Ciao" },
75
76
  { code: "ar", label: "العربية (ar)", sample: "مرحبا" },
package/utils/utils.mjs CHANGED
@@ -162,7 +162,7 @@ export async function saveGitHeadToConfig(gitHead) {
162
162
  }
163
163
 
164
164
  try {
165
- const docSmithDir = path.join(process.cwd(), "doc-smith");
165
+ const docSmithDir = path.join(process.cwd(), "./.aigne/doc-smith");
166
166
  if (!existsSync(docSmithDir)) {
167
167
  mkdirSync(docSmithDir, { recursive: true });
168
168
  }
@@ -345,7 +345,11 @@ export function hasFileChangesBetweenCommits(
345
345
  * @returns {Promise<Object|null>} - The config object or null if file doesn't exist
346
346
  */
347
347
  export async function loadConfigFromFile() {
348
- const configPath = path.join(process.cwd(), "doc-smith", "config.yaml");
348
+ const configPath = path.join(
349
+ process.cwd(),
350
+ "./.aigne/doc-smith",
351
+ "config.yaml"
352
+ );
349
353
 
350
354
  try {
351
355
  if (!existsSync(configPath)) {
@@ -371,7 +375,7 @@ export async function saveValueToConfig(key, value) {
371
375
  }
372
376
 
373
377
  try {
374
- const docSmithDir = path.join(process.cwd(), "doc-smith");
378
+ const docSmithDir = path.join(process.cwd(), "./.aigne/doc-smith");
375
379
  if (!existsSync(docSmithDir)) {
376
380
  mkdirSync(docSmithDir, { recursive: true });
377
381
  }
@@ -472,20 +476,20 @@ export function validatePaths(paths) {
472
476
  }
473
477
 
474
478
  /**
475
- * Check if input is a valid directory and add it to results if so
479
+ * Check if input is a valid directory or file and add it to results if so
476
480
  * @param {string} searchTerm - The search term to check
477
481
  * @param {Array} results - The results array to modify
478
482
  */
479
- function addExactDirectoryMatch(searchTerm, results) {
483
+ function addExactPathMatch(searchTerm, results) {
480
484
  const inputValidation = validatePath(searchTerm);
481
485
  if (inputValidation.isValid) {
482
486
  const stats = statSync(normalizePath(searchTerm));
483
- if (stats.isDirectory()) {
484
- results.unshift({
485
- name: searchTerm,
486
- value: searchTerm,
487
- });
488
- }
487
+ const isDirectory = stats.isDirectory();
488
+ results.unshift({
489
+ name: searchTerm,
490
+ value: searchTerm,
491
+ description: isDirectory ? "📁 Directory" : "📄 File",
492
+ });
489
493
  }
490
494
  }
491
495
 
@@ -510,7 +514,7 @@ export function getAvailablePaths(userInput = "") {
510
514
  const dirPath = path.dirname(searchTerm);
511
515
  const fileName = path.basename(searchTerm);
512
516
  results = getDirectoryContents(dirPath, fileName);
513
- addExactDirectoryMatch(searchTerm, results);
517
+ addExactPathMatch(searchTerm, results);
514
518
  }
515
519
  // Handle relative paths
516
520
  else if (searchTerm.startsWith("./") || searchTerm.startsWith("../")) {
@@ -519,7 +523,7 @@ export function getAvailablePaths(userInput = "") {
519
523
  if (lastSlashIndex === -1) {
520
524
  // No slash found, treat as current directory search
521
525
  results = getDirectoryContents("./", searchTerm);
522
- addExactDirectoryMatch(searchTerm, results);
526
+ addExactPathMatch(searchTerm, results);
523
527
  } else {
524
528
  const dirPath = searchTerm.substring(0, lastSlashIndex + 1);
525
529
  const fileName = searchTerm.substring(lastSlashIndex + 1);
@@ -537,13 +541,13 @@ export function getAvailablePaths(userInput = "") {
537
541
  }
538
542
 
539
543
  results = getDirectoryContents(dirPath, fileName);
540
- addExactDirectoryMatch(searchTerm, results);
544
+ addExactPathMatch(searchTerm, results);
541
545
  }
542
546
  }
543
547
  // Handle simple file/directory names (search in current directory)
544
548
  else {
545
549
  results = getDirectoryContents("./", searchTerm);
546
- addExactDirectoryMatch(searchTerm, results);
550
+ addExactPathMatch(searchTerm, results);
547
551
  }
548
552
 
549
553
  // Remove duplicates based on value (path)
@@ -571,7 +575,7 @@ export function getAvailablePaths(userInput = "") {
571
575
  * Get directory contents for a specific path
572
576
  * @param {string} dirPath - Directory path to search in
573
577
  * @param {string} searchTerm - Optional search term to filter results
574
- * @returns {Array<Object>} - Array of path objects
578
+ * @returns {Array<Object>} - Array of path objects (both files and directories)
575
579
  */
576
580
  function getDirectoryContents(dirPath, searchTerm = "") {
577
581
  try {
@@ -623,17 +627,24 @@ function getDirectoryContents(dirPath, searchTerm = "") {
623
627
 
624
628
  const isDirectory = entry.isDirectory();
625
629
 
626
- // Only include directories, skip files
627
- if (isDirectory) {
628
- items.push({
629
- name: relativePath,
630
- value: relativePath,
631
- });
632
- }
630
+ // Include both directories and files
631
+ items.push({
632
+ name: relativePath,
633
+ value: relativePath,
634
+ description: isDirectory ? "📁 Directory" : "📄 File",
635
+ });
633
636
  }
634
637
 
635
- // Sort alphabetically (all items are directories now)
636
- items.sort((a, b) => a.name.localeCompare(b.name));
638
+ // Sort alphabetically (directories first, then files)
639
+ items.sort((a, b) => {
640
+ const aIsDir = a.description === "📁 Directory";
641
+ const bIsDir = b.description === "📁 Directory";
642
+
643
+ if (aIsDir && !bIsDir) return -1;
644
+ if (!aIsDir && bIsDir) return 1;
645
+
646
+ return a.name.localeCompare(b.name);
647
+ });
637
648
 
638
649
  return items;
639
650
  } catch (error) {