ai-readme-mcp 0.2.0 → 0.3.1

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/dist/index.js CHANGED
@@ -305,29 +305,56 @@ async function getContextForFile(input) {
305
305
  let promptText = `## \u{1F4DA} Project Context for: ${filePath}
306
306
 
307
307
  `;
308
- for (const ctx of formattedContexts) {
309
- if (ctx.relevance === "root") {
310
- promptText += `### Root Conventions (${ctx.path})
308
+ if (contexts.length === 0) {
309
+ promptText += `\u26A0\uFE0F **No AI_README.md files found in this project.**
311
310
 
312
311
  `;
313
- } else if (ctx.relevance === "direct") {
314
- promptText += `### Direct Module Conventions (${ctx.path})
312
+ promptText += `To get the most out of AI assistance, consider creating an AI_README.md file to document:
313
+ `;
314
+ promptText += `- Project architecture and conventions
315
+ `;
316
+ promptText += `- Coding standards and best practices
317
+ `;
318
+ promptText += `- Testing requirements
319
+ `;
320
+ promptText += `- Common patterns to follow
315
321
 
316
322
  `;
317
- } else {
318
- promptText += `### Parent Module Conventions (${ctx.path})
323
+ promptText += `**Quick Start:**
324
+ `;
325
+ promptText += `Use the \`init_ai_readme\` tool to create a template:
326
+ `;
327
+ promptText += `- Creates AI_README.md from a customizable template
328
+ `;
329
+ promptText += `- Helps maintain consistency across your team
330
+ `;
331
+ promptText += `- Improves AI output quality
332
+ `;
333
+ } else {
334
+ for (const ctx of formattedContexts) {
335
+ if (ctx.relevance === "root") {
336
+ promptText += `### Root Conventions (${ctx.path})
319
337
 
320
338
  `;
339
+ } else if (ctx.relevance === "direct") {
340
+ promptText += `### Direct Module Conventions (${ctx.path})
341
+
342
+ `;
343
+ } else {
344
+ promptText += `### Parent Module Conventions (${ctx.path})
345
+
346
+ `;
347
+ }
348
+ promptText += ctx.content + "\n\n";
321
349
  }
322
- promptText += ctx.content + "\n\n";
323
- }
324
- promptText += `---
350
+ promptText += `---
325
351
  **Important Reminders:**
326
352
  `;
327
- promptText += `- Follow the above conventions when making changes
353
+ promptText += `- Follow the above conventions when making changes
328
354
  `;
329
- promptText += `- If your changes affect architecture or conventions, consider updating the relevant AI_README
355
+ promptText += `- If your changes affect architecture or conventions, consider updating the relevant AI_README
330
356
  `;
357
+ }
331
358
  return {
332
359
  filePath,
333
360
  totalContexts: contexts.length,
@@ -883,53 +910,199 @@ async function validateAIReadmes(input) {
883
910
 
884
911
  // src/tools/init.ts
885
912
  import { z as z5 } from "zod";
886
- import { readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
887
- import { join as join4, dirname as dirname4 } from "path";
888
- import { existsSync as existsSync3 } from "fs";
913
+ import { readFile as readFile6, writeFile as writeFile2 } from "fs/promises";
914
+ import { join as join5, dirname as dirname4, relative } from "path";
915
+ import { existsSync as existsSync4 } from "fs";
889
916
  import { fileURLToPath } from "url";
917
+
918
+ // src/core/detector.ts
919
+ import { readFile as readFile5, readdir, stat } from "fs/promises";
920
+ import { join as join4 } from "path";
921
+ import { existsSync as existsSync3 } from "fs";
922
+ var ProjectDetector = class {
923
+ constructor(targetPath) {
924
+ this.targetPath = targetPath;
925
+ }
926
+ /**
927
+ * Detect project information by analyzing files and structure
928
+ */
929
+ async detect() {
930
+ const info = {
931
+ projectName: "Project",
932
+ projectType: "unknown",
933
+ language: "JavaScript",
934
+ hasTests: false,
935
+ mainDirs: []
936
+ };
937
+ const packageJsonPath = join4(this.targetPath, "package.json");
938
+ if (existsSync3(packageJsonPath)) {
939
+ await this.analyzePackageJson(packageJsonPath, info);
940
+ }
941
+ if (existsSync3(join4(this.targetPath, "requirements.txt")) || existsSync3(join4(this.targetPath, "setup.py")) || existsSync3(join4(this.targetPath, "pyproject.toml"))) {
942
+ info.language = "Python";
943
+ }
944
+ if (existsSync3(join4(this.targetPath, "go.mod"))) {
945
+ info.language = "Go";
946
+ }
947
+ if (existsSync3(join4(this.targetPath, "Cargo.toml"))) {
948
+ info.language = "Rust";
949
+ }
950
+ if (existsSync3(join4(this.targetPath, "pom.xml")) || existsSync3(join4(this.targetPath, "build.gradle"))) {
951
+ info.language = "Java";
952
+ }
953
+ await this.analyzeStructure(info);
954
+ return info;
955
+ }
956
+ /**
957
+ * Analyze package.json to extract project information
958
+ */
959
+ async analyzePackageJson(path, info) {
960
+ try {
961
+ const content = await readFile5(path, "utf-8");
962
+ const pkg = JSON.parse(content);
963
+ if (pkg.name) {
964
+ info.projectName = pkg.name;
965
+ }
966
+ if (pkg.devDependencies?.typescript || pkg.dependencies?.typescript) {
967
+ info.language = "TypeScript";
968
+ }
969
+ if (pkg.dependencies?.react || pkg.devDependencies?.react) {
970
+ info.framework = "React";
971
+ } else if (pkg.dependencies?.vue || pkg.devDependencies?.vue) {
972
+ info.framework = "Vue";
973
+ } else if (pkg.dependencies?.["@angular/core"]) {
974
+ info.framework = "Angular";
975
+ } else if (pkg.dependencies?.next) {
976
+ info.framework = "Next.js";
977
+ } else if (pkg.dependencies?.express) {
978
+ info.framework = "Express";
979
+ } else if (pkg.dependencies?.nestjs || pkg.dependencies?.["@nestjs/core"]) {
980
+ info.framework = "NestJS";
981
+ }
982
+ const lockFiles = await readdir(this.targetPath);
983
+ if (lockFiles.includes("pnpm-lock.yaml")) {
984
+ info.packageManager = "pnpm";
985
+ } else if (lockFiles.includes("yarn.lock")) {
986
+ info.packageManager = "yarn";
987
+ } else if (lockFiles.includes("bun.lockb")) {
988
+ info.packageManager = "bun";
989
+ } else if (lockFiles.includes("package-lock.json")) {
990
+ info.packageManager = "npm";
991
+ }
992
+ if (pkg.workspaces || existsSync3(join4(this.targetPath, "pnpm-workspace.yaml"))) {
993
+ info.projectType = "monorepo";
994
+ }
995
+ if (!info.projectType || info.projectType === "unknown") {
996
+ if (pkg.main || pkg.exports) {
997
+ info.projectType = "library";
998
+ } else {
999
+ info.projectType = "application";
1000
+ }
1001
+ }
1002
+ if (pkg.dependencies) {
1003
+ info.dependencies = Object.keys(pkg.dependencies).slice(0, 5);
1004
+ }
1005
+ } catch (error) {
1006
+ }
1007
+ }
1008
+ /**
1009
+ * Analyze directory structure
1010
+ */
1011
+ async analyzeStructure(info) {
1012
+ try {
1013
+ const entries = await readdir(this.targetPath);
1014
+ const dirs = [];
1015
+ for (const entry of entries) {
1016
+ try {
1017
+ const entryPath = join4(this.targetPath, entry);
1018
+ const stats = await stat(entryPath);
1019
+ if (stats.isDirectory()) {
1020
+ if (["src", "lib", "app", "pages", "components", "api", "server", "client"].includes(entry)) {
1021
+ dirs.push(entry);
1022
+ }
1023
+ if (["test", "tests", "__tests__", "spec"].includes(entry)) {
1024
+ info.hasTests = true;
1025
+ }
1026
+ if (["apps", "packages", "modules"].includes(entry)) {
1027
+ info.projectType = "monorepo";
1028
+ dirs.push(entry);
1029
+ }
1030
+ }
1031
+ } catch {
1032
+ }
1033
+ }
1034
+ info.mainDirs = dirs;
1035
+ } catch (error) {
1036
+ }
1037
+ }
1038
+ };
1039
+
1040
+ // src/tools/init.ts
890
1041
  var initSchema = z5.object({
891
1042
  targetPath: z5.string().describe("Directory where AI_README.md will be created"),
892
1043
  projectName: z5.string().optional().describe("Project name to use in the template (optional)"),
893
- overwrite: z5.boolean().optional().describe("Whether to overwrite existing AI_README.md (default: false)")
1044
+ overwrite: z5.boolean().optional().describe("Whether to overwrite existing AI_README.md (default: false)"),
1045
+ smart: z5.boolean().optional().describe("Enable smart content generation based on project analysis (default: true)")
894
1046
  });
895
1047
  async function initAIReadme(input) {
896
1048
  const { targetPath, projectName, overwrite = false } = input;
1049
+ const smart = input.smart !== false;
1050
+ console.error(`[DEBUG] init_ai_readme: smart=${smart}, input.smart=${input.smart}`);
897
1051
  try {
898
- if (!existsSync3(targetPath)) {
1052
+ if (!existsSync4(targetPath)) {
899
1053
  return {
900
1054
  success: false,
901
1055
  error: `Target directory does not exist: ${targetPath}`,
902
1056
  message: `Failed to create AI_README.md: Directory not found`
903
1057
  };
904
1058
  }
905
- const readmePath = join4(targetPath, "AI_README.md");
906
- if (existsSync3(readmePath) && !overwrite) {
907
- return {
908
- success: false,
909
- error: "AI_README.md already exists",
910
- message: `AI_README.md already exists at ${readmePath}. Use overwrite: true to replace it.`,
911
- existingPath: readmePath
912
- };
1059
+ const readmePath = join5(targetPath, "AI_README.md");
1060
+ const fileExists = existsSync4(readmePath);
1061
+ let isEmpty = false;
1062
+ if (fileExists) {
1063
+ const existingContent = await readFile6(readmePath, "utf-8");
1064
+ isEmpty = existingContent.trim().length < 50;
1065
+ if (!isEmpty && !overwrite) {
1066
+ return {
1067
+ success: false,
1068
+ error: "AI_README.md already exists with content",
1069
+ message: `AI_README.md already exists at ${readmePath}. Use overwrite: true to replace it.`,
1070
+ existingPath: readmePath
1071
+ };
1072
+ }
913
1073
  }
914
- const __filename2 = fileURLToPath(import.meta.url);
915
- const __dirname2 = dirname4(__filename2);
916
- const templatePath = join4(__dirname2, "..", "..", "docs", "templates", "basic.md");
917
- if (!existsSync3(templatePath)) {
918
- return {
919
- success: false,
920
- error: `Template file not found: ${templatePath}`,
921
- message: "Template file is missing. Please check installation."
922
- };
1074
+ let content;
1075
+ let detectedInfo = {};
1076
+ if (smart) {
1077
+ const detector = new ProjectDetector(targetPath);
1078
+ const projectInfo = await detector.detect();
1079
+ detectedInfo = projectInfo;
1080
+ const parentReadme = await findParentReadme(targetPath);
1081
+ const isSubdirectory = parentReadme !== null;
1082
+ content = await generateSmartContent(projectInfo, targetPath, isSubdirectory, parentReadme);
1083
+ } else {
1084
+ const __filename2 = fileURLToPath(import.meta.url);
1085
+ const __dirname2 = dirname4(__filename2);
1086
+ const templatePath = join5(__dirname2, "..", "..", "docs", "templates", "basic.md");
1087
+ if (!existsSync4(templatePath)) {
1088
+ return {
1089
+ success: false,
1090
+ error: `Template file not found: ${templatePath}`,
1091
+ message: "Template file is missing. Please check installation."
1092
+ };
1093
+ }
1094
+ content = await readFile6(templatePath, "utf-8");
1095
+ const finalProjectName = projectName || "Project Name";
1096
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, finalProjectName);
923
1097
  }
924
- let content = await readFile5(templatePath, "utf-8");
925
- const finalProjectName = projectName || "Project Name";
926
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, finalProjectName);
927
1098
  await writeFile2(readmePath, content, "utf-8");
1099
+ const action = fileExists ? isEmpty ? "filled" : "overwritten" : "created";
928
1100
  return {
929
1101
  success: true,
930
1102
  readmePath,
931
- projectName: finalProjectName,
932
- message: `Successfully created AI_README.md at ${readmePath}. Edit the file to customize it for your project.`
1103
+ action,
1104
+ projectInfo: smart ? detectedInfo : void 0,
1105
+ message: `Successfully ${action} AI_README.md at ${readmePath}. ${smart ? "Content generated based on project analysis." : "Edit the file to customize it for your project."}`
933
1106
  };
934
1107
  } catch (error) {
935
1108
  return {
@@ -939,6 +1112,152 @@ async function initAIReadme(input) {
939
1112
  };
940
1113
  }
941
1114
  }
1115
+ async function findParentReadme(targetPath) {
1116
+ let currentPath = dirname4(targetPath);
1117
+ const root = "/";
1118
+ while (currentPath !== root) {
1119
+ const readmePath = join5(currentPath, "AI_README.md");
1120
+ if (existsSync4(readmePath)) {
1121
+ const content = await readFile6(readmePath, "utf-8");
1122
+ if (content.trim().length > 50) {
1123
+ return currentPath;
1124
+ }
1125
+ }
1126
+ const parentPath = dirname4(currentPath);
1127
+ if (parentPath === currentPath) break;
1128
+ currentPath = parentPath;
1129
+ }
1130
+ return null;
1131
+ }
1132
+ async function generateSmartContent(projectInfo, targetPath, isSubdirectory, parentPath) {
1133
+ let content = `# ${projectInfo.projectName}
1134
+
1135
+ `;
1136
+ if (isSubdirectory && parentPath) {
1137
+ const relativePath = relative(parentPath, targetPath);
1138
+ content += `> This README extends the root AI_README.md with ${relativePath}-specific conventions.
1139
+
1140
+ `;
1141
+ }
1142
+ content += `## Architecture
1143
+
1144
+ `;
1145
+ content += `- **Type:** ${projectInfo.projectType}
1146
+ `;
1147
+ content += `- **Language:** ${projectInfo.language}
1148
+ `;
1149
+ if (projectInfo.framework) {
1150
+ content += `- **Framework:** ${projectInfo.framework}
1151
+ `;
1152
+ }
1153
+ content += `
1154
+ `;
1155
+ if (projectInfo.mainDirs && projectInfo.mainDirs.length > 0) {
1156
+ content += `## Directory Structure
1157
+
1158
+ `;
1159
+ for (const dir of projectInfo.mainDirs) {
1160
+ content += `- ${dir}/ - [Add description]
1161
+ `;
1162
+ }
1163
+ content += `
1164
+ `;
1165
+ }
1166
+ content += `## Coding Conventions
1167
+
1168
+ `;
1169
+ content += `### File Naming
1170
+ `;
1171
+ content += `- [Add your file naming conventions here]
1172
+
1173
+ `;
1174
+ content += `### Code Style
1175
+ `;
1176
+ if (projectInfo.language === "TypeScript") {
1177
+ content += `- Use TypeScript strict mode
1178
+ `;
1179
+ content += `- Prefer interfaces over types for object shapes
1180
+ `;
1181
+ } else if (projectInfo.language === "Python") {
1182
+ content += `- Follow PEP 8 style guide
1183
+ `;
1184
+ content += `- Use type hints
1185
+ `;
1186
+ }
1187
+ content += `- [Add more style guidelines]
1188
+
1189
+ `;
1190
+ if (projectInfo.framework) {
1191
+ content += `### ${projectInfo.framework} Conventions
1192
+ `;
1193
+ if (projectInfo.framework === "React") {
1194
+ content += `- Component naming: PascalCase
1195
+ `;
1196
+ content += `- Hooks naming: use prefix 'use'
1197
+ `;
1198
+ content += `- Prefer functional components
1199
+ `;
1200
+ } else if (projectInfo.framework === "Vue") {
1201
+ content += `- Component naming: PascalCase or kebab-case
1202
+ `;
1203
+ content += `- Use Composition API
1204
+ `;
1205
+ }
1206
+ content += `
1207
+ `;
1208
+ }
1209
+ content += `## Testing
1210
+
1211
+ `;
1212
+ if (projectInfo.hasTests) {
1213
+ content += `- Tests are located in test directories
1214
+ `;
1215
+ }
1216
+ if (projectInfo.packageManager) {
1217
+ content += `- Run: \`${projectInfo.packageManager} test\`
1218
+ `;
1219
+ } else {
1220
+ content += `- Run: [Add test command]
1221
+ `;
1222
+ }
1223
+ content += `- Coverage target: [Add target or "not enforced"]
1224
+
1225
+ `;
1226
+ if (projectInfo.dependencies && projectInfo.dependencies.length > 0) {
1227
+ content += `## Key Dependencies
1228
+
1229
+ `;
1230
+ for (const dep of projectInfo.dependencies) {
1231
+ content += `- ${dep} - [Add purpose]
1232
+ `;
1233
+ }
1234
+ content += `
1235
+ `;
1236
+ }
1237
+ content += `## Development
1238
+
1239
+ `;
1240
+ if (projectInfo.packageManager) {
1241
+ content += `- Install: \`${projectInfo.packageManager} install\`
1242
+ `;
1243
+ content += `- Dev: \`${projectInfo.packageManager} run dev\`
1244
+ `;
1245
+ content += `- Build: \`${projectInfo.packageManager} run build\`
1246
+ `;
1247
+ }
1248
+ content += `
1249
+ `;
1250
+ content += `## Important Notes
1251
+
1252
+ `;
1253
+ content += `- [Add critical information that AI should know]
1254
+ `;
1255
+ content += `- [Security considerations]
1256
+ `;
1257
+ content += `- [Performance considerations]
1258
+ `;
1259
+ return content;
1260
+ }
942
1261
 
943
1262
  // src/index.ts
944
1263
  var server = new Server(
@@ -962,12 +1281,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
962
1281
  },
963
1282
  {
964
1283
  name: "get_context_for_file",
965
- description: "Get relevant AI_README context for a specific file path. Returns formatted context from relevant README files to help understand project conventions.",
1284
+ description: "Get relevant AI_README context for a specific file path. Returns formatted context from relevant README files to help understand project conventions. Use this BEFORE creating or editing files to understand conventions - works even if the file does not exist yet.",
966
1285
  inputSchema: zodToJsonSchema(getContextSchema)
967
1286
  },
968
1287
  {
969
1288
  name: "update_ai_readme",
970
- description: "Update an AI_README.md file with specified operations (append, prepend, replace, insert-after, insert-before). Auto-validates after update. Changes are written directly; use Git for version control.",
1289
+ description: "Update an AI_README.md file when you discover important conventions, patterns, or rules while editing code. Use this to keep documentation in sync with code changes. Supports append, prepend, replace, insert-after, insert-before operations. Auto-validates after update. Note: Only update when you find NEW conventions not already documented - avoid duplicate updates.",
971
1290
  inputSchema: zodToJsonSchema(updateSchema)
972
1291
  },
973
1292
  {
@@ -977,7 +1296,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
977
1296
  },
978
1297
  {
979
1298
  name: "init_ai_readme",
980
- description: "Initialize a new AI_README.md file from a template. Creates a basic structure with common sections to help you get started quickly.",
1299
+ description: "Initialize or fill an AI_README.md file with smart content based on project analysis. Auto-detects project type, language, and framework. Use this to create new READMEs or fill empty ones.",
981
1300
  inputSchema: zodToJsonSchema(initSchema)
982
1301
  }
983
1302
  ]