claudeos-core 1.2.4 → 1.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.
Potentially problematic release.
This version of claudeos-core might be problematic. Click here for more details.
- package/CHANGELOG.md +76 -0
- package/README.de.md +50 -10
- package/README.es.md +51 -10
- package/README.fr.md +51 -10
- package/README.hi.md +51 -10
- package/README.ja.md +52 -10
- package/README.ko.md +51 -10
- package/README.md +62 -15
- package/README.ru.md +51 -10
- package/README.vi.md +51 -10
- package/README.zh-CN.md +51 -10
- package/bin/cli.js +171 -36
- package/bootstrap.sh +71 -23
- package/content-validator/index.js +16 -13
- package/health-checker/index.js +4 -3
- package/lib/safe-fs.js +110 -0
- package/manifest-generator/index.js +13 -7
- package/package.json +4 -2
- package/pass-json-validator/index.js +3 -5
- package/pass-prompts/templates/java-spring/pass1.md +4 -1
- package/pass-prompts/templates/java-spring/pass2.md +3 -3
- package/pass-prompts/templates/java-spring/pass3.md +42 -5
- package/pass-prompts/templates/kotlin-spring/pass1.md +4 -1
- package/pass-prompts/templates/kotlin-spring/pass2.md +5 -5
- package/pass-prompts/templates/kotlin-spring/pass3.md +42 -5
- package/pass-prompts/templates/node-express/pass1.md +4 -1
- package/pass-prompts/templates/node-express/pass2.md +4 -1
- package/pass-prompts/templates/node-express/pass3.md +44 -6
- package/pass-prompts/templates/node-nextjs/pass1.md +14 -4
- package/pass-prompts/templates/node-nextjs/pass2.md +6 -4
- package/pass-prompts/templates/node-nextjs/pass3.md +45 -6
- package/pass-prompts/templates/python-django/pass1.md +4 -2
- package/pass-prompts/templates/python-django/pass2.md +4 -4
- package/pass-prompts/templates/python-django/pass3.md +42 -5
- package/pass-prompts/templates/python-fastapi/pass1.md +4 -1
- package/pass-prompts/templates/python-fastapi/pass2.md +4 -4
- package/pass-prompts/templates/python-fastapi/pass3.md +42 -5
- package/plan-installer/domain-grouper.js +74 -0
- package/plan-installer/index.js +35 -1305
- package/plan-installer/prompt-generator.js +94 -0
- package/plan-installer/stack-detector.js +326 -0
- package/plan-installer/structure-scanner.js +783 -0
- package/plan-validator/index.js +84 -20
- package/sync-checker/index.js +7 -3
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — Prompt Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates dynamic prompts for Pass 1/2/3 based on detected stack and language.
|
|
5
|
+
* Supports multi-stack combined prompts (backend + frontend merged in Pass 3).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const path = require("path");
|
|
9
|
+
const { readFileSafe, readJsonSafe, existsSafe, writeFileSafe } = require("../lib/safe-fs");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate pass prompts from templates.
|
|
13
|
+
* @param {object} templates - { backend: string|null, frontend: string|null }
|
|
14
|
+
* @param {string} lang - output language code (e.g. "ko", "en")
|
|
15
|
+
* @param {string} templatesDir - path to pass-prompts/templates/
|
|
16
|
+
* @param {string} generatedDir - path to claudeos-core/generated/
|
|
17
|
+
*/
|
|
18
|
+
function generatePrompts(templates, lang, templatesDir, generatedDir) {
|
|
19
|
+
const commonDir = path.join(templatesDir, "common");
|
|
20
|
+
const headerPath = path.join(commonDir, "header.md");
|
|
21
|
+
const footerPath = path.join(commonDir, "pass3-footer.md");
|
|
22
|
+
const langPath = path.join(commonDir, "lang-instructions.json");
|
|
23
|
+
|
|
24
|
+
const header = existsSafe(headerPath) ? readFileSafe(headerPath) : "";
|
|
25
|
+
const footer = existsSafe(footerPath) ? readFileSafe(footerPath) : "";
|
|
26
|
+
|
|
27
|
+
let langInstruction = "";
|
|
28
|
+
if (lang && lang !== "en" && existsSafe(langPath)) {
|
|
29
|
+
const langData = readJsonSafe(langPath);
|
|
30
|
+
if (langData && langData.instructions && langData.instructions[lang]) {
|
|
31
|
+
langInstruction = langData.instructions[lang];
|
|
32
|
+
console.log(` 🌐 Language: ${langData.labels[lang]} (Pass 3 output)`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readTemplate(templateName, passName) {
|
|
37
|
+
const src = path.join(templatesDir, templateName, `${passName}.md`);
|
|
38
|
+
if (!existsSafe(src)) return null;
|
|
39
|
+
let body = readFileSafe(src);
|
|
40
|
+
body = body.replace(/^Project root path:.*\nInterpret all file paths.*\n\n?---\n\n?/s, "");
|
|
41
|
+
body = body.replace(/\nAfter completion, run the following commands in order:[\s\S]*$/, "");
|
|
42
|
+
return body;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const activeTemplates = [templates.backend, templates.frontend].filter(Boolean);
|
|
46
|
+
const primaryTemplate = templates.backend || templates.frontend;
|
|
47
|
+
|
|
48
|
+
for (let ti = 0; ti < activeTemplates.length; ti++) {
|
|
49
|
+
const tmpl = activeTemplates[ti];
|
|
50
|
+
const type = (tmpl === templates.frontend && tmpl !== templates.backend) ? "frontend"
|
|
51
|
+
: (ti === 1 && templates.frontend) ? "frontend" : "backend";
|
|
52
|
+
const body = readTemplate(tmpl, "pass1");
|
|
53
|
+
if (body) {
|
|
54
|
+
writeFileSafe(path.join(generatedDir, `pass1-${type}-prompt.md`), header + body);
|
|
55
|
+
console.log(` ✅ pass1-${type}-prompt.md (${tmpl})`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (primaryTemplate) {
|
|
60
|
+
const body = readTemplate(primaryTemplate, "pass2");
|
|
61
|
+
if (body) {
|
|
62
|
+
writeFileSafe(path.join(generatedDir, "pass2-prompt.md"), header + body);
|
|
63
|
+
console.log(` ✅ pass2-prompt.md (${primaryTemplate})`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (primaryTemplate) {
|
|
68
|
+
const primaryBody = readTemplate(primaryTemplate, "pass3");
|
|
69
|
+
let combinedBody = primaryBody || "";
|
|
70
|
+
|
|
71
|
+
if (templates.backend && templates.frontend && templates.backend !== templates.frontend) {
|
|
72
|
+
const frontendBody = readTemplate(templates.frontend, "pass3");
|
|
73
|
+
if (frontendBody) {
|
|
74
|
+
combinedBody += "\n\n---\n\n";
|
|
75
|
+
combinedBody += "# Additional: Frontend generation targets (auto-detected)\n\n";
|
|
76
|
+
combinedBody += "In addition to the backend standards above, also generate the following frontend standards.\n";
|
|
77
|
+
combinedBody += "Reference the frontend analysis results in pass2-merged.json.\n\n";
|
|
78
|
+
const frontendSections = frontendBody
|
|
79
|
+
.split(/\n(?=\d+\.\s)/)
|
|
80
|
+
.filter(s => /frontend|component|page|routing|data[.\-]fetch|state|styling/i.test(s))
|
|
81
|
+
.join("\n");
|
|
82
|
+
if (frontendSections.trim()) combinedBody += frontendSections;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
writeFileSafe(
|
|
87
|
+
path.join(generatedDir, "pass3-prompt.md"),
|
|
88
|
+
header + langInstruction + combinedBody.trimEnd() + "\n" + footer
|
|
89
|
+
);
|
|
90
|
+
console.log(` ✅ pass3-prompt.md${templates.frontend && templates.backend ? " (multi-stack combined)" : ""}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = { generatePrompts };
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaudeOS-Core — Stack Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects project language, framework, build tool, database, ORM, and frontend.
|
|
5
|
+
* Supports: Java, Kotlin, TypeScript/JavaScript, Python
|
|
6
|
+
* Multi-stack aware (backend + frontend simultaneous detection).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const { glob } = require("glob");
|
|
11
|
+
const { readFileSafe, readJsonSafe, existsSafe } = require("../lib/safe-fs");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect the project's technology stack.
|
|
15
|
+
* @param {string} ROOT - project root path
|
|
16
|
+
* @returns {Promise<object>} stack info
|
|
17
|
+
*/
|
|
18
|
+
async function detectStack(ROOT) {
|
|
19
|
+
const stack = {
|
|
20
|
+
language: null, languageVersion: null,
|
|
21
|
+
framework: null, frameworkVersion: null,
|
|
22
|
+
buildTool: null, database: null, orm: null,
|
|
23
|
+
frontend: null, frontendVersion: null,
|
|
24
|
+
packageManager: null, detected: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// ── Java/Kotlin: Gradle ──
|
|
28
|
+
const gradleFile = existsSafe(path.join(ROOT, "build.gradle.kts"))
|
|
29
|
+
? "build.gradle.kts"
|
|
30
|
+
: existsSafe(path.join(ROOT, "build.gradle")) ? "build.gradle" : null;
|
|
31
|
+
if (gradleFile) {
|
|
32
|
+
const g = readFileSafe(path.join(ROOT, gradleFile));
|
|
33
|
+
if (g) {
|
|
34
|
+
stack.buildTool = "gradle"; stack.detected.push(gradleFile);
|
|
35
|
+
if (g.includes("spring-boot")) { stack.language = "java"; stack.framework = "spring-boot"; stack.detected.push("spring-boot"); }
|
|
36
|
+
const svPatterns = [
|
|
37
|
+
/org\.springframework\.boot.*version\s*['"]([^'"]+)['"]/,
|
|
38
|
+
/id\s*\(\s*["']org\.springframework\.boot["']\s*\)\s*version\s*["']([^"']+)["']/,
|
|
39
|
+
/spring-boot-dependencies:([^'")\s]+)/,
|
|
40
|
+
];
|
|
41
|
+
for (const pattern of svPatterns) {
|
|
42
|
+
const sv = g.match(pattern);
|
|
43
|
+
if (sv) { stack.frameworkVersion = sv[1]; break; }
|
|
44
|
+
}
|
|
45
|
+
const jv = g.match(/sourceCompatibility\s*=\s*['"]?(\d+)['"]?/);
|
|
46
|
+
if (jv) stack.languageVersion = jv[1];
|
|
47
|
+
// ORM detection (first match wins — order by specificity)
|
|
48
|
+
if (!stack.orm && g.includes("mybatis")) { stack.orm = "mybatis"; stack.detected.push("mybatis"); }
|
|
49
|
+
if (!stack.orm && (g.includes("jpa") || g.includes("hibernate"))) { stack.orm = "jpa"; stack.detected.push("jpa"); }
|
|
50
|
+
if (!stack.orm && g.includes("exposed")) { stack.orm = "exposed"; stack.detected.push("exposed"); }
|
|
51
|
+
if (!stack.orm && g.includes("jooq")) { stack.orm = "jooq"; stack.detected.push("jooq"); }
|
|
52
|
+
if (!stack.orm && g.includes("spring-data-jdbc")) { stack.orm = "spring-data-jdbc"; stack.detected.push("spring-data-jdbc"); }
|
|
53
|
+
if (!stack.orm && g.includes("r2dbc")) { stack.orm = "r2dbc"; stack.detected.push("r2dbc"); }
|
|
54
|
+
// DB detection
|
|
55
|
+
if (!stack.database && g.includes("postgresql")) { stack.database = "postgresql"; stack.detected.push("postgresql"); }
|
|
56
|
+
if (!stack.database && g.includes("mysql")) { stack.database = "mysql"; stack.detected.push("mysql"); }
|
|
57
|
+
if (!stack.database && g.includes("oracle")) { stack.database = "oracle"; stack.detected.push("oracle"); }
|
|
58
|
+
if (!stack.database && g.includes("mongodb")) { stack.database = "mongodb"; stack.detected.push("mongodb"); }
|
|
59
|
+
if (!stack.database && /\bh2\b/.test(g)) { stack.database = "h2"; stack.detected.push("h2"); }
|
|
60
|
+
// Kotlin detection: override language if Kotlin plugin found
|
|
61
|
+
if (g.includes("kotlin") || g.includes("org.jetbrains.kotlin")) {
|
|
62
|
+
stack.language = "kotlin"; stack.detected.push("kotlin");
|
|
63
|
+
const kvPatterns = [
|
|
64
|
+
/kotlin\S*\s*version\s*['"]([^'"]+)['"]/,
|
|
65
|
+
/org\.jetbrains\.kotlin\S*\s*version\s*['"]([^'"]+)['"]/,
|
|
66
|
+
/kotlin\("jvm"\)\s*version\s*["']([^"']+)["']/,
|
|
67
|
+
];
|
|
68
|
+
for (const pattern of kvPatterns) {
|
|
69
|
+
const match = g.match(pattern);
|
|
70
|
+
if (match) { stack.languageVersion = match[1]; break; }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Gradle: version catalogs (libs.versions.toml) ──
|
|
77
|
+
const versionCatalog = path.join(ROOT, "gradle/libs.versions.toml");
|
|
78
|
+
if (existsSafe(versionCatalog)) {
|
|
79
|
+
const vc = readFileSafe(versionCatalog);
|
|
80
|
+
if (vc) {
|
|
81
|
+
stack.detected.push("libs.versions.toml");
|
|
82
|
+
if (!stack.languageVersion) {
|
|
83
|
+
const kvMatch = vc.match(/kotlin\s*=\s*["']([^"']+)["']/);
|
|
84
|
+
if (kvMatch) stack.languageVersion = kvMatch[1];
|
|
85
|
+
}
|
|
86
|
+
if (!stack.frameworkVersion) {
|
|
87
|
+
const sbMatch = vc.match(/spring-boot\s*=\s*["']([^"']+)["']/);
|
|
88
|
+
if (sbMatch) stack.frameworkVersion = sbMatch[1];
|
|
89
|
+
}
|
|
90
|
+
if (!stack.orm && vc.includes("exposed")) { stack.orm = "exposed"; stack.detected.push("exposed (catalog)"); }
|
|
91
|
+
if (!stack.orm && vc.includes("jooq")) { stack.orm = "jooq"; stack.detected.push("jooq (catalog)"); }
|
|
92
|
+
if (!stack.orm && (vc.includes("jpa") || vc.includes("hibernate"))) { stack.orm = "jpa"; stack.detected.push("jpa (catalog)"); }
|
|
93
|
+
if (!stack.database && vc.includes("postgresql")) { stack.database = "postgresql"; }
|
|
94
|
+
if (!stack.database && vc.includes("mysql")) { stack.database = "mysql"; }
|
|
95
|
+
if (!stack.database && vc.includes("mongodb")) { stack.database = "mongodb"; }
|
|
96
|
+
if (!stack.language && vc.includes("kotlin")) {
|
|
97
|
+
stack.language = "kotlin"; stack.detected.push("kotlin (catalog)");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Kotlin: multi-module Gradle detection ──
|
|
103
|
+
if (stack.language !== "kotlin" && stack.buildTool === "gradle") {
|
|
104
|
+
const subBuildFiles = await glob("**/build.gradle{,.kts}", { cwd: ROOT, ignore: ["**/node_modules/**", "**/build/**"] });
|
|
105
|
+
for (const sbf of subBuildFiles.slice(0, 5)) {
|
|
106
|
+
const sc = readFileSafe(path.join(ROOT, sbf));
|
|
107
|
+
if (sc && (sc.includes("kotlin") || sc.includes("org.jetbrains.kotlin"))) {
|
|
108
|
+
stack.language = "kotlin"; stack.detected.push("kotlin (submodule)");
|
|
109
|
+
const kv = sc.match(/kotlin\S*\s*version\s*['"]([^'"]+)['"]/);
|
|
110
|
+
if (kv) stack.languageVersion = kv[1];
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Kotlin: detect CQRS/multi-module from settings.gradle ──
|
|
117
|
+
const settingsFile = existsSafe(path.join(ROOT, "settings.gradle.kts"))
|
|
118
|
+
? "settings.gradle.kts"
|
|
119
|
+
: existsSafe(path.join(ROOT, "settings.gradle")) ? "settings.gradle" : null;
|
|
120
|
+
if (settingsFile && stack.language === "kotlin") {
|
|
121
|
+
const sg = readFileSafe(path.join(ROOT, settingsFile));
|
|
122
|
+
if (sg) {
|
|
123
|
+
const sgClean = sg.split("\n").filter(l => !l.trimStart().startsWith("//")).join("\n");
|
|
124
|
+
const includes = [];
|
|
125
|
+
const includeBlocks = [...sgClean.matchAll(/include\s*\(([^)]*)\)/gs)];
|
|
126
|
+
for (const block of includeBlocks) {
|
|
127
|
+
const quotedValues = [...block[1].matchAll(/["']([^"']+)["']/g)].map(m => m[1]);
|
|
128
|
+
includes.push(...quotedValues);
|
|
129
|
+
}
|
|
130
|
+
if (includes.length === 0) {
|
|
131
|
+
const groovyIncludes = [...sgClean.matchAll(/include\s+(.+)/g)];
|
|
132
|
+
for (const line of groovyIncludes) {
|
|
133
|
+
const quotedValues = [...line[1].matchAll(/['"]([^'"]+)['"]/g)].map(m => m[1]);
|
|
134
|
+
includes.push(...quotedValues);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const cleanModules = includes.map(m => m.replace(/^:/, ""));
|
|
138
|
+
if (cleanModules.length > 0) {
|
|
139
|
+
stack.multiModule = true;
|
|
140
|
+
stack.modules = cleanModules;
|
|
141
|
+
stack.detected.push(`multi-module (${cleanModules.length} modules)`);
|
|
142
|
+
const hasCommand = cleanModules.some(m => m.includes("command"));
|
|
143
|
+
const hasQuery = cleanModules.some(m => m.includes("query"));
|
|
144
|
+
const hasBff = cleanModules.some(m => m.includes("bff"));
|
|
145
|
+
if (hasCommand && hasQuery) { stack.architecture = "cqrs"; stack.detected.push("cqrs"); }
|
|
146
|
+
if (hasBff) { stack.detected.push("bff"); }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Java: Maven ──
|
|
152
|
+
if (existsSafe(path.join(ROOT, "pom.xml"))) {
|
|
153
|
+
const pom = readFileSafe(path.join(ROOT, "pom.xml"));
|
|
154
|
+
if (pom) {
|
|
155
|
+
if (!stack.buildTool) { stack.buildTool = "maven"; stack.language = "java"; stack.detected.push("pom.xml"); }
|
|
156
|
+
const sv = pom.match(/<spring-boot[^>]*version>([^<]+)/);
|
|
157
|
+
if (sv) stack.frameworkVersion = sv[1];
|
|
158
|
+
const jv = pom.match(/<java\.version>(\d+)/);
|
|
159
|
+
if (jv) stack.languageVersion = jv[1];
|
|
160
|
+
if (pom.includes("spring-boot") && !stack.framework) { stack.framework = "spring-boot"; stack.detected.push("spring-boot"); }
|
|
161
|
+
if (!stack.orm && pom.includes("mybatis")) { stack.orm = "mybatis"; stack.detected.push("mybatis"); }
|
|
162
|
+
if (!stack.orm && (pom.includes("jpa") || pom.includes("hibernate"))) { stack.orm = "jpa"; stack.detected.push("jpa"); }
|
|
163
|
+
if (!stack.orm && pom.includes("exposed")) { stack.orm = "exposed"; stack.detected.push("exposed"); }
|
|
164
|
+
if (!stack.orm && pom.includes("jooq")) { stack.orm = "jooq"; stack.detected.push("jooq"); }
|
|
165
|
+
if (!stack.database && pom.includes("postgresql")) stack.database = "postgresql";
|
|
166
|
+
if (!stack.database && pom.includes("mysql")) stack.database = "mysql";
|
|
167
|
+
if (!stack.database && pom.includes("oracle")) stack.database = "oracle";
|
|
168
|
+
if (!stack.database && pom.includes("mongodb")) stack.database = "mongodb";
|
|
169
|
+
if (!stack.database && /\bh2\b/.test(pom)) stack.database = "h2";
|
|
170
|
+
if (!stack.database && pom.includes("sqlite")) stack.database = "sqlite";
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Node.js ──
|
|
175
|
+
if (existsSafe(path.join(ROOT, "package.json"))) {
|
|
176
|
+
const pkg = readJsonSafe(path.join(ROOT, "package.json"));
|
|
177
|
+
if (pkg) {
|
|
178
|
+
stack.detected.push("package.json");
|
|
179
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
180
|
+
|
|
181
|
+
if (!stack.language) stack.language = deps.typescript ? "typescript" : "javascript";
|
|
182
|
+
if (deps.typescript) { stack.detected.push("typescript"); const tv = deps.typescript.match(/(\d+(?:\.\d+)*)/); if (tv) stack.languageVersion = tv[1]; }
|
|
183
|
+
|
|
184
|
+
// Frontend
|
|
185
|
+
if (deps.next) { stack.frontend = "nextjs"; stack.detected.push("next.js"); stack.frontendVersion = deps.next.replace(/[^0-9.]/g, ""); }
|
|
186
|
+
else if (deps.react) { stack.frontend = "react"; stack.detected.push("react"); stack.frontendVersion = deps.react.replace(/[^0-9.]/g, ""); }
|
|
187
|
+
else if (deps.vue) { stack.frontend = "vue"; stack.detected.push("vue"); stack.frontendVersion = deps.vue.replace(/[^0-9.]/g, ""); }
|
|
188
|
+
|
|
189
|
+
// Backend framework
|
|
190
|
+
if (deps.express && !stack.framework) { stack.framework = "express"; stack.detected.push("express"); }
|
|
191
|
+
if (deps["@nestjs/core"]) { stack.framework = "nestjs"; stack.detected.push("nestjs"); stack.frameworkVersion = deps["@nestjs/core"].replace(/[^0-9.]/g, ""); }
|
|
192
|
+
|
|
193
|
+
// ORM
|
|
194
|
+
if ((deps["@prisma/client"] || deps.prisma) && !stack.orm) { stack.orm = "prisma"; stack.detected.push("prisma"); }
|
|
195
|
+
if (deps.typeorm && !stack.orm) { stack.orm = "typeorm"; stack.detected.push("typeorm"); }
|
|
196
|
+
if (deps.sequelize && !stack.orm) { stack.orm = "sequelize"; stack.detected.push("sequelize"); }
|
|
197
|
+
if (deps["drizzle-orm"] && !stack.orm) { stack.orm = "drizzle"; stack.detected.push("drizzle"); }
|
|
198
|
+
if (deps.knex && !stack.orm) { stack.orm = "knex"; stack.detected.push("knex"); }
|
|
199
|
+
if (deps.mongoose) { if (!stack.database) stack.database = "mongodb"; if (!stack.orm) stack.orm = "mongoose"; stack.detected.push("mongoose"); }
|
|
200
|
+
|
|
201
|
+
// DB
|
|
202
|
+
if (deps.pg && !stack.database) stack.database = "postgresql";
|
|
203
|
+
if (deps.mysql2 && !stack.database) stack.database = "mysql";
|
|
204
|
+
if (deps.mongodb && !stack.database) stack.database = "mongodb";
|
|
205
|
+
|
|
206
|
+
// Package manager
|
|
207
|
+
stack.packageManager = existsSafe(path.join(ROOT, "pnpm-lock.yaml")) ? "pnpm"
|
|
208
|
+
: existsSafe(path.join(ROOT, "yarn.lock")) ? "yarn" : "npm";
|
|
209
|
+
|
|
210
|
+
if (pkg.engines && pkg.engines.node && !stack.languageVersion) {
|
|
211
|
+
const nv = pkg.engines.node.match(/(\d+(?:\.\d+)*)/);
|
|
212
|
+
if (nv) stack.languageVersion = nv[1];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Python ──
|
|
218
|
+
const hasPyproject = existsSafe(path.join(ROOT, "pyproject.toml"));
|
|
219
|
+
const hasRequirements = existsSafe(path.join(ROOT, "requirements.txt"));
|
|
220
|
+
if (hasPyproject || hasRequirements) {
|
|
221
|
+
if (!stack.language) stack.language = "python";
|
|
222
|
+
stack.detected.push("python");
|
|
223
|
+
|
|
224
|
+
if (hasPyproject) {
|
|
225
|
+
const pp = readFileSafe(path.join(ROOT, "pyproject.toml"));
|
|
226
|
+
if (pp) {
|
|
227
|
+
const pv = pp.match(/python\s*=\s*"[><=^~]*(\d+\.\d+)/);
|
|
228
|
+
if (pv && !stack.languageVersion) stack.languageVersion = pv[1];
|
|
229
|
+
if (pp.includes("django") && !stack.framework) { stack.framework = "django"; stack.detected.push("django"); }
|
|
230
|
+
if (pp.includes("fastapi") && !stack.framework) { stack.framework = "fastapi"; stack.detected.push("fastapi"); }
|
|
231
|
+
if (pp.includes("flask") && !stack.framework) { stack.framework = "flask"; stack.detected.push("flask"); }
|
|
232
|
+
if (pp.includes("sqlalchemy") && !stack.orm) { stack.orm = "sqlalchemy"; stack.detected.push("sqlalchemy"); }
|
|
233
|
+
if (pp.includes("tortoise") && !stack.orm) { stack.orm = "tortoise-orm"; stack.detected.push("tortoise-orm"); }
|
|
234
|
+
if (pp.includes("poetry")) { stack.packageManager = "poetry"; }
|
|
235
|
+
if (pp.includes("pdm")) { stack.packageManager = "pdm"; }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (hasRequirements) {
|
|
240
|
+
const r = readFileSafe(path.join(ROOT, "requirements.txt"));
|
|
241
|
+
if (r) {
|
|
242
|
+
if (r.includes("django") && !stack.framework) { stack.framework = "django"; stack.detected.push("django"); }
|
|
243
|
+
if (r.includes("fastapi") && !stack.framework) { stack.framework = "fastapi"; stack.detected.push("fastapi"); }
|
|
244
|
+
if (r.includes("flask") && !stack.framework) { stack.framework = "flask"; stack.detected.push("flask"); }
|
|
245
|
+
if (r.includes("sqlalchemy") && !stack.orm) { stack.orm = "sqlalchemy"; }
|
|
246
|
+
if (r.includes("tortoise") && !stack.orm) { stack.orm = "tortoise-orm"; }
|
|
247
|
+
if (r.includes("psycopg") && !stack.database) stack.database = "postgresql";
|
|
248
|
+
if (r.includes("mysqlclient") && !stack.database) stack.database = "mysql";
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!stack.packageManager) {
|
|
253
|
+
stack.packageManager = existsSafe(path.join(ROOT, "Pipfile")) ? "pipenv"
|
|
254
|
+
: existsSafe(path.join(ROOT, "poetry.lock")) ? "poetry" : "pip";
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── DB from config files ──
|
|
259
|
+
const ymls = await glob("**/application*.yml", { cwd: ROOT, absolute: true, ignore: ["**/node_modules/**", "**/build/**", "**/target/**", "**/.gradle/**"] });
|
|
260
|
+
for (const y of ymls) {
|
|
261
|
+
const c = readFileSafe(y);
|
|
262
|
+
if (!c) continue;
|
|
263
|
+
if (!stack.database && c.includes("postgresql")) stack.database = "postgresql";
|
|
264
|
+
if (!stack.database && c.includes("mysql")) stack.database = "mysql";
|
|
265
|
+
if (!stack.database && c.includes("oracle")) stack.database = "oracle";
|
|
266
|
+
if (!stack.database && c.includes("mongodb")) stack.database = "mongodb";
|
|
267
|
+
if (!stack.database && /\bh2\b/.test(c)) stack.database = "h2";
|
|
268
|
+
if (!stack.database && c.includes("sqlite")) stack.database = "sqlite";
|
|
269
|
+
if (!stack.port) {
|
|
270
|
+
const pm = c.match(/server:\s*\n\s*port:\s*(\d+)/) || c.match(/server\.port\s*[=:]\s*(\d+)/);
|
|
271
|
+
if (pm) stack.port = parseInt(pm[1]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// .env
|
|
276
|
+
for (const ef of [".env", ".env.local", ".env.development"]) {
|
|
277
|
+
const ep = path.join(ROOT, ef);
|
|
278
|
+
if (existsSafe(ep)) {
|
|
279
|
+
const ec = readFileSafe(ep);
|
|
280
|
+
if (ec && ec.includes("DATABASE_URL")) {
|
|
281
|
+
if (ec.includes("postgres") && !stack.database) stack.database = "postgresql";
|
|
282
|
+
if (ec.includes("mysql") && !stack.database) stack.database = "mysql";
|
|
283
|
+
if (ec.includes("mongodb") && !stack.database) stack.database = "mongodb";
|
|
284
|
+
if (ec.includes("sqlite") && !stack.database) stack.database = "sqlite";
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Prisma schema
|
|
290
|
+
const prismaSchema = path.join(ROOT, "prisma/schema.prisma");
|
|
291
|
+
if (existsSafe(prismaSchema)) {
|
|
292
|
+
const ps = readFileSafe(prismaSchema);
|
|
293
|
+
if (ps) {
|
|
294
|
+
const prov = ps.match(/provider\s*=\s*"(\w+)"/);
|
|
295
|
+
if (prov && !stack.database) {
|
|
296
|
+
const db = { postgresql: "postgresql", mysql: "mysql", sqlite: "sqlite", mongodb: "mongodb" };
|
|
297
|
+
if (db[prov[1]]) stack.database = db[prov[1]];
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Config file fallback (monorepo) ──
|
|
303
|
+
if (!stack.frontend) {
|
|
304
|
+
const nextConfigs = ["next.config.js", "next.config.mjs", "next.config.ts"];
|
|
305
|
+
if (nextConfigs.some(c => existsSafe(path.join(ROOT, c)))) {
|
|
306
|
+
stack.frontend = "nextjs"; stack.detected.push("next.config (fallback)");
|
|
307
|
+
if (!stack.language) stack.language = "typescript";
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (!stack.frontend) {
|
|
311
|
+
if (existsSafe(path.join(ROOT, "vite.config.ts")) || existsSafe(path.join(ROOT, "vite.config.js"))) {
|
|
312
|
+
stack.frontend = "react"; stack.detected.push("vite.config (fallback)");
|
|
313
|
+
if (!stack.language) stack.language = "typescript";
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (!stack.frontend) {
|
|
317
|
+
if (existsSafe(path.join(ROOT, "nuxt.config.ts")) || existsSafe(path.join(ROOT, "nuxt.config.js"))) {
|
|
318
|
+
stack.frontend = "vue"; stack.detected.push("nuxt.config (fallback)");
|
|
319
|
+
if (!stack.language) stack.language = "typescript";
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return stack;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
module.exports = { detectStack };
|