imhcode 1.1.0 → 2.0.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 (34) hide show
  1. package/README.md +18 -0
  2. package/USER_MANUAL.md +29 -0
  3. package/bin/imhcode.js +799 -1
  4. package/dist/orchestrator/builder.d.ts.map +1 -1
  5. package/dist/orchestrator/builder.js +27 -4
  6. package/dist/orchestrator/builder.js.map +1 -1
  7. package/dist/orchestrator/context-scanner.d.ts +23 -0
  8. package/dist/orchestrator/context-scanner.d.ts.map +1 -0
  9. package/dist/orchestrator/context-scanner.js +248 -0
  10. package/dist/orchestrator/context-scanner.js.map +1 -0
  11. package/dist/orchestrator/import-engine.d.ts +18 -0
  12. package/dist/orchestrator/import-engine.d.ts.map +1 -0
  13. package/dist/orchestrator/import-engine.js +116 -0
  14. package/dist/orchestrator/import-engine.js.map +1 -0
  15. package/dist/orchestrator/index.d.ts +4 -0
  16. package/dist/orchestrator/index.d.ts.map +1 -1
  17. package/dist/orchestrator/index.js +13 -1
  18. package/dist/orchestrator/index.js.map +1 -1
  19. package/dist/orchestrator/modification-engine.d.ts +12 -0
  20. package/dist/orchestrator/modification-engine.d.ts.map +1 -0
  21. package/dist/orchestrator/modification-engine.js +109 -0
  22. package/dist/orchestrator/modification-engine.js.map +1 -0
  23. package/dist/orchestrator/project-scanner.d.ts +16 -0
  24. package/dist/orchestrator/project-scanner.d.ts.map +1 -0
  25. package/dist/orchestrator/project-scanner.js +164 -0
  26. package/dist/orchestrator/project-scanner.js.map +1 -0
  27. package/package.json +1 -1
  28. package/skills/typeui-main/test/orchestrator.test.ts +88 -41
  29. package/src/orchestrator/builder.ts +29 -4
  30. package/src/orchestrator/context-scanner.ts +221 -0
  31. package/src/orchestrator/import-engine.ts +106 -0
  32. package/src/orchestrator/index.ts +8 -0
  33. package/src/orchestrator/modification-engine.ts +108 -0
  34. package/src/orchestrator/project-scanner.ts +134 -0
@@ -0,0 +1,221 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export interface ProjectScanResult {
5
+ hasFrontend: boolean;
6
+ hasBackend: boolean;
7
+ frontendFramework?: string;
8
+ backendFramework?: string;
9
+ dependencies: Record<string, string>;
10
+ directories: string[];
11
+ fileCount: number;
12
+ recentModifications: string[];
13
+ }
14
+
15
+ /**
16
+ * Scan project structure to build context for modifications.
17
+ */
18
+ export function scanProjectContext(cwd: string): ProjectScanResult {
19
+ const result: ProjectScanResult = {
20
+ hasFrontend: false,
21
+ hasBackend: false,
22
+ dependencies: {},
23
+ directories: [],
24
+ fileCount: 0,
25
+ recentModifications: []
26
+ };
27
+
28
+ // 1. Detect directories
29
+ try {
30
+ const items = fs.readdirSync(cwd);
31
+ for (const item of items) {
32
+ if (item.startsWith(".") || item === "node_modules" || item === "dist") continue;
33
+ const itemPath = path.join(cwd, item);
34
+ try {
35
+ const stat = fs.statSync(itemPath);
36
+ if (stat.isDirectory()) {
37
+ result.directories.push(item);
38
+ }
39
+ } catch {
40
+ // Ignore
41
+ }
42
+ }
43
+ } catch {
44
+ // Ignore
45
+ }
46
+
47
+ // 2. Scan frontend
48
+ const frontendPath = path.join(cwd, "frontend");
49
+ if (fs.existsSync(frontendPath) && fs.statSync(frontendPath).isDirectory()) {
50
+ result.hasFrontend = true;
51
+ const pkgJsonPath = path.join(frontendPath, "package.json");
52
+ if (fs.existsSync(pkgJsonPath)) {
53
+ try {
54
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
55
+ result.frontendFramework = pkg.dependencies?.next ? "Next.js" : pkg.dependencies?.vue ? "Vue" : "React";
56
+ Object.assign(result.dependencies, pkg.dependencies || {});
57
+ Object.assign(result.dependencies, pkg.devDependencies || {});
58
+ } catch {
59
+ // Ignore
60
+ }
61
+ }
62
+ }
63
+
64
+ // 3. Scan backend
65
+ const backendPath = path.join(cwd, "backend");
66
+ if (fs.existsSync(backendPath) && fs.statSync(backendPath).isDirectory()) {
67
+ result.hasBackend = true;
68
+ const composerJsonPath = path.join(backendPath, "composer.json");
69
+ if (fs.existsSync(composerJsonPath)) {
70
+ try {
71
+ const comp = JSON.parse(fs.readFileSync(composerJsonPath, "utf-8"));
72
+ result.backendFramework = "Laravel";
73
+ if (comp.require) {
74
+ Object.assign(result.dependencies, comp.require);
75
+ }
76
+ } catch {
77
+ // Ignore
78
+ }
79
+ }
80
+ // Also check for Python
81
+ const reqTxtPath = path.join(backendPath, "requirements.txt");
82
+ if (fs.existsSync(reqTxtPath)) {
83
+ result.backendFramework = "Python/FastAPI/Django";
84
+ }
85
+ }
86
+
87
+ // 4. Count files (shallow-ish recursive count for safety/perf)
88
+ let count = 0;
89
+ function countFiles(dir: string, depth = 0) {
90
+ if (depth > 4 || count > 500) return; // Cap at 500 files for scanning performance
91
+ try {
92
+ const files = fs.readdirSync(dir);
93
+ for (const file of files) {
94
+ if (file.startsWith(".") || file === "node_modules" || file === "dist" || file === "vendor") continue;
95
+ const fp = path.join(dir, file);
96
+ const stat = fs.statSync(fp);
97
+ if (stat.isDirectory()) {
98
+ countFiles(fp, depth + 1);
99
+ } else {
100
+ count++;
101
+ }
102
+ }
103
+ } catch {
104
+ // Ignore
105
+ }
106
+ }
107
+ countFiles(cwd);
108
+ result.fileCount = count;
109
+
110
+ // 5. Read recent modifications from log if exists
111
+ const modLogPath = path.join(cwd, "docs", "modifications.md");
112
+ if (fs.existsSync(modLogPath)) {
113
+ try {
114
+ const content = fs.readFileSync(modLogPath, "utf-8");
115
+ const lines = content.split("\n").filter(l => l.trim().startsWith("|")).slice(2);
116
+ result.recentModifications = lines.map(l => l.trim());
117
+ } catch {
118
+ // Ignore
119
+ }
120
+ }
121
+
122
+ return result;
123
+ }
124
+
125
+ /**
126
+ * Builds context prompt specifically for a modification task.
127
+ */
128
+ export function buildModificationPrompt(context: ProjectScanResult, description: string): string {
129
+ const lines = [
130
+ `# IMH-Code Project Modification Request`,
131
+ `You are tasked with modifying or extending an existing project.`,
132
+ ``,
133
+ `## Project Inventory`,
134
+ `- Frontend Active: ${context.hasFrontend ? `Yes (${context.frontendFramework})` : "No"}`,
135
+ `- Backend Active: ${context.hasBackend ? `Yes (${context.backendFramework})` : "No"}`,
136
+ `- Total Directories: ${context.directories.join(", ")}`,
137
+ `- File Count: ${context.fileCount}`,
138
+ ``,
139
+ `## Detected Stacks & Dependencies`,
140
+ Object.entries(context.dependencies)
141
+ .slice(0, 15) // Top 15 dependencies
142
+ .map(([k, v]) => `- \`${k}\`: \`${v}\``)
143
+ .join("\n"),
144
+ context.recentModifications.length > 0
145
+ ? `\n## Recent Project Modifications\n` + context.recentModifications.join("\n")
146
+ : "",
147
+ ``,
148
+ `## Modification Instructions`,
149
+ `Task: ${description}`,
150
+ ``,
151
+ `CRITICAL DIRECTIONS:`,
152
+ `1. Review the existing directories (e.g. frontend/ and backend/) to locate the relevant files.`,
153
+ `2. Perform the edit IN-PLACE. Do NOT delete or rewrite entire directories unless necessary.`,
154
+ `3. Adhere strictly to the existing coding style, frameworks, and packages.`,
155
+ `4. If new dependencies are needed, install them using the appropriate package manager in the correct directory.`,
156
+ `5. After completing, explain clearly what files were changed.`
157
+ ];
158
+
159
+ return lines.join("\n");
160
+ }
161
+
162
+ /**
163
+ * Helper to auto-select best agent ID based on task description keywords.
164
+ */
165
+ export function detectBestAgent(description: string, context: ProjectScanResult): string {
166
+ const d = description.toLowerCase();
167
+
168
+ // 1. Planning/Architecture
169
+ if (d.includes("plan") || d.includes("architect") || d.includes("road map") || d.includes("sprint")) {
170
+ return "planner";
171
+ }
172
+
173
+ // 2. DevOps/Infrastructure
174
+ if (d.includes("docker") || d.includes("kubernetes") || d.includes("deploy") || d.includes("ci/cd") || d.includes("github action") || d.includes("nginx")) {
175
+ return "devops-executor";
176
+ }
177
+
178
+ // 3. Testing
179
+ if (d.includes("test") || d.includes("spec") || d.includes("audit") || d.includes("security")) {
180
+ if (d.includes("security") || d.includes("vulnerability") || d.includes("owasp")) {
181
+ return "security-auditor";
182
+ }
183
+ return "tester";
184
+ }
185
+
186
+ // 4. SEO
187
+ if (d.includes("seo") || d.includes("meta tag") || d.includes("sitemap") || d.includes("robots.txt")) {
188
+ return "seo-optimizer";
189
+ }
190
+
191
+ // 5. Frontend Keywords Check
192
+ const hasFrontendKeywords = d.includes("ui") || d.includes("page") || d.includes("component") || d.includes("navbar") || d.includes("style") || d.includes("css") || d.includes("tailwind") || d.includes("button") || d.includes("layout");
193
+ if (hasFrontendKeywords) {
194
+ if (context.frontendFramework === "Next.js") return "nextjs-executor";
195
+ if (context.frontendFramework === "Vue") return "vue-executor";
196
+ return "react-executor";
197
+ }
198
+
199
+ // 6. Backend Keywords Check
200
+ const hasBackendKeywords = d.includes("api") || d.includes("route") || d.includes("database") || d.includes("controller") || d.includes("migration") || d.includes("model") || d.includes("query") || d.includes("auth");
201
+ if (hasBackendKeywords) {
202
+ if (context.backendFramework === "Laravel") return "laravel-executor";
203
+ if (context.backendFramework === "Python/FastAPI/Django") return "python-executor";
204
+ return "laravel-executor";
205
+ }
206
+
207
+ // 7. Directory fallbacks if no keywords matched
208
+ if (context.hasFrontend) {
209
+ if (context.frontendFramework === "Next.js") return "nextjs-executor";
210
+ if (context.frontendFramework === "Vue") return "vue-executor";
211
+ return "react-executor";
212
+ }
213
+
214
+ if (context.hasBackend) {
215
+ if (context.backendFramework === "Laravel") return "laravel-executor";
216
+ if (context.backendFramework === "Python/FastAPI/Django") return "python-executor";
217
+ return "laravel-executor";
218
+ }
219
+
220
+ return "laravel-executor"; // Ultimate default
221
+ }
@@ -0,0 +1,106 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { scanProject } from "./project-scanner";
4
+
5
+ export interface ImportResult {
6
+ success: boolean;
7
+ scanResult: any;
8
+ importMap: {
9
+ frontend: string;
10
+ backend: string;
11
+ database: string;
12
+ detectedStack: {
13
+ frontend: string | null;
14
+ backend: string | null;
15
+ };
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Import an existing codebase into the IMH-Code orchestration framework.
21
+ */
22
+ export function importProject(cwd: string): ImportResult {
23
+ const scanResult = scanProject(cwd);
24
+
25
+ const imhDir = path.join(cwd, ".imhcode");
26
+ if (!fs.existsSync(imhDir)) {
27
+ fs.mkdirSync(imhDir, { recursive: true });
28
+ }
29
+
30
+ const cmdDir = path.join(imhDir, "commands");
31
+ if (!fs.existsSync(cmdDir)) {
32
+ fs.mkdirSync(cmdDir, { recursive: true });
33
+ }
34
+
35
+ const sessionsDir = path.join(imhDir, "sessions");
36
+ if (!fs.existsSync(sessionsDir)) {
37
+ fs.mkdirSync(sessionsDir, { recursive: true });
38
+ }
39
+
40
+ // Map paths. If it scanned to ".", default to empty or standard string
41
+ const importMap = {
42
+ frontend: scanResult.frontendPath || "frontend",
43
+ backend: scanResult.backendPath || "backend",
44
+ database: scanResult.database || "PostgreSQL",
45
+ detectedStack: {
46
+ frontend: scanResult.detectedFrontend,
47
+ backend: scanResult.detectedBackend
48
+ }
49
+ };
50
+
51
+ fs.writeFileSync(
52
+ path.join(imhDir, "import-map.json"),
53
+ JSON.stringify(importMap, null, 2),
54
+ "utf-8"
55
+ );
56
+
57
+ // PROJECT_BRIEF.md
58
+ const briefPath = path.join(cwd, "PROJECT_BRIEF.md");
59
+ const briefContent = `# PROJECT_BRIEF.md (Imported Project)
60
+
61
+ > **IMH-Code — Imam Hussain Coding Harness Platform**
62
+ > Centralized memory for this imported project.
63
+
64
+ ## Project Summary
65
+
66
+ *(Imported project. Add a description of your project here to guide the agents.)*
67
+
68
+ ## Status
69
+
70
+ - **Imported**: ${new Date().toLocaleDateString()}
71
+ - **Current Sprint**: Sprint 1 (Modifications Mode)
72
+
73
+ ## Design Specification
74
+
75
+ - **Design Style**: Modern SaaS
76
+ - **Color Palette**: Dark Premium
77
+
78
+ ## Scope
79
+
80
+ - **Frontend Path**: \`${importMap.frontend}\` (${scanResult.detectedFrontend || "None"})
81
+ - **Backend Path**: \`${importMap.backend}\` (${scanResult.detectedBackend || "None"})
82
+ - **Database**: ${importMap.database}
83
+ `;
84
+ fs.writeFileSync(briefPath, briefContent, "utf-8");
85
+
86
+ // context.md
87
+ const contextPath = path.join(imhDir, "context.md");
88
+ const contextContent = `# IMH-Code Project Context (Imported)
89
+
90
+ Generated: ${new Date().toLocaleDateString()}
91
+ Project Path: ${cwd}
92
+ Frontend Framework: ${scanResult.detectedFrontend || "None"} (Path: ${importMap.frontend})
93
+ Backend Framework: ${scanResult.detectedBackend || "None"} (Path: ${importMap.backend})
94
+
95
+ ## Directory Structure Mappings
96
+ - Frontend: \`${importMap.frontend}\`
97
+ - Backend: \`${importMap.backend}\`
98
+ `;
99
+ fs.writeFileSync(contextPath, contextContent, "utf-8");
100
+
101
+ return {
102
+ success: true,
103
+ scanResult,
104
+ importMap
105
+ };
106
+ }
@@ -48,6 +48,14 @@ export {
48
48
  // Session
49
49
  export { saveSession, resolveSessionDir } from "./session";
50
50
 
51
+ // Modification & Scanning
52
+ export { scanProjectContext, buildModificationPrompt, detectBestAgent } from "./context-scanner";
53
+ export { runModification } from "./modification-engine";
54
+
55
+ // Project Import & Extension
56
+ export { scanProject } from "./project-scanner";
57
+ export { importProject } from "./import-engine";
58
+
51
59
  // ─── High-level run function ──────────────────────────────────────────────────
52
60
 
53
61
  import * as fs from "fs";
@@ -0,0 +1,108 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { scanProjectContext, buildModificationPrompt, detectBestAgent } from "./context-scanner";
4
+ import { loadRegistry, getAgent } from "./registry";
5
+ import { runAgent } from "./index";
6
+
7
+ export interface ModificationOptions {
8
+ agent?: string;
9
+ engine?: string;
10
+ model?: string;
11
+ dryRun?: boolean;
12
+ scope?: string;
13
+ }
14
+
15
+ /**
16
+ * Execute a targeted project modification.
17
+ */
18
+ export async function runModification(
19
+ cwd: string,
20
+ description: string,
21
+ options: ModificationOptions = {}
22
+ ): Promise<any> {
23
+ // 1. Scan context
24
+ const context = scanProjectContext(cwd);
25
+
26
+ // 2. Select agent
27
+ const selectedAgentId = options.agent || detectBestAgent(description, context);
28
+
29
+ console.log(`\n🛠️ [IMH-Code] Initializing Modification Engine`);
30
+ console.log(` Task: "${description}"`);
31
+ console.log(` Agent: ${selectedAgentId}`);
32
+
33
+ // Resolve agent manifests directory
34
+ const globalAgents = path.join(process.env.HOME || "", ".imhcode", "agents");
35
+ const localAgents = path.join(cwd, "agents");
36
+ const pkgAgents = path.join(__dirname, "..", "..", "agents");
37
+ const agentsDir = fs.existsSync(globalAgents) ? globalAgents :
38
+ fs.existsSync(localAgents) ? localAgents :
39
+ pkgAgents;
40
+
41
+ // 3. Load Registry
42
+ const { agents, errors } = await loadRegistry(agentsDir, cwd);
43
+ if (errors.some(e => e.agentId === selectedAgentId)) {
44
+ throw new Error(`Failed to load agent "${selectedAgentId}": ${errors.find(e => e.agentId === selectedAgentId)?.error}`);
45
+ }
46
+
47
+ const agent = getAgent(agents, selectedAgentId);
48
+
49
+ // 4. Build custom modification task
50
+ const modificationTask = buildModificationPrompt(context, description);
51
+
52
+ // 5. Run the agent
53
+ const result = await runAgent(
54
+ agent,
55
+ modificationTask,
56
+ {
57
+ dryRun: options.dryRun !== false,
58
+ engine: options.engine,
59
+ model: options.model,
60
+ outputDir: path.join(cwd, ".imhcode", "sessions"),
61
+ cwd
62
+ }
63
+ );
64
+
65
+ // 6. Log changes if live and successful
66
+ if (!result.dryRun && result.errors.length === 0) {
67
+ logModification(cwd, {
68
+ task: description,
69
+ agentId: selectedAgentId,
70
+ model: result.model,
71
+ durationMs: result.durationMs
72
+ });
73
+ }
74
+
75
+ return result;
76
+ }
77
+
78
+ interface LogEntry {
79
+ task: string;
80
+ agentId: string;
81
+ model: string;
82
+ durationMs: number;
83
+ }
84
+
85
+ function logModification(cwd: string, entry: LogEntry): void {
86
+ const docsDir = path.join(cwd, "docs");
87
+ if (!fs.existsSync(docsDir)) {
88
+ fs.mkdirSync(docsDir, { recursive: true });
89
+ }
90
+
91
+ const logPath = path.join(docsDir, "modifications.md");
92
+ const timestamp = new Date().toISOString().replace("T", " ").substring(0, 19);
93
+
94
+ let header = `# 🛠️ IMH-Code Project Modifications\n\n| Date | Task | Agent | Model | Duration |\n|------|------|-------|-------|----------|\n`;
95
+ if (fs.existsSync(logPath)) {
96
+ try {
97
+ const existing = fs.readFileSync(logPath, "utf-8");
98
+ if (existing.trim().length > 10) {
99
+ header = existing;
100
+ }
101
+ } catch {
102
+ // Ignore
103
+ }
104
+ }
105
+
106
+ const row = `| ${timestamp} | ${entry.task} | \`${entry.agentId}\` | ${entry.model} | ${(entry.durationMs / 1000).toFixed(1)}s |\n`;
107
+ fs.writeFileSync(logPath, header.endsWith("\n") ? header + row : header + "\n" + row, "utf-8");
108
+ }
@@ -0,0 +1,134 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ export interface ScanResult {
5
+ detectedFrontend: string | null;
6
+ detectedBackend: string | null;
7
+ frontendPath: string | null;
8
+ backendPath: string | null;
9
+ database: string | null;
10
+ dockerized: boolean;
11
+ hasCICD: boolean;
12
+ packageJson?: any;
13
+ composerJson?: any;
14
+ }
15
+
16
+ /**
17
+ * Deep scan an existing codebase to detect stack and structure.
18
+ */
19
+ export function scanProject(cwd: string): ScanResult {
20
+ const result: ScanResult = {
21
+ detectedFrontend: null,
22
+ detectedBackend: null,
23
+ frontendPath: null,
24
+ backendPath: null,
25
+ database: null,
26
+ dockerized: false,
27
+ hasCICD: false,
28
+ };
29
+
30
+ // 1. Helper to search for files recursively with limits
31
+ function findFile(dir: string, name: string, depth = 0): string | null {
32
+ if (depth > 3) return null;
33
+ try {
34
+ const files = fs.readdirSync(dir);
35
+ if (files.includes(name)) {
36
+ return path.join(dir, name);
37
+ }
38
+ for (const file of files) {
39
+ if (file.startsWith(".") || file === "node_modules" || file === "dist" || file === "vendor") continue;
40
+ const fp = path.join(dir, file);
41
+ if (fs.statSync(fp).isDirectory()) {
42
+ const found = findFile(fp, name, depth + 1);
43
+ if (found) return found;
44
+ }
45
+ }
46
+ } catch {
47
+ // Ignore
48
+ }
49
+ return null;
50
+ }
51
+
52
+ // 2. Scan for package.json (Frontend detection)
53
+ const pkgJsonPath = findFile(cwd, "package.json");
54
+ if (pkgJsonPath) {
55
+ try {
56
+ const content = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
57
+ result.packageJson = content;
58
+ const deps = { ...(content.dependencies || {}), ...(content.devDependencies || {}) };
59
+
60
+ if (deps.next) result.detectedFrontend = "Next.js";
61
+ else if (deps.nuxt || deps.nuxt3 || deps.nuxt4) result.detectedFrontend = "Nuxt/Vue";
62
+ else if (deps.react) result.detectedFrontend = "React SPA";
63
+ else if (deps.vue) result.detectedFrontend = "Vue SPA";
64
+ else if (deps.svelte) result.detectedFrontend = "Svelte";
65
+ else result.detectedFrontend = "Node/JS";
66
+
67
+ // If package.json is in a subdirectory, use that subdirectory
68
+ const relative = path.relative(cwd, path.dirname(pkgJsonPath));
69
+ result.frontendPath = relative === "" ? "." : relative;
70
+ } catch {
71
+ // Ignore
72
+ }
73
+ }
74
+
75
+ // 3. Scan for composer.json, requirements.txt, or pom.xml (Backend detection)
76
+ const composerPath = findFile(cwd, "composer.json");
77
+ if (composerPath) {
78
+ try {
79
+ const content = JSON.parse(fs.readFileSync(composerPath, "utf-8"));
80
+ result.composerJson = content;
81
+ if (content.require && (content.require["laravel/framework"] || content.require["laravel/lumen-framework"])) {
82
+ result.detectedBackend = "Laravel";
83
+ } else {
84
+ result.detectedBackend = "PHP";
85
+ }
86
+ const relative = path.relative(cwd, path.dirname(composerPath));
87
+ result.backendPath = relative === "" ? "." : relative;
88
+ } catch {
89
+ // Ignore
90
+ }
91
+ }
92
+
93
+ if (!result.detectedBackend) {
94
+ const reqsTxtPath = findFile(cwd, "requirements.txt");
95
+ const pyprojectPath = findFile(cwd, "pyproject.toml");
96
+ if (reqsTxtPath || pyprojectPath) {
97
+ result.detectedBackend = "Python (Django/FastAPI)";
98
+ const matchedPath = reqsTxtPath || pyprojectPath || "";
99
+ const relative = path.relative(cwd, path.dirname(matchedPath));
100
+ result.backendPath = relative === "" ? "." : relative;
101
+ }
102
+ }
103
+
104
+ // 4. Database Detection from env or configs
105
+ const envPath = findFile(cwd, ".env") || findFile(cwd, ".env.example");
106
+ if (envPath) {
107
+ try {
108
+ const content = fs.readFileSync(envPath, "utf-8");
109
+ if (content.includes("DB_CONNECTION=pgsql") || content.includes("postgresql://")) {
110
+ result.database = "PostgreSQL";
111
+ } else if (content.includes("DB_CONNECTION=mysql") || content.includes("mysql://")) {
112
+ result.database = "MySQL";
113
+ } else if (content.includes("DB_CONNECTION=sqlite") || content.includes("sqlite3")) {
114
+ result.database = "SQLite";
115
+ }
116
+ } catch {
117
+ // Ignore
118
+ }
119
+ }
120
+
121
+ // 5. Infrastructure detection
122
+ const dockerCompose = findFile(cwd, "docker-compose.yml");
123
+ const dockerfile = findFile(cwd, "Dockerfile");
124
+ if (dockerCompose || dockerfile) {
125
+ result.dockerized = true;
126
+ }
127
+
128
+ const githubWorkflows = findFile(cwd, ".github/workflows");
129
+ if (githubWorkflows) {
130
+ result.hasCICD = true;
131
+ }
132
+
133
+ return result;
134
+ }