@aigne/doc-smith 0.2.3 → 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,19 @@
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
+
10
+ ## [0.2.4](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.3...v0.2.4) (2025-08-07)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * polish agent output log ([40a2451](https://github.com/AIGNE-io/aigne-doc-smith/commit/40a245122ce4d8747e5b5dbe88be6986047c38ae))
16
+
3
17
  ## [0.2.3](https://github.com/AIGNE-io/aigne-doc-smith/compare/v0.2.2...v0.2.3) (2025-08-07)
4
18
 
5
19
 
@@ -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
  }
@@ -45,41 +45,41 @@ export default async function saveDocs({
45
45
  console.error("Failed to cleanup invalid .md files:", err.message);
46
46
  }
47
47
 
48
- // const message = `## ✅ Documentation Generated Successfully!
48
+ const message = `## ✅ Documentation Generated Successfully!
49
49
 
50
- // Successfully generated **${structurePlan.length}** documents and saved to: \`${docsDir}\`
50
+ Successfully generated **${structurePlan.length}** documents and saved to: \`${docsDir}\`
51
51
 
52
- // ### 🚀 Next Steps
52
+ ### 🚀 Next Steps
53
53
 
54
- // 1. Publish Documentation
54
+ 1. Publish Documentation
55
55
 
56
- // \`\`\`bash
57
- // aigne doc publish
58
- // \`\`\`
56
+ \`\`\`bash
57
+ aigne doc publish
58
+ \`\`\`
59
59
 
60
- // Get an online preview link to share with your team
60
+ Get an online preview link to share with your team
61
61
 
62
- // ### 🔧 Optional Improvements
62
+ ### 🔧 Optional Improvements
63
63
 
64
- // 1. Update Specific Documents
64
+ 1. Update Specific Documents
65
65
 
66
- // \`\`\`bash
67
- // aigne doc update
68
- // \`\`\`
66
+ \`\`\`bash
67
+ aigne doc update
68
+ \`\`\`
69
69
 
70
- // Regenerate content for specific documents
70
+ Regenerate content for specific documents
71
71
 
72
- // 2. Provide Structure Feedback
73
- // \`\`\`bash
74
- // aigne doc generate --feedback "Your feedback on document structure"
75
- // \`\`\`
76
- // Improve the overall documentation structure
72
+ 2. Provide Structure Feedback
73
+ \`\`\`bash
74
+ aigne doc generate --feedback "Your feedback on document structure"
75
+ \`\`\`
76
+ Improve the overall documentation structure
77
77
 
78
- // ---
79
- // `;
78
+ ---
79
+ `;
80
80
 
81
81
  return {
82
- // message,
82
+ message,
83
83
  };
84
84
  }
85
85
 
@@ -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.3",
3
+ "version": "0.2.5",
4
4
  "description": "",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -7,6 +7,7 @@
7
7
  - 结构规划要精炼,避免同一个功能被拆为了多个部分,当内容足够复杂,放在一起展示过长影响用户阅读时,考虑拆出子层级
8
8
  - **第一层 <= 7 项**,层级 <= 3 级;同一层使用统一语义(动词时态、名词单复数)
9
9
  - 如果当前部分是存在子文档,当前文档只展示简要的内容,引导用户到子文档中查看详细的内容
10
+ - 如果存在测试相关的代码,可以作为生成文档的参考,**不要为测试代码生成文档**
10
11
  - 总是在一开始包含下列内容:
11
12
  - Overview:简要说明产品能解决什么,产品能提供什么,产品的结构信息,让用户能快速有一个全面的认识,给出下一步的入口,引导用户阅读
12
13
  - Getting Started:内容包含安装、最小运行示例,让用户用通过一部分文档,在很短的时间就能跑通一个最简单的示例
@@ -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 {
@@ -595,7 +599,12 @@ function getDirectoryContents(dirPath, searchTerm = "") {
595
599
 
596
600
  for (const entry of entries) {
597
601
  const entryName = entry.name;
598
- const relativePath = path.join(dirPath, entryName);
602
+
603
+ // Preserve ./ prefix when dirPath is "./"
604
+ let relativePath = path.join(dirPath, entryName);
605
+ if (dirPath?.startsWith("./")) {
606
+ relativePath = `./${relativePath}`;
607
+ }
599
608
 
600
609
  // Filter by search term if provided
601
610
  if (
@@ -618,17 +627,24 @@ function getDirectoryContents(dirPath, searchTerm = "") {
618
627
 
619
628
  const isDirectory = entry.isDirectory();
620
629
 
621
- // Only include directories, skip files
622
- if (isDirectory) {
623
- items.push({
624
- name: relativePath,
625
- value: relativePath,
626
- });
627
- }
630
+ // Include both directories and files
631
+ items.push({
632
+ name: relativePath,
633
+ value: relativePath,
634
+ description: isDirectory ? "📁 Directory" : "📄 File",
635
+ });
628
636
  }
629
637
 
630
- // Sort alphabetically (all items are directories now)
631
- 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
+ });
632
648
 
633
649
  return items;
634
650
  } catch (error) {