prelude-context 1.6.0 → 1.7.0

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.
Files changed (35) hide show
  1. package/README.md +63 -4
  2. package/dist/bin/prelude.js +2 -0
  3. package/dist/bin/prelude.js.map +1 -1
  4. package/dist/src/commands/export.d.ts.map +1 -1
  5. package/dist/src/commands/export.js +5 -3
  6. package/dist/src/commands/export.js.map +1 -1
  7. package/dist/src/commands/init.d.ts.map +1 -1
  8. package/dist/src/commands/init.js +151 -0
  9. package/dist/src/commands/init.js.map +1 -1
  10. package/dist/src/commands/validate.d.ts +3 -0
  11. package/dist/src/commands/validate.d.ts.map +1 -0
  12. package/dist/src/commands/validate.js +203 -0
  13. package/dist/src/commands/validate.js.map +1 -0
  14. package/dist/src/constants.d.ts +1 -1
  15. package/dist/src/constants.d.ts.map +1 -1
  16. package/dist/src/constants.js +4 -1
  17. package/dist/src/constants.js.map +1 -1
  18. package/dist/src/core/claude-md-parser.d.ts +33 -0
  19. package/dist/src/core/claude-md-parser.d.ts.map +1 -0
  20. package/dist/src/core/claude-md-parser.js +410 -0
  21. package/dist/src/core/claude-md-parser.js.map +1 -0
  22. package/dist/src/core/exporter.d.ts +2 -1
  23. package/dist/src/core/exporter.d.ts.map +1 -1
  24. package/dist/src/core/exporter.js +159 -1
  25. package/dist/src/core/exporter.js.map +1 -1
  26. package/dist/src/core/infer.d.ts.map +1 -1
  27. package/dist/src/core/infer.js +403 -21
  28. package/dist/src/core/infer.js.map +1 -1
  29. package/dist/src/core/source-scanner.d.ts.map +1 -1
  30. package/dist/src/core/source-scanner.js +58 -1
  31. package/dist/src/core/source-scanner.js.map +1 -1
  32. package/dist/src/utils/fs.d.ts.map +1 -1
  33. package/dist/src/utils/fs.js +2 -1
  34. package/dist/src/utils/fs.js.map +1 -1
  35. package/package.json +1 -1
@@ -277,6 +277,49 @@ export async function inferProjectMetadata(rootDir) {
277
277
  }
278
278
  catch { }
279
279
  }
280
+ // Try Cargo.toml for Rust projects
281
+ const cargoProjectPath = join(rootDir, 'Cargo.toml');
282
+ if (await fileExists(cargoProjectPath)) {
283
+ try {
284
+ const cargoContent = await readFile(cargoProjectPath, 'utf-8');
285
+ const cargo = parsePyprojectToml(cargoContent);
286
+ const pkg = cargo?.package || {};
287
+ if (!name && pkg.name)
288
+ name = pkg.name;
289
+ if (!description && pkg.description)
290
+ description = pkg.description;
291
+ if (!projectVersion && pkg.version)
292
+ projectVersion = pkg.version;
293
+ if (!license && pkg.license)
294
+ license = pkg.license;
295
+ if (!repository && pkg.repository)
296
+ repository = pkg.repository;
297
+ if (!homepage && pkg.homepage)
298
+ homepage = pkg.homepage;
299
+ if (team.length === 0 && Array.isArray(pkg.authors)) {
300
+ for (const author of pkg.authors) {
301
+ if (typeof author === 'string') {
302
+ team.push({ name: author.replace(/<.*>/, '').trim() });
303
+ }
304
+ }
305
+ }
306
+ }
307
+ catch { }
308
+ }
309
+ // Try go.mod for Go projects (module name only)
310
+ const goModProjectPath = join(rootDir, 'go.mod');
311
+ if (await fileExists(goModProjectPath) && !name) {
312
+ try {
313
+ const goModContent = await readFile(goModProjectPath, 'utf-8');
314
+ const moduleMatch = goModContent.match(/^module\s+(\S+)/m);
315
+ if (moduleMatch) {
316
+ // Use the last segment of the module path as name
317
+ const parts = moduleMatch[1].split('/');
318
+ name = parts[parts.length - 1];
319
+ }
320
+ }
321
+ catch { }
322
+ }
280
323
  // Fall back to directory name for name
281
324
  if (!name)
282
325
  name = basename(rootDir);
@@ -893,12 +936,255 @@ export async function inferStack(rootDir) {
893
936
  if (await fileExists(cargoPath)) {
894
937
  stack.language = 'Rust';
895
938
  stack.packageManager = 'cargo';
939
+ try {
940
+ const cargoContent = await readFile(cargoPath, 'utf-8');
941
+ const cargo = parsePyprojectToml(cargoContent); // TOML parser works for Cargo.toml too
942
+ // Rust edition as runtime
943
+ const edition = cargo?.package?.edition;
944
+ if (edition) {
945
+ stack.runtime = `Rust Edition ${edition}`;
946
+ }
947
+ // Collect all deps
948
+ const cargoDeps = cargo?.dependencies || {};
949
+ const cargoDevDeps = cargo?.['dev-dependencies'] || {};
950
+ const cargoBuildDeps = cargo?.['build-dependencies'] || {};
951
+ const allCrateDeps = new Set([
952
+ ...Object.keys(cargoDeps).map((s) => s.toLowerCase()),
953
+ ...Object.keys(cargoDevDeps).map((s) => s.toLowerCase()),
954
+ ...Object.keys(cargoBuildDeps).map((s) => s.toLowerCase()),
955
+ ]);
956
+ // Store deps
957
+ const rustDepsRecord = {};
958
+ for (const [name, spec] of Object.entries(cargoDeps)) {
959
+ rustDepsRecord[name] = typeof spec === 'string' ? spec : '*';
960
+ }
961
+ if (Object.keys(rustDepsRecord).length > 0)
962
+ stack.dependencies = rustDepsRecord;
963
+ const rustDevDepsRecord = {};
964
+ for (const [name, spec] of Object.entries(cargoDevDeps)) {
965
+ rustDevDepsRecord[name] = typeof spec === 'string' ? spec : '*';
966
+ }
967
+ if (Object.keys(rustDevDepsRecord).length > 0)
968
+ stack.devDependencies = rustDevDepsRecord;
969
+ // === FRAMEWORKS ===
970
+ const rustFrameworks = [];
971
+ // Web frameworks
972
+ if (allCrateDeps.has('actix-web'))
973
+ rustFrameworks.push('Actix Web');
974
+ if (allCrateDeps.has('axum'))
975
+ rustFrameworks.push('Axum');
976
+ if (allCrateDeps.has('rocket'))
977
+ rustFrameworks.push('Rocket');
978
+ if (allCrateDeps.has('warp'))
979
+ rustFrameworks.push('Warp');
980
+ if (allCrateDeps.has('tide'))
981
+ rustFrameworks.push('Tide');
982
+ // Async runtime
983
+ if (allCrateDeps.has('tokio'))
984
+ rustFrameworks.push('Tokio');
985
+ if (allCrateDeps.has('async-std'))
986
+ rustFrameworks.push('async-std');
987
+ // CLI
988
+ if (allCrateDeps.has('clap'))
989
+ rustFrameworks.push('Clap');
990
+ if (allCrateDeps.has('structopt'))
991
+ rustFrameworks.push('StructOpt');
992
+ // Serialization
993
+ if (allCrateDeps.has('serde'))
994
+ rustFrameworks.push('Serde');
995
+ // GUI
996
+ if (allCrateDeps.has('tauri'))
997
+ rustFrameworks.push('Tauri');
998
+ if (allCrateDeps.has('egui'))
999
+ rustFrameworks.push('egui');
1000
+ if (allCrateDeps.has('iced'))
1001
+ rustFrameworks.push('Iced');
1002
+ if (rustFrameworks.length > 0) {
1003
+ stack.frameworks = rustFrameworks;
1004
+ stack.framework = rustFrameworks[0];
1005
+ }
1006
+ // === TESTING ===
1007
+ const rustTesting = [];
1008
+ if (allCrateDeps.has('criterion'))
1009
+ rustTesting.push('Criterion (benchmarks)');
1010
+ if (allCrateDeps.has('proptest'))
1011
+ rustTesting.push('proptest');
1012
+ if (allCrateDeps.has('quickcheck'))
1013
+ rustTesting.push('quickcheck');
1014
+ if (allCrateDeps.has('mockall'))
1015
+ rustTesting.push('mockall');
1016
+ if (allCrateDeps.has('rstest'))
1017
+ rustTesting.push('rstest');
1018
+ // Rust has built-in testing
1019
+ rustTesting.unshift('cargo test (built-in)');
1020
+ stack.testingFrameworks = rustTesting;
1021
+ // === ORM/DATABASE ===
1022
+ if (allCrateDeps.has('diesel'))
1023
+ stack.orm = 'Diesel';
1024
+ else if (allCrateDeps.has('sea-orm') || allCrateDeps.has('sea_orm'))
1025
+ stack.orm = 'SeaORM';
1026
+ else if (allCrateDeps.has('sqlx'))
1027
+ stack.orm = 'SQLx';
1028
+ const rustDatabases = [];
1029
+ if (allCrateDeps.has('tokio-postgres') || allCrateDeps.has('sqlx') || allCrateDeps.has('diesel'))
1030
+ rustDatabases.push('PostgreSQL');
1031
+ if (allCrateDeps.has('mongodb'))
1032
+ rustDatabases.push('MongoDB');
1033
+ if (allCrateDeps.has('redis'))
1034
+ rustDatabases.push('Redis');
1035
+ if (allCrateDeps.has('rusqlite'))
1036
+ rustDatabases.push('SQLite');
1037
+ if (rustDatabases.length > 0)
1038
+ stack.database = rustDatabases.join(', ');
1039
+ // === BUILD TOOLS ===
1040
+ const rustBuildTools = [];
1041
+ if (allCrateDeps.has('maturin'))
1042
+ rustBuildTools.push('Maturin');
1043
+ if (allCrateDeps.has('wasm-bindgen'))
1044
+ rustBuildTools.push('wasm-bindgen');
1045
+ if (allCrateDeps.has('napi') || allCrateDeps.has('napi-derive'))
1046
+ rustBuildTools.push('napi-rs');
1047
+ if (rustBuildTools.length > 0)
1048
+ stack.buildTools = rustBuildTools;
1049
+ // === DEPLOYMENT ===
1050
+ if (!stack.deployment) {
1051
+ if (await fileExists(join(rootDir, 'Dockerfile')))
1052
+ stack.deployment = 'Docker';
1053
+ else if (await fileExists(join(rootDir, 'fly.toml')))
1054
+ stack.deployment = 'Fly.io';
1055
+ else if (await fileExists(join(rootDir, 'shuttle.toml')) || allCrateDeps.has('shuttle-runtime'))
1056
+ stack.deployment = 'Shuttle';
1057
+ }
1058
+ // === CI/CD ===
1059
+ if (!stack.cicd || stack.cicd.length === 0) {
1060
+ const cicd = [];
1061
+ if (await fileExists(join(rootDir, '.github/workflows')))
1062
+ cicd.push('GitHub Actions');
1063
+ if (await fileExists(join(rootDir, '.gitlab-ci.yml')))
1064
+ cicd.push('GitLab CI');
1065
+ stack.cicd = cicd;
1066
+ }
1067
+ }
1068
+ catch { }
896
1069
  }
897
1070
  // Check for Go project
898
1071
  const goModPath = join(rootDir, 'go.mod');
899
1072
  if (await fileExists(goModPath)) {
900
1073
  stack.language = 'Go';
901
1074
  stack.packageManager = 'go';
1075
+ try {
1076
+ const goModContent = await readFile(goModPath, 'utf-8');
1077
+ // Parse Go version
1078
+ const goVersionMatch = goModContent.match(/^go\s+(\S+)/m);
1079
+ if (goVersionMatch) {
1080
+ stack.runtime = `Go ${goVersionMatch[1]}`;
1081
+ }
1082
+ // Parse module path
1083
+ const moduleMatch = goModContent.match(/^module\s+(\S+)/m);
1084
+ const modulePath = moduleMatch?.[1] || '';
1085
+ // Parse require block
1086
+ const allGoMods = new Set();
1087
+ const depsRecord = {};
1088
+ // Single-line requires: require github.com/foo/bar v1.2.3
1089
+ const singleReqs = goModContent.matchAll(/^require\s+(\S+)\s+(\S+)/gm);
1090
+ for (const m of singleReqs) {
1091
+ const modName = m[1].toLowerCase();
1092
+ allGoMods.add(modName);
1093
+ depsRecord[m[1]] = m[2];
1094
+ }
1095
+ // Block requires
1096
+ const requireBlocks = goModContent.matchAll(/require\s*\(([\s\S]*?)\)/g);
1097
+ for (const block of requireBlocks) {
1098
+ const lines = block[1].split('\n');
1099
+ for (const line of lines) {
1100
+ const depMatch = line.trim().match(/^(\S+)\s+(\S+)/);
1101
+ if (depMatch && !depMatch[1].startsWith('//')) {
1102
+ allGoMods.add(depMatch[1].toLowerCase());
1103
+ depsRecord[depMatch[1]] = depMatch[2];
1104
+ }
1105
+ }
1106
+ }
1107
+ if (Object.keys(depsRecord).length > 0)
1108
+ stack.dependencies = depsRecord;
1109
+ // Helper: check if any go module path contains a substring
1110
+ const hasGoMod = (name) => [...allGoMods].some(m => m.includes(name));
1111
+ // === FRAMEWORKS ===
1112
+ const goFrameworks = [];
1113
+ if (hasGoMod('gin-gonic/gin'))
1114
+ goFrameworks.push('Gin');
1115
+ if (hasGoMod('labstack/echo'))
1116
+ goFrameworks.push('Echo');
1117
+ if (hasGoMod('gofiber/fiber'))
1118
+ goFrameworks.push('Fiber');
1119
+ if (hasGoMod('go-chi/chi'))
1120
+ goFrameworks.push('Chi');
1121
+ if (hasGoMod('gorilla/mux'))
1122
+ goFrameworks.push('Gorilla Mux');
1123
+ if (hasGoMod('beego'))
1124
+ goFrameworks.push('Beego');
1125
+ if (hasGoMod('grpc'))
1126
+ goFrameworks.push('gRPC');
1127
+ // CLI
1128
+ if (hasGoMod('spf13/cobra'))
1129
+ goFrameworks.push('Cobra');
1130
+ if (hasGoMod('urfave/cli'))
1131
+ goFrameworks.push('urfave/cli');
1132
+ // Config
1133
+ if (hasGoMod('spf13/viper'))
1134
+ goFrameworks.push('Viper');
1135
+ if (goFrameworks.length > 0) {
1136
+ stack.frameworks = goFrameworks;
1137
+ stack.framework = goFrameworks[0];
1138
+ }
1139
+ // === TESTING ===
1140
+ const goTesting = ['go test (built-in)'];
1141
+ if (hasGoMod('stretchr/testify'))
1142
+ goTesting.push('Testify');
1143
+ if (hasGoMod('onsi/ginkgo'))
1144
+ goTesting.push('Ginkgo');
1145
+ if (hasGoMod('onsi/gomega'))
1146
+ goTesting.push('Gomega');
1147
+ stack.testingFrameworks = goTesting;
1148
+ // === ORM/DATABASE ===
1149
+ if (hasGoMod('gorm.io'))
1150
+ stack.orm = 'GORM';
1151
+ else if (hasGoMod('ent/ent'))
1152
+ stack.orm = 'Ent';
1153
+ else if (hasGoMod('sqlc'))
1154
+ stack.orm = 'sqlc';
1155
+ else if (hasGoMod('jmoiron/sqlx'))
1156
+ stack.orm = 'sqlx';
1157
+ const goDatabases = [];
1158
+ if (hasGoMod('lib/pq') || hasGoMod('jackc/pgx') || hasGoMod('pgx'))
1159
+ goDatabases.push('PostgreSQL');
1160
+ if (hasGoMod('go.mongodb.org') || hasGoMod('mongo-driver'))
1161
+ goDatabases.push('MongoDB');
1162
+ if (hasGoMod('go-redis/redis') || hasGoMod('redis/go-redis'))
1163
+ goDatabases.push('Redis');
1164
+ if (hasGoMod('mattn/go-sqlite3'))
1165
+ goDatabases.push('SQLite');
1166
+ if (hasGoMod('go-sql-driver/mysql'))
1167
+ goDatabases.push('MySQL');
1168
+ if (goDatabases.length > 0)
1169
+ stack.database = goDatabases.join(', ');
1170
+ // === DEPLOYMENT ===
1171
+ if (!stack.deployment) {
1172
+ if (await fileExists(join(rootDir, 'Dockerfile')))
1173
+ stack.deployment = 'Docker';
1174
+ else if (await fileExists(join(rootDir, 'fly.toml')))
1175
+ stack.deployment = 'Fly.io';
1176
+ }
1177
+ // === CI/CD ===
1178
+ if (!stack.cicd || stack.cicd.length === 0) {
1179
+ const cicd = [];
1180
+ if (await fileExists(join(rootDir, '.github/workflows')))
1181
+ cicd.push('GitHub Actions');
1182
+ if (await fileExists(join(rootDir, '.gitlab-ci.yml')))
1183
+ cicd.push('GitLab CI');
1184
+ stack.cicd = cicd;
1185
+ }
1186
+ }
1187
+ catch { }
902
1188
  }
903
1189
  return stack;
904
1190
  }
@@ -1007,29 +1293,107 @@ export async function inferArchitecture(rootDir) {
1007
1293
  architecture.entryPoints = architecture.entryPoints || [];
1008
1294
  architecture.entryPoints.push({ file: 'manage.py', purpose: 'Django management' });
1009
1295
  }
1010
- if (hasPackages || hasApps) {
1011
- architecture.type = 'monorepo';
1012
- }
1013
- else if (hasServices) {
1014
- architecture.type = 'microservices';
1015
- }
1016
- else if (hasPythonScripts && !hasPythonWebFramework) {
1017
- architecture.type = 'cli';
1018
- }
1019
- else if (hasPythonWebFramework) {
1020
- architecture.type = 'backend';
1021
- }
1022
- else if (hasApp && hasSrc) {
1023
- architecture.type = 'fullstack';
1024
- }
1025
- else if (hasLib && !hasApp) {
1026
- architecture.type = 'library';
1296
+ // Rust architecture detection
1297
+ let hasRustBin = false;
1298
+ let hasRustLib = false;
1299
+ let hasRustWebFramework = false;
1300
+ let hasRustCliFramework = false;
1301
+ const cargoArchPath = join(rootDir, 'Cargo.toml');
1302
+ if (await fileExists(cargoArchPath)) {
1303
+ try {
1304
+ const cargoContent = await readFile(cargoArchPath, 'utf-8');
1305
+ const cargo = parsePyprojectToml(cargoContent);
1306
+ hasRustBin = await fileExists(join(rootDir, 'src/main.rs'));
1307
+ hasRustLib = await fileExists(join(rootDir, 'src/lib.rs'));
1308
+ const cargoDeps = Object.keys(cargo?.dependencies || {}).map(s => s.toLowerCase());
1309
+ hasRustWebFramework = cargoDeps.some(d => ['actix-web', 'axum', 'rocket', 'warp', 'tide'].includes(d));
1310
+ hasRustCliFramework = cargoDeps.some(d => ['clap', 'structopt'].includes(d));
1311
+ // Rust entry points
1312
+ if (hasRustBin) {
1313
+ architecture.entryPoints = architecture.entryPoints || [];
1314
+ architecture.entryPoints.push({ file: 'src/main.rs', purpose: 'Binary entry point' });
1315
+ }
1316
+ if (hasRustLib) {
1317
+ architecture.entryPoints = architecture.entryPoints || [];
1318
+ architecture.entryPoints.push({ file: 'src/lib.rs', purpose: 'Library entry point' });
1319
+ }
1320
+ }
1321
+ catch { }
1027
1322
  }
1028
- else if (await fileExists(join(rootDir, 'bin'))) {
1029
- architecture.type = 'cli';
1323
+ // Go architecture detection
1324
+ let hasGoCmd = false;
1325
+ let hasGoWebFramework = false;
1326
+ let hasGoCliFramework = false;
1327
+ const goModArchPath = join(rootDir, 'go.mod');
1328
+ if (await fileExists(goModArchPath)) {
1329
+ try {
1330
+ const goModContent = await readFile(goModArchPath, 'utf-8');
1331
+ hasGoCmd = relativeDirs.some(d => d === 'cmd' || d.startsWith('cmd/'));
1332
+ const goMainExists = await fileExists(join(rootDir, 'main.go'));
1333
+ const goModLower = goModContent.toLowerCase();
1334
+ hasGoWebFramework = ['gin-gonic', 'labstack/echo', 'gofiber/fiber', 'go-chi/chi', 'gorilla/mux'].some(f => goModLower.includes(f));
1335
+ hasGoCliFramework = ['spf13/cobra', 'urfave/cli'].some(f => goModLower.includes(f));
1336
+ // Go entry points
1337
+ if (hasGoCmd) {
1338
+ architecture.entryPoints = architecture.entryPoints || [];
1339
+ architecture.entryPoints.push({ file: 'cmd/', purpose: 'CLI entry points' });
1340
+ }
1341
+ else if (goMainExists) {
1342
+ architecture.entryPoints = architecture.entryPoints || [];
1343
+ architecture.entryPoints.push({ file: 'main.go', purpose: 'Application entry' });
1344
+ }
1345
+ }
1346
+ catch { }
1030
1347
  }
1031
- else if (hasPages || hasApp) {
1032
- architecture.type = 'frontend';
1348
+ // === Score-based architecture type detection ===
1349
+ // Each signal contributes to a score for each type
1350
+ const typeScores = {
1351
+ monorepo: 0, microservices: 0, cli: 0, backend: 0,
1352
+ fullstack: 0, library: 0, frontend: 0
1353
+ };
1354
+ // Monorepo signals
1355
+ if (hasPackages || hasApps)
1356
+ typeScores.monorepo += 10;
1357
+ // Microservices signals
1358
+ if (hasServices)
1359
+ typeScores.microservices += 8;
1360
+ // CLI signals
1361
+ if (hasPythonScripts && !hasPythonWebFramework)
1362
+ typeScores.cli += 8;
1363
+ if (hasRustCliFramework && hasRustBin)
1364
+ typeScores.cli += 8;
1365
+ if (hasGoCliFramework || hasGoCmd)
1366
+ typeScores.cli += 8;
1367
+ if (await fileExists(join(rootDir, 'bin')))
1368
+ typeScores.cli += 5;
1369
+ // Backend signals
1370
+ if (hasPythonWebFramework)
1371
+ typeScores.backend += 8;
1372
+ if (hasRustWebFramework)
1373
+ typeScores.backend += 8;
1374
+ if (hasGoWebFramework)
1375
+ typeScores.backend += 8;
1376
+ if (relativeDirs.some(d => d.includes('api')))
1377
+ typeScores.backend += 2;
1378
+ // Fullstack signals
1379
+ if (hasApp && hasSrc)
1380
+ typeScores.fullstack += 5;
1381
+ if (hasPythonWebFramework && relativeDirs.some(d => d.includes('dashboard') || d.includes('frontend')))
1382
+ typeScores.fullstack += 7;
1383
+ // Library signals
1384
+ if (hasLib && !hasApp && !hasPages)
1385
+ typeScores.library += 5;
1386
+ if (hasRustLib && !hasRustBin)
1387
+ typeScores.library += 8;
1388
+ // Frontend signals
1389
+ if (hasPages || (hasApp && !hasSrc && !hasServices))
1390
+ typeScores.frontend += 5;
1391
+ // Pick highest score, with fallback
1392
+ const sortedTypes = Object.entries(typeScores)
1393
+ .filter(([, score]) => score > 0)
1394
+ .sort((a, b) => b[1] - a[1]);
1395
+ if (sortedTypes.length > 0) {
1396
+ architecture.type = sortedTypes[0][0];
1033
1397
  }
1034
1398
  else {
1035
1399
  architecture.type = 'backend';
@@ -1153,6 +1517,24 @@ export async function inferArchitecture(rootDir) {
1153
1517
  if (await fileExists(join(rootDir, 'mypy.ini')) || await fileExists(join(rootDir, '.mypy.ini'))) {
1154
1518
  conventions.push('mypy type checking');
1155
1519
  }
1520
+ // Rust conventions
1521
+ if (await fileExists(cargoArchPath)) {
1522
+ if (await fileExists(join(rootDir, 'rustfmt.toml')) || await fileExists(join(rootDir, '.rustfmt.toml'))) {
1523
+ conventions.push('rustfmt code formatting');
1524
+ }
1525
+ if (await fileExists(join(rootDir, 'clippy.toml')) || await fileExists(join(rootDir, '.clippy.toml'))) {
1526
+ conventions.push('Clippy linting');
1527
+ }
1528
+ // Rust always has these by convention
1529
+ conventions.push('cargo fmt / cargo clippy');
1530
+ }
1531
+ // Go conventions
1532
+ if (await fileExists(goModArchPath)) {
1533
+ if (await fileExists(join(rootDir, '.golangci.yml')) || await fileExists(join(rootDir, '.golangci.yaml'))) {
1534
+ conventions.push('golangci-lint');
1535
+ }
1536
+ conventions.push('gofmt / go vet');
1537
+ }
1156
1538
  architecture.conventions = conventions;
1157
1539
  // Detect entry points (preserve any already detected, e.g. from pyproject.toml)
1158
1540
  const entryPoints = architecture.entryPoints || [];