imhcode 1.1.1 → 2.0.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.
Files changed (34) hide show
  1. package/README.md +18 -0
  2. package/USER_MANUAL.md +29 -0
  3. package/bin/imhcode.js +839 -0
  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
package/bin/imhcode.js CHANGED
@@ -82,6 +82,41 @@ if (command === 'plan') {
82
82
  console.error(err.message ?? err);
83
83
  process.exit(1);
84
84
  });
85
+ } else if (command === 'modify') {
86
+ runModifyCommand(args.slice(1)).catch(err => {
87
+ console.error(err.message ?? err);
88
+ process.exit(1);
89
+ });
90
+ } else if (command === 'feature') {
91
+ runFeatureCommand(args.slice(1)).catch(err => {
92
+ console.error(err.message ?? err);
93
+ process.exit(1);
94
+ });
95
+ } else if (command === 'fix') {
96
+ runFixCommand(args.slice(1)).catch(err => {
97
+ console.error(err.message ?? err);
98
+ process.exit(1);
99
+ });
100
+ } else if (command === 'import') {
101
+ runImportCommand(args.slice(1)).catch(err => {
102
+ console.error(err.message ?? err);
103
+ process.exit(1);
104
+ });
105
+ } else if (command === 'scan') {
106
+ runScanCommand(args.slice(1)).catch(err => {
107
+ console.error(err.message ?? err);
108
+ process.exit(1);
109
+ });
110
+ } else if (command === 'gui') {
111
+ runGuiCommand(args.slice(1)).catch(err => {
112
+ console.error(err.message ?? err);
113
+ process.exit(1);
114
+ });
115
+ } else if (command === 'execute-feature' || command === 'exec-feature') {
116
+ runExecuteFeatureCommand(args.slice(1)).catch(err => {
117
+ console.error(err.message ?? err);
118
+ process.exit(1);
119
+ });
85
120
  } else if (command === 'sprint') {
86
121
  // Legacy compat: zeoel sprint design/execute → imhcode execute
87
122
  runSprintLegacyCommand(subcommand, args.slice(2)).catch(err => {
@@ -118,6 +153,15 @@ function printGeneralHelp() {
118
153
  -m, --model <model> → Override model
119
154
  --output <path> → Save session to custom path
120
155
 
156
+ Usability Upgrades (v2.0):
157
+ ${CLI_CMD} modify "task" → In-place codebase modification/enhancement (runs targeted agent)
158
+ ${CLI_CMD} feature "description" → Plan a targeted mini-sprint for a new feature addition
159
+ ${CLI_CMD} execute-feature [N] → Execute planned feature sprint N
160
+ ${CLI_CMD} fix "description" → Quick targeted bug fix modification (alias of modify)
161
+ ${CLI_CMD} scan [path] → Scan existing directory structure and detect technology stacks
162
+ ${CLI_CMD} import [path] → Import existing project structure and configure IMH-Code context
163
+ ${CLI_CMD} gui [--port N] → Start the Laravel GUI Control Center web server
164
+
121
165
  Pipeline:
122
166
  1. ${CLI_CMD} → Init project (no frontend/backend dirs yet)
123
167
  2. Edit docs/start.md → Answer scope questions + write your description
@@ -2667,3 +2711,798 @@ function ensureCavemanAndGraphify() {
2667
2711
  try { execSync('npx skills add juliusbrussee/caveman', { cwd: GLOBAL_DIR, stdio: 'inherit' }); console.log(' ✅ Caveman compression rules integrated globally'); } catch { /* ignore */ }
2668
2712
  }
2669
2713
  }
2714
+
2715
+ // ─── Phase A & B Upgrade Commands ──────────────────────────────────────────────
2716
+
2717
+ async function runModifyCommand(restArgs) {
2718
+ const cwd = process.cwd();
2719
+ const live = restArgs.includes('--live');
2720
+ const agentIdx = restArgs.indexOf('--agent');
2721
+ const agent = agentIdx >= 0 ? restArgs[agentIdx + 1] : undefined;
2722
+ const engineIdx = restArgs.indexOf('--engine');
2723
+ const engine = engineIdx >= 0 ? restArgs[engineIdx + 1] : undefined;
2724
+ const modelIdx = restArgs.includes('--model') ? restArgs.indexOf('--model') : restArgs.indexOf('-m');
2725
+ const model = modelIdx >= 0 ? restArgs[modelIdx + 1] : undefined;
2726
+
2727
+ const descriptionArgs = [];
2728
+ for (let i = 0; i < restArgs.length; i++) {
2729
+ const arg = restArgs[i];
2730
+ if (arg === '--live') continue;
2731
+ if (arg === '--agent' || arg === '--engine' || arg === '--model' || arg === '-m') {
2732
+ i++;
2733
+ continue;
2734
+ }
2735
+ descriptionArgs.push(arg);
2736
+ }
2737
+
2738
+ const description = descriptionArgs.join(' ').trim();
2739
+ if (!description) {
2740
+ console.error('❌ Error: Please provide a description for the modification.');
2741
+ console.error(' Usage: imhcode modify "description" [--agent <agent>] [--engine <engine>] [-m/--model <model>] [--live]');
2742
+ process.exit(1);
2743
+ }
2744
+
2745
+ const orc = loadOrchestrator();
2746
+ console.log(`\n\uD83D\uDCD7 IMH-Code — Running In-place Modification`);
2747
+ const result = await orc.runModification(cwd, description, {
2748
+ agent,
2749
+ engine,
2750
+ model,
2751
+ dryRun: !live
2752
+ });
2753
+
2754
+ if (result.errors && result.errors.length > 0) {
2755
+ console.error(`❌ Modification failed with errors:`, result.errors.join(', '));
2756
+ process.exit(1);
2757
+ }
2758
+ }
2759
+
2760
+ async function runFixCommand(restArgs) {
2761
+ return runModifyCommand(restArgs);
2762
+ }
2763
+
2764
+ async function runFeatureCommand(restArgs) {
2765
+ const cwd = process.cwd();
2766
+ const live = restArgs.includes('--live');
2767
+
2768
+ const descriptionArgs = [];
2769
+ for (let i = 0; i < restArgs.length; i++) {
2770
+ if (restArgs[i] === '--live') continue;
2771
+ descriptionArgs.push(restArgs[i]);
2772
+ }
2773
+
2774
+ const description = descriptionArgs.join(' ').trim();
2775
+ if (!description) {
2776
+ console.error('❌ Error: Please provide a description for the feature.');
2777
+ console.error(' Usage: imhcode feature "description"');
2778
+ process.exit(1);
2779
+ }
2780
+
2781
+ console.log(`\n\uD83D\uDCD7 IMH-Code — Feature Sprint Planner`);
2782
+ console.log(` Feature: "${description}"\n`);
2783
+
2784
+ const config = loadLocalConfig(cwd);
2785
+ const orc = loadOrchestrator();
2786
+ const scanResult = orc.scanProjectContext(cwd);
2787
+
2788
+ const docsDir = path.join(cwd, DOCS_DIR);
2789
+ let nextFeatureNum = 1;
2790
+ if (fs.existsSync(docsDir)) {
2791
+ try {
2792
+ const folders = fs.readdirSync(docsDir);
2793
+ for (const f of folders) {
2794
+ const m = f.match(/^feature-(\d+)$/i);
2795
+ if (m) nextFeatureNum = Math.max(nextFeatureNum, parseInt(m[1], 10) + 1);
2796
+ }
2797
+ } catch {}
2798
+ }
2799
+
2800
+ const promptText = `You are the IMH-Code Planner.
2801
+ An existing project needs a new feature: "${description}"
2802
+ Existing Project Stack:
2803
+ - Frontend: ${scanResult.hasFrontend ? scanResult.frontendFramework : 'None'}
2804
+ - Backend: ${scanResult.hasBackend ? scanResult.backendFramework : 'None'}
2805
+ - Directories: ${scanResult.directories.join(', ')}
2806
+
2807
+ Design a mini sprint plan with 1-3 tasks to implement this feature.
2808
+ Output ONLY valid JSON in this format (no explanations, no markdown fences):
2809
+ {
2810
+ "title": "Feature: ${description.replace(/"/g, '\\"')}",
2811
+ "tasks": [
2812
+ {
2813
+ "num": 1,
2814
+ "task": "Specific task description",
2815
+ "agent": "nextjs-executor|laravel-executor|designer|devops-executor",
2816
+ "tier": "light|standard|complex"
2817
+ }
2818
+ ]
2819
+ }`;
2820
+
2821
+ console.log(`\uD83E\uDDE0 Invoking Planning LLM...`);
2822
+ const llmOutput = await invokePlanningLLM(promptText, config, cwd);
2823
+
2824
+ let parsed;
2825
+ if (llmOutput) {
2826
+ try {
2827
+ const cleaned = llmOutput.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
2828
+ parsed = JSON.parse(cleaned);
2829
+ } catch {
2830
+ const jsonMatch = llmOutput.match(/\{[\s\S]*"tasks"[\s\S]*\}/);
2831
+ if (jsonMatch) {
2832
+ try { parsed = JSON.parse(jsonMatch[0]); } catch {}
2833
+ }
2834
+ }
2835
+ }
2836
+
2837
+ if (!parsed || !parsed.tasks || parsed.tasks.length === 0) {
2838
+ console.log(`\u26A0\uFE0F Could not parse JSON. Falling back to static task plan...`);
2839
+ parsed = {
2840
+ title: `Feature: ${description}`,
2841
+ tasks: [
2842
+ { num: 1, task: `Design and implement: ${description}`, agent: scanResult.hasFrontend ? 'nextjs-executor' : 'laravel-executor', tier: 'standard' }
2843
+ ]
2844
+ };
2845
+ }
2846
+
2847
+ await generateFeatureSprint(cwd, nextFeatureNum, parsed.title, parsed.tasks, config);
2848
+
2849
+ console.log(`\n\u2705 Feature sprint planned successfully!`);
2850
+ console.log(` Folder: docs/feature-${nextFeatureNum}/`);
2851
+ console.log(` Tasks planned: ${parsed.tasks.length}`);
2852
+ console.log(`\n\uD83D\uDE80 NEXT STEPS:`);
2853
+ console.log(` 1. Review the tasks in docs/feature-${nextFeatureNum}/plan.md`);
2854
+ console.log(` 2. Run command to execute all feature tasks sequentially:`);
2855
+ console.log(` imhcode execute-feature ${nextFeatureNum}\n`);
2856
+ }
2857
+
2858
+ async function generateFeatureSprint(cwd, featureNum, title, tasks, config) {
2859
+ const sprintDir = path.join(cwd, DOCS_DIR, `feature-${featureNum}`);
2860
+ fs.mkdirSync(sprintDir, { recursive: true });
2861
+ const tasksDir = path.join(cwd, LOCAL_DIR_NAME, 'commands', `feature-${featureNum}`);
2862
+ fs.mkdirSync(tasksDir, { recursive: true });
2863
+
2864
+ let planMd = `# Feature Sprint ${featureNum}: ${title}\n\n## Task Table\n\n| # | Task | Agent | Category | Model | Tier |\n|---|------|-------|----------|-------|------|\n`;
2865
+ tasks.forEach((t, i) => {
2866
+ const cat = getAgentCategory(t.agent);
2867
+ const model = config?.model_routing?.[cat]?.model || 'default';
2868
+ planMd += `| ${i+1} | ${t.task} | \`${t.agent}\` | ${cat} | ${model} | ${t.tier} |\n`;
2869
+ });
2870
+ fs.writeFileSync(path.join(sprintDir, 'plan.md'), planMd, 'utf-8');
2871
+
2872
+ const progressMd = `# Feature Sprint ${featureNum} Progress\n\nStatus: \uD83D\uDDF2 Not Started\nStart: \u2014\nEnd: \u2014\n\n## Tasks\n` +
2873
+ tasks.map((t, i) => `- [ ] Task ${i+1}: ${t.task} [\`${t.agent}\`]`).join('\n') + '\n';
2874
+ fs.writeFileSync(path.join(sprintDir, 'progress.md'), progressMd, 'utf-8');
2875
+ fs.writeFileSync(path.join(sprintDir, 'deferred.md'), `# Feature Sprint ${featureNum} Deferred Items\n\nNone yet.\n`, 'utf-8');
2876
+
2877
+ for (let i = 0; i < tasks.length; i++) {
2878
+ const t = tasks[i];
2879
+ const taskNum = i + 1;
2880
+ const cat = getAgentCategory(t.agent);
2881
+ const routedEngine = config?.model_routing?.[cat]?.engine || '';
2882
+ const routedModel = config?.model_routing?.[cat]?.model || '';
2883
+
2884
+ const engineFlag = routedEngine ? `--engine ${routedEngine}` : '';
2885
+ const modelFlag = routedModel ? `--model "${routedModel}"` : '';
2886
+
2887
+ const taskScript = `#!/bin/bash
2888
+ # IMH-Code — Feature \${featureNum} Task \${taskNum}
2889
+ CWD="\$(cd "\$(dirname "\\\${BASH_SOURCE[0]}")" && pwd)"
2890
+ cd "\$CWD/../../.."
2891
+
2892
+ PROGRESS_FILE="\$CWD/../../../docs/feature-${featureNum}/progress.md"
2893
+ if [ -f "\$PROGRESS_FILE" ]; then
2894
+ if grep -q -e "- \\\\[xX\\\\] Task \${taskNum}:" "\$PROGRESS_FILE"; then
2895
+ echo "\u2705 Task \${taskNum} is already completed. Skipping."
2896
+ exit 0
2897
+ fi
2898
+ fi
2899
+
2900
+ TASK="${t.task.replace(/"/g, '\\"')}"
2901
+
2902
+ echo "\uD83D\uDCCB Running Task \${taskNum}: ${t.task}"
2903
+ echo " Agent: ${t.agent}"
2904
+ echo " Model: ${routedModel || 'default'} via ${routedEngine || 'default'}"
2905
+
2906
+ if command -v imhcode >/dev/null 2>&1; then
2907
+ imhcode agent run ${t.agent} "\$TASK" --live ${engineFlag} ${modelFlag}
2908
+ else
2909
+ node "\$(npm root -g)/imhcode/bin/imhcode.js" agent run ${t.agent} "\$TASK" --live ${engineFlag} ${modelFlag}
2910
+ fi
2911
+
2912
+ if [ \$? -eq 0 ]; then
2913
+ if [ -f "\$PROGRESS_FILE" ]; then
2914
+ sed -i '' "s/- \\\\[ \\\\] Task \${taskNum}:/- [x] Task \${taskNum}:/g" "\$PROGRESS_FILE" 2>/dev/null || sed -i "s/- \\\\[ \\\\] Task \${taskNum}:/- [x] Task \${taskNum}:/g" "\$PROGRESS_FILE"
2915
+ echo "\u2705 Marked Task \${taskNum} as completed."
2916
+ fi
2917
+ fi
2918
+ `;
2919
+ fs.writeFileSync(path.join(tasksDir, `task_${taskNum}.sh`), taskScript, { mode: 0o755 });
2920
+ }
2921
+
2922
+ const runAllScript = `#!/bin/bash
2923
+ # IMH-Code — Run Feature Sprint ${featureNum}
2924
+ set -e
2925
+ CWD="\$(cd "\$(dirname "\\\${BASH_SOURCE[0]}")" && pwd)"
2926
+ TASKS_DIR="\$CWD/../../.imhcode/commands/feature-${featureNum}"
2927
+
2928
+ echo "\uD83D\uDCD7 IMH-Code — Executing Feature Sprint ${featureNum}"
2929
+ ` + tasks.map((t, i) => `echo "\\n─── Task ${i+1}/${tasks.length} ───"\nbash "\$TASKS_DIR/task_${i+1}.sh"`).join('\n') + `\n\necho "\\n\uD83C\uDFC1 Feature Sprint ${featureNum} completed!"\n`;
2930
+ fs.writeFileSync(path.join(sprintDir, 'run_all_tasks.sh'), runAllScript, { mode: 0o755 });
2931
+ }
2932
+
2933
+ async function runExecuteFeatureCommand(restArgs) {
2934
+ const cwd = process.cwd();
2935
+ const featureNum = parseInt(restArgs[0], 10);
2936
+ if (isNaN(featureNum)) {
2937
+ console.error('\u274C Error: Please specify a feature number to execute.');
2938
+ console.error(' Usage: imhcode execute-feature <number>');
2939
+ process.exit(1);
2940
+ }
2941
+
2942
+ const scriptPath = path.join(cwd, 'docs', `feature-${featureNum}`, 'run_all_tasks.sh');
2943
+ if (!fs.existsSync(scriptPath)) {
2944
+ console.error(`\u274C Error: Feature sprint ${featureNum} not found.`);
2945
+ process.exit(1);
2946
+ }
2947
+
2948
+ try { fs.chmodSync(scriptPath, 0o755); } catch {}
2949
+ console.log(`\n\uD83D\uDCD7 IMH-Code — Executing Feature ${featureNum}\n`);
2950
+ execSync(`sh "${scriptPath}"`, { stdio: 'inherit', cwd });
2951
+ }
2952
+
2953
+ async function runImportCommand(restArgs) {
2954
+ const cwd = process.cwd();
2955
+ const targetDir = restArgs[0] ? path.resolve(cwd, restArgs[0]) : cwd;
2956
+
2957
+ console.log(`\n\uD83D\uDCD7 IMH-Code — Importing Codebase`);
2958
+ console.log(` Target Directory: ${targetDir}\n`);
2959
+
2960
+ if (!fs.existsSync(targetDir)) {
2961
+ console.error(`\u274C Error: Directory does not exist: ${targetDir}`);
2962
+ process.exit(1);
2963
+ }
2964
+
2965
+ const orc = loadOrchestrator();
2966
+ const result = orc.importProject(targetDir);
2967
+
2968
+ if (result.success) {
2969
+ console.log(`\n\u2705 Codebase imported successfully!`);
2970
+ console.log(` Stack detected:`);
2971
+ console.log(` Frontend: ${result.scanResult.detectedFrontend || 'None'}`);
2972
+ console.log(` Backend: ${result.scanResult.detectedBackend || 'None'}`);
2973
+ console.log(` Database: ${result.scanResult.database || 'None'}`);
2974
+ console.log(`\n\uD83D\uDCBE Context files generated:`);
2975
+ console.log(` • ${path.join(targetDir, '.imhcode', 'import-map.json')}`);
2976
+ console.log(` • ${path.join(targetDir, 'PROJECT_BRIEF.md')}`);
2977
+ console.log(` • ${path.join(targetDir, '.imhcode', 'context.md')}`);
2978
+ console.log(`\n\uD83D\uDE80 You can now run "imhcode modify" or "imhcode feature" inside this directory.`);
2979
+ } else {
2980
+ console.error(`\u274C Import failed.`);
2981
+ process.exit(1);
2982
+ }
2983
+ }
2984
+
2985
+ async function runScanCommand(restArgs) {
2986
+ const cwd = process.cwd();
2987
+ const targetDir = restArgs[0] ? path.resolve(cwd, restArgs[0]) : cwd;
2988
+
2989
+ console.log(`\n\uD83D\uDCD7 IMH-Code — Scanning Codebase`);
2990
+ console.log(` Target Directory: ${targetDir}\n`);
2991
+
2992
+ if (!fs.existsSync(targetDir)) {
2993
+ console.error(`\u274C Error: Directory does not exist: ${targetDir}`);
2994
+ process.exit(1);
2995
+ }
2996
+
2997
+ const orc = loadOrchestrator();
2998
+ const scanResult = orc.scanProject(targetDir);
2999
+
3000
+ console.log(`\uD83D\uDD0D Scan Results:`);
3001
+ console.log(` Frontend Stack: ${scanResult.detectedFrontend || 'None'} (Path: ${scanResult.frontendPath || 'N/A'})`);
3002
+ console.log(` Backend Stack: ${scanResult.detectedBackend || 'None'} (Path: ${scanResult.backendPath || 'N/A'})`);
3003
+ console.log(` Database: ${scanResult.database || 'None'}`);
3004
+ console.log(` Dockerized: ${scanResult.dockerized ? 'Yes' : 'No'}`);
3005
+ console.log(` CI/CD Pipelines: ${scanResult.hasCICD ? 'Yes' : 'No'}`);
3006
+ console.log('');
3007
+ }
3008
+
3009
+ async function runGuiCommand(restArgs) {
3010
+ const portIdx = restArgs.indexOf('--port');
3011
+ const port = portIdx >= 0 ? parseInt(restArgs[portIdx + 1], 10) : 8000;
3012
+ const openBrowser = restArgs.includes('--open');
3013
+
3014
+ console.log(`\n🕌 IMH-Code — Starting GUI Dashboard`);
3015
+
3016
+ // 1. Verify environment
3017
+ try {
3018
+ execSync('php -v', { stdio: 'ignore' });
3019
+ } catch {
3020
+ console.error('❌ Error: PHP is not installed or not in PATH. Laravel GUI requires PHP 8.2+.');
3021
+ process.exit(1);
3022
+ }
3023
+
3024
+ try {
3025
+ execSync('composer --version', { stdio: 'ignore' });
3026
+ } catch {
3027
+ console.error('❌ Error: Composer is not installed or not in PATH. Laravel GUI requires Composer.');
3028
+ process.exit(1);
3029
+ }
3030
+
3031
+ const guiPath = path.join(GLOBAL_DIR, 'gui');
3032
+
3033
+ // 2. Setup/Scaffold if not present
3034
+ if (!fs.existsSync(guiPath)) {
3035
+ console.log(`⏳ Laravel Control Center not installed. Scaffolding in: ${guiPath}`);
3036
+ console.log(` (This may take a minute...)`);
3037
+ try {
3038
+ execSync(`composer create-project laravel/laravel "${guiPath}" --prefer-dist --no-interaction`, { stdio: 'inherit' });
3039
+ console.log('✅ Laravel framework scaffolded.');
3040
+ } catch (e) {
3041
+ console.error('❌ Scaffolding failed:', e.message);
3042
+ process.exit(1);
3043
+ }
3044
+ }
3045
+
3046
+ // 3. Inject customized views, routes, models, migrations and controllers
3047
+ setupLaravelGui(guiPath);
3048
+
3049
+ // 3b. Automatically register the folder from which imhcode gui was executed
3050
+ const currentPath = process.cwd();
3051
+ const folderName = path.basename(currentPath);
3052
+ try {
3053
+ const escapedPath = currentPath.replace(/\\/g, '\\\\');
3054
+ execSync(`php artisan imhcode:register "${folderName}" "${escapedPath}"`, { cwd: guiPath, stdio: 'ignore' });
3055
+ console.log(`✅ Automatically registered project "${folderName}" (${currentPath})`);
3056
+ } catch (e) {
3057
+ // Suppress errors if database is not migrated yet or locked
3058
+ }
3059
+
3060
+ // 4. Serve
3061
+ console.log(`\n🚀 Starting Laravel server on http://localhost:${port}`);
3062
+ if (openBrowser) {
3063
+ setTimeout(() => {
3064
+ try {
3065
+ const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
3066
+ execSync(`${cmd} http://localhost:${port}`);
3067
+ } catch {}
3068
+ }, 2000);
3069
+ }
3070
+
3071
+ try {
3072
+ execSync(`php artisan serve --port=${port}`, { cwd: guiPath, stdio: 'inherit' });
3073
+ } catch (err) {
3074
+ console.error('❌ Server execution failed:', err.message);
3075
+ process.exit(1);
3076
+ }
3077
+ }
3078
+
3079
+ function setupLaravelGui(guiPath) {
3080
+ // Write custom config database.sqlite
3081
+ const dbDir = path.join(guiPath, 'database');
3082
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
3083
+ const dbFile = path.join(dbDir, 'database.sqlite');
3084
+ if (!fs.existsSync(dbFile)) {
3085
+ fs.writeFileSync(dbFile, '');
3086
+ }
3087
+
3088
+ // Edit .env for SQLite
3089
+ const envPath = path.join(guiPath, '.env');
3090
+ if (fs.existsSync(envPath)) {
3091
+ let envContent = fs.readFileSync(envPath, 'utf8');
3092
+ // Remove default DB settings
3093
+ envContent = envContent.replace(/DB_CONNECTION=\w+/g, 'DB_CONNECTION=sqlite');
3094
+ envContent = envContent.replace(/DB_HOST=[^\n]+/g, '');
3095
+ envContent = envContent.replace(/DB_PORT=[^\n]+/g, '');
3096
+ envContent = envContent.replace(/DB_DATABASE=[^\n]+/g, '');
3097
+ envContent = envContent.replace(/DB_USERNAME=[^\n]+/g, '');
3098
+ envContent = envContent.replace(/DB_PASSWORD=[^\n]+/g, '');
3099
+ fs.writeFileSync(envPath, envContent, 'utf8');
3100
+ }
3101
+
3102
+ // Generate Model: Project.php
3103
+ const modelDir = path.join(guiPath, 'app', 'Models');
3104
+ if (!fs.existsSync(modelDir)) fs.mkdirSync(modelDir, { recursive: true });
3105
+ const modelContent = `<?php
3106
+ namespace App\\Models;
3107
+ use Illuminate\\Database\\Eloquent\\Model;
3108
+ class Project extends Model {
3109
+ protected $fillable = ['name', 'path', 'frontend_stack', 'backend_stack', 'database'];
3110
+ }
3111
+ `;
3112
+ fs.writeFileSync(path.join(modelDir, 'Project.php'), modelContent, 'utf8');
3113
+
3114
+ // Generate Controller: DashboardController.php
3115
+ const controllerDir = path.join(guiPath, 'app', 'Http', 'Controllers');
3116
+ if (!fs.existsSync(controllerDir)) fs.mkdirSync(controllerDir, { recursive: true });
3117
+ const controllerContent = `<?php
3118
+ namespace App\\Http\\Controllers;
3119
+
3120
+ use Illuminate\\Http\\Request;
3121
+ use Illuminate\\Support\\Facades\\Process;
3122
+ use Illuminate\\Support\\Facades\\DB;
3123
+
3124
+ class DashboardController extends Controller {
3125
+ public function index() {
3126
+ $projects = DB::table('projects')->get();
3127
+ return view('dashboard', compact('projects'));
3128
+ }
3129
+
3130
+ public function create(Request $request) {
3131
+ $name = $request->input('name');
3132
+ $path = $request->input('path');
3133
+
3134
+ DB::table('projects')->insert([
3135
+ 'name' => $name,
3136
+ 'path' => $path,
3137
+ 'created_at' => now(),
3138
+ 'updated_at' => now(),
3139
+ ]);
3140
+
3141
+ // Run imhcode init
3142
+ Process::path($path)->run('imhcode');
3143
+
3144
+ return redirect()->route('dashboard');
3145
+ }
3146
+
3147
+ public function show($id) {
3148
+ $project = DB::table('projects')->where('id', $id)->first();
3149
+ if (!$project) abort(404);
3150
+
3151
+ // Scan folder for sprints
3152
+ $sprints = [];
3153
+ $docsPath = $project->path . '/docs';
3154
+ if (is_dir($docsPath)) {
3155
+ $files = scandir($docsPath);
3156
+ foreach ($files as $file) {
3157
+ if (preg_match('/^sprint-(\\d+)$/', $file, $matches)) {
3158
+ $sprints[] = [
3159
+ 'num' => $matches[1],
3160
+ 'name' => 'Sprint ' . $matches[1],
3161
+ ];
3162
+ }
3163
+ }
3164
+ }
3165
+
3166
+ // Get logs
3167
+ $logs = [];
3168
+ $sessionsPath = $project->path . '/.imhcode/sessions';
3169
+ if (is_dir($sessionsPath)) {
3170
+ $files = array_diff(scandir($sessionsPath), ['.', '..']);
3171
+ foreach ($files as $file) {
3172
+ $logs[] = $file;
3173
+ }
3174
+ }
3175
+
3176
+ return view('project', compact('project', 'sprints', 'logs'));
3177
+ }
3178
+
3179
+ public function execute($id, Request $request) {
3180
+ $project = DB::table('projects')->where('id', $id)->first();
3181
+ $sprint = $request->input('sprint');
3182
+
3183
+ $result = Process::path($project->path)
3184
+ ->timeout(600)
3185
+ ->run("imhcode execute {$sprint}");
3186
+
3187
+ return back()->with('output', $result->output());
3188
+ }
3189
+
3190
+ public function modify($id, Request $request) {
3191
+ $project = DB::table('projects')->where('id', $id)->first();
3192
+ $desc = $request->input('description');
3193
+
3194
+ $result = Process::path($project->path)
3195
+ ->timeout(600)
3196
+ ->run("imhcode modify \\"{$desc}\\" --live");
3197
+
3198
+ return back()->with('output', $result->output());
3199
+ }
3200
+ }
3201
+ `;
3202
+ fs.writeFileSync(path.join(controllerDir, 'DashboardController.php'), controllerContent, 'utf8');
3203
+
3204
+ // Generate web.php routes
3205
+ const routesDir = path.join(guiPath, 'routes');
3206
+ if (!fs.existsSync(routesDir)) fs.mkdirSync(routesDir, { recursive: true });
3207
+ const routesContent = `<?php
3208
+ use Illuminate\\Support\\Facades\\Route;
3209
+ use App\\Http\\Controllers\\DashboardController;
3210
+
3211
+ Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
3212
+ Route::post('/project/create', [DashboardController::class, 'create'])->name('project.create');
3213
+ Route::get('/project/{id}', [DashboardController::class, 'show'])->name('project.show');
3214
+ Route::post('/project/{id}/execute', [DashboardController::class, 'execute'])->name('project.execute');
3215
+ Route::post('/project/{id}/modify', [DashboardController::class, 'modify'])->name('project.modify');
3216
+ `;
3217
+ fs.writeFileSync(path.join(routesDir, 'web.php'), routesContent, 'utf8');
3218
+
3219
+ // Generate Migration: create_projects_table
3220
+ const migrationsDir = path.join(guiPath, 'database', 'migrations');
3221
+ if (!fs.existsSync(migrationsDir)) fs.mkdirSync(migrationsDir, { recursive: true });
3222
+ const migrationContent = `<?php
3223
+ use Illuminate\\Database\\Migrations\\Migration;
3224
+ use Illuminate\\Database\\Schema\\Blueprint;
3225
+ use Illuminate\\Support\\Facades\\Schema;
3226
+
3227
+ return new class extends Migration {
3228
+ public function up() {
3229
+ if (!Schema::hasTable('projects')) {
3230
+ Schema::create('projects', function (Blueprint $table) {
3231
+ $table->id();
3232
+ $table->string('name');
3233
+ $table->string('path');
3234
+ $table->string('frontend_stack')->nullable();
3235
+ $table->string('backend_stack')->nullable();
3236
+ $table->string('database')->nullable();
3237
+ $table->timestamps();
3238
+ });
3239
+ }
3240
+ }
3241
+ public function down() {
3242
+ Schema::dropIfExists('projects');
3243
+ }
3244
+ };
3245
+ `;
3246
+ fs.writeFileSync(path.join(migrationsDir, '2026_07_05_000000_create_imhcode_projects_table.php'), migrationContent, 'utf8');
3247
+
3248
+ // Generate Views
3249
+ const viewsDir = path.join(guiPath, 'resources', 'views');
3250
+ if (!fs.existsSync(viewsDir)) fs.mkdirSync(viewsDir, { recursive: true });
3251
+
3252
+ const dashboardView = `<!DOCTYPE html>
3253
+ <html lang="en" class="dark">
3254
+ <head>
3255
+ <meta charset="UTF-8">
3256
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3257
+ <title>IMH-Code Control Center</title>
3258
+ <script src="https://cdn.tailwindcss.com"></script>
3259
+ <script>
3260
+ tailwind.config = {
3261
+ darkMode: 'class',
3262
+ theme: {
3263
+ extend: {
3264
+ colors: {
3265
+ dark: {
3266
+ 50: '#f8fafc',
3267
+ 900: '#0f172a',
3268
+ 950: '#020617'
3269
+ }
3270
+ }
3271
+ }
3272
+ }
3273
+ }
3274
+ </script>
3275
+ </head>
3276
+ <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3277
+ <div class="flex">
3278
+ <!-- Sidebar -->
3279
+ <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3280
+ <div class="flex items-center gap-3 mb-8">
3281
+ <span class="text-2xl">🕌</span>
3282
+ <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3283
+ </div>
3284
+ <nav class="space-y-2">
3285
+ <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-indigo-600/10 text-indigo-400 font-medium">
3286
+ Dashboard
3287
+ </a>
3288
+ </nav>
3289
+ </aside>
3290
+
3291
+ <!-- Main Content -->
3292
+ <main class="flex-1 p-10">
3293
+ <header class="flex justify-between items-center mb-10">
3294
+ <div>
3295
+ <h2 class="text-3xl font-bold text-white tracking-tight">Control Center</h2>
3296
+ <p class="text-slate-400 mt-1">Manage and orchestrate multi-agent coding harnesses.</p>
3297
+ </div>
3298
+ </header>
3299
+
3300
+ @if(session('output'))
3301
+ <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3302
+ <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3303
+ <pre class="font-mono text-sm bg-black/50 p-4 rounded-lg overflow-x-auto text-green-400 max-h-96">{{ session('output') }}</pre>
3304
+ </div>
3305
+ @endif
3306
+
3307
+ <!-- Grid -->
3308
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
3309
+ <!-- Project List -->
3310
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6">
3311
+ <h3 class="text-xl font-semibold text-white mb-6">Your Projects</h3>
3312
+ @if($projects->isEmpty())
3313
+ <div class="text-center py-12 text-slate-500">
3314
+ <p>No projects imported yet.</p>
3315
+ </div>
3316
+ @else
3317
+ <div class="space-y-4">
3318
+ @foreach($projects as $p)
3319
+ <div class="p-4 bg-slate-950/40 border border-slate-800/80 rounded-xl flex justify-between items-center hover:border-slate-700 transition">
3320
+ <div>
3321
+ <h4 class="font-medium text-white">{{ $p->name }}</h4>
3322
+ <p class="text-xs text-slate-500 mt-1">{{ $p->path }}</p>
3323
+ </div>
3324
+ <a href="/project/{{ $p->id }}" class="px-4 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-lg text-sm font-medium transition">
3325
+ Open
3326
+ </a>
3327
+ </div>
3328
+ @endforeach
3329
+ </div>
3330
+ @endif
3331
+ </div>
3332
+
3333
+ <!-- Create/Import Form -->
3334
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3335
+ <div>
3336
+ <h3 class="text-xl font-semibold text-white mb-2">Import Project</h3>
3337
+ <p class="text-sm text-slate-400">Initialize or import an existing project directory.</p>
3338
+ </div>
3339
+ <form action="/project/create" method="POST" class="space-y-4">
3340
+ @csrf
3341
+ <div>
3342
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Project Name</label>
3343
+ <input type="text" name="name" required class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200">
3344
+ </div>
3345
+ <div>
3346
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Local Path</label>
3347
+ <input type="text" name="path" required class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200" placeholder="/path/to/project">
3348
+ </div>
3349
+ <button type="submit" class="w-full py-3 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg text-sm transition">
3350
+ Initialize & Open
3351
+ </button>
3352
+ </form>
3353
+ </div>
3354
+ </div>
3355
+ </main>
3356
+ </div>
3357
+ </body>
3358
+ </html>
3359
+ `;
3360
+ fs.writeFileSync(path.join(viewsDir, 'dashboard.blade.php'), dashboardView, 'utf8');
3361
+
3362
+ const projectView = `<!DOCTYPE html>
3363
+ <html lang="en" class="dark">
3364
+ <head>
3365
+ <meta charset="UTF-8">
3366
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3367
+ <title>Project: {{ $project->name }}</title>
3368
+ <script src="https://cdn.tailwindcss.com"></script>
3369
+ <script>
3370
+ tailwind.config = {
3371
+ darkMode: 'class',
3372
+ theme: {
3373
+ extend: {
3374
+ colors: {
3375
+ dark: {
3376
+ 50: '#f8fafc',
3377
+ 900: '#0f172a',
3378
+ 950: '#020617'
3379
+ }
3380
+ }
3381
+ }
3382
+ }
3383
+ }
3384
+ </script>
3385
+ </head>
3386
+ <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3387
+ <div class="flex">
3388
+ <!-- Sidebar -->
3389
+ <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3390
+ <div class="flex items-center gap-3 mb-8">
3391
+ <span class="text-2xl">🕌</span>
3392
+ <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3393
+ </div>
3394
+ <nav class="space-y-2">
3395
+ <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 font-medium hover:bg-slate-800">
3396
+ Dashboard
3397
+ </a>
3398
+ </nav>
3399
+ </aside>
3400
+
3401
+ <!-- Main Content -->
3402
+ <main class="flex-1 p-10">
3403
+ <header class="flex justify-between items-center mb-10">
3404
+ <div>
3405
+ <h2 class="text-3xl font-bold text-white tracking-tight">{{ $project->name }}</h2>
3406
+ <p class="text-slate-400 mt-1">{{ $project->path }}</p>
3407
+ </div>
3408
+ </header>
3409
+
3410
+ @if(session('output'))
3411
+ <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3412
+ <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3413
+ <pre class="font-mono text-sm bg-black/50 p-4 rounded-lg overflow-x-auto text-green-400 max-h-96">{{ session('output') }}</pre>
3414
+ </div>
3415
+ @endif
3416
+
3417
+ <!-- Action Grid -->
3418
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
3419
+ <!-- Sprints Kanban / Executor -->
3420
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 lg:col-span-2 space-y-6">
3421
+ <h3 class="text-xl font-semibold text-white">Sprints and Execution</h3>
3422
+ @if(empty($sprints))
3423
+ <div class="text-center py-12 text-slate-500">
3424
+ <p>No active sprints detected. Use the CLI or modification panel to start development.</p>
3425
+ </div>
3426
+ @else
3427
+ <div class="space-y-4">
3428
+ @foreach($sprints as $s)
3429
+ <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl flex justify-between items-center">
3430
+ <div>
3431
+ <h4 class="font-semibold text-white">{{ $s['name'] }}</h4>
3432
+ </div>
3433
+ <form action="/project/{{ $project->id }}/execute" method="POST">
3434
+ @csrf
3435
+ <input type="hidden" name="sprint" value="{{ $s['num'] }}">
3436
+ <button type="submit" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-sm font-medium transition">
3437
+ Run Sprint
3438
+ </button>
3439
+ </form>
3440
+ </div>
3441
+ @endforeach
3442
+ </div>
3443
+ @endif
3444
+ </div>
3445
+
3446
+ <!-- Quick Modification Panel -->
3447
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3448
+ <div>
3449
+ <h3 class="text-xl font-semibold text-white mb-2">Modify Project</h3>
3450
+ <p class="text-sm text-slate-400">Targeted, in-place codebase modifications.</p>
3451
+ </div>
3452
+ <form action="/project/{{ $project->id }}/modify" method="POST" class="space-y-4">
3453
+ @csrf
3454
+ <div>
3455
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Describe Change</label>
3456
+ <textarea name="description" required rows="4" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-indigo-500 text-slate-200" placeholder="e.g. Add a logout button to the navbar..."></textarea>
3457
+ </div>
3458
+ <button type="submit" class="w-full py-3 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg text-sm transition">
3459
+ Apply Modification
3460
+ </button>
3461
+ </form>
3462
+ </div>
3463
+ </div>
3464
+ </main>
3465
+ </div>
3466
+ </body>
3467
+ </html>
3468
+ `;
3469
+ fs.writeFileSync(path.join(viewsDir, 'project.blade.php'), projectView, 'utf8');
3470
+
3471
+ // Generate Command: RegisterProject.php
3472
+ const consoleDir = path.join(guiPath, 'app', 'Console', 'Commands');
3473
+ if (!fs.existsSync(consoleDir)) fs.mkdirSync(consoleDir, { recursive: true });
3474
+ const commandContent = `<?php
3475
+ namespace App\\Console\\Commands;
3476
+
3477
+ use Illuminate\\Console\\Command;
3478
+ use Illuminate\\Support\\Facades\\DB;
3479
+
3480
+ class RegisterProject extends Command {
3481
+ protected $signature = 'imhcode:register {name} {path}';
3482
+ protected $description = 'Register a project in the dashboard';
3483
+
3484
+ public function handle() {
3485
+ $name = $this->argument('name');
3486
+ $path = $this->argument('path');
3487
+
3488
+ DB::table('projects')->updateOrInsert(
3489
+ ['path' => $path],
3490
+ [
3491
+ 'name' => $name,
3492
+ 'created_at' => now(),
3493
+ 'updated_at' => now()
3494
+ ]
3495
+ );
3496
+ }
3497
+ }
3498
+ `;
3499
+ fs.writeFileSync(path.join(consoleDir, 'RegisterProject.php'), commandContent, 'utf8');
3500
+
3501
+ // Trigger migration
3502
+ try {
3503
+ execSync('php artisan migrate --force', { cwd: guiPath, stdio: 'ignore' });
3504
+ } catch (e) {
3505
+ // Suppress if DB is locked or already migrated
3506
+ }
3507
+ }
3508
+