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.
- package/README.md +63 -4
- package/dist/bin/prelude.js +2 -0
- package/dist/bin/prelude.js.map +1 -1
- package/dist/src/commands/export.d.ts.map +1 -1
- package/dist/src/commands/export.js +5 -3
- package/dist/src/commands/export.js.map +1 -1
- package/dist/src/commands/init.d.ts.map +1 -1
- package/dist/src/commands/init.js +151 -0
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/validate.d.ts +3 -0
- package/dist/src/commands/validate.d.ts.map +1 -0
- package/dist/src/commands/validate.js +203 -0
- package/dist/src/commands/validate.js.map +1 -0
- package/dist/src/constants.d.ts +1 -1
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +4 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/core/claude-md-parser.d.ts +33 -0
- package/dist/src/core/claude-md-parser.d.ts.map +1 -0
- package/dist/src/core/claude-md-parser.js +410 -0
- package/dist/src/core/claude-md-parser.js.map +1 -0
- package/dist/src/core/exporter.d.ts +2 -1
- package/dist/src/core/exporter.d.ts.map +1 -1
- package/dist/src/core/exporter.js +159 -1
- package/dist/src/core/exporter.js.map +1 -1
- package/dist/src/core/infer.d.ts.map +1 -1
- package/dist/src/core/infer.js +403 -21
- package/dist/src/core/infer.js.map +1 -1
- package/dist/src/core/source-scanner.d.ts.map +1 -1
- package/dist/src/core/source-scanner.js +58 -1
- package/dist/src/core/source-scanner.js.map +1 -1
- package/dist/src/utils/fs.d.ts.map +1 -1
- package/dist/src/utils/fs.js +2 -1
- package/dist/src/utils/fs.js.map +1 -1
- package/package.json +1 -1
package/dist/src/core/infer.js
CHANGED
|
@@ -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
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
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
|
-
|
|
1029
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
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 || [];
|