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
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
@@ -2228,7 +2272,7 @@ function getAssetsSummary(cwd) {
2228
2272
 
2229
2273
  if (stat.isDirectory()) {
2230
2274
  const subfiles = fs.readdirSync(filePath);
2231
- summary.push(`- Directory \`${file}/\` containing ${subfiles.length} items (e.g. ${subfiles.slice(0, 5).map(f => \`\`\${f}\`\`).join(', ')}). This contains shared reference code/implementation for this category.`);
2275
+ summary.push(`- Directory \`${file}/\` containing ${subfiles.length} items (e.g. ${subfiles.slice(0, 5).map(f => "\`" + f + "\`").join(', ')}). This contains shared reference code/implementation for this category.`);
2232
2276
  } else {
2233
2277
  const ext = path.extname(file).toLowerCase();
2234
2278
  if (['.txt', '.md', '.json', '.js', '.ts', '.css', '.html', '.php', '.py'].includes(ext)) {
@@ -2667,3 +2711,757 @@ 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
+ // 4. Serve
3050
+ console.log(`\n🚀 Starting Laravel server on http://localhost:${port}`);
3051
+ if (openBrowser) {
3052
+ setTimeout(() => {
3053
+ try {
3054
+ const cmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
3055
+ execSync(`${cmd} http://localhost:${port}`);
3056
+ } catch {}
3057
+ }, 2000);
3058
+ }
3059
+
3060
+ try {
3061
+ execSync(`php artisan serve --port=${port}`, { cwd: guiPath, stdio: 'inherit' });
3062
+ } catch (err) {
3063
+ console.error('❌ Server execution failed:', err.message);
3064
+ process.exit(1);
3065
+ }
3066
+ }
3067
+
3068
+ function setupLaravelGui(guiPath) {
3069
+ // Write custom config database.sqlite
3070
+ const dbDir = path.join(guiPath, 'database');
3071
+ if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
3072
+ const dbFile = path.join(dbDir, 'database.sqlite');
3073
+ if (!fs.existsSync(dbFile)) {
3074
+ fs.writeFileSync(dbFile, '');
3075
+ }
3076
+
3077
+ // Edit .env for SQLite
3078
+ const envPath = path.join(guiPath, '.env');
3079
+ if (fs.existsSync(envPath)) {
3080
+ let envContent = fs.readFileSync(envPath, 'utf8');
3081
+ // Remove default DB settings
3082
+ envContent = envContent.replace(/DB_CONNECTION=\w+/g, 'DB_CONNECTION=sqlite');
3083
+ envContent = envContent.replace(/DB_HOST=[^\n]+/g, '');
3084
+ envContent = envContent.replace(/DB_PORT=[^\n]+/g, '');
3085
+ envContent = envContent.replace(/DB_DATABASE=[^\n]+/g, '');
3086
+ envContent = envContent.replace(/DB_USERNAME=[^\n]+/g, '');
3087
+ envContent = envContent.replace(/DB_PASSWORD=[^\n]+/g, '');
3088
+ fs.writeFileSync(envPath, envContent, 'utf8');
3089
+ }
3090
+
3091
+ // Generate Model: Project.php
3092
+ const modelDir = path.join(guiPath, 'app', 'Models');
3093
+ if (!fs.existsSync(modelDir)) fs.mkdirSync(modelDir, { recursive: true });
3094
+ const modelContent = `<?php
3095
+ namespace App\\Models;
3096
+ use Illuminate\\Database\\Eloquent\\Model;
3097
+ class Project extends Model {
3098
+ protected $fillable = ['name', 'path', 'frontend_stack', 'backend_stack', 'database'];
3099
+ }
3100
+ `;
3101
+ fs.writeFileSync(path.join(modelDir, 'Project.php'), modelContent, 'utf8');
3102
+
3103
+ // Generate Controller: DashboardController.php
3104
+ const controllerDir = path.join(guiPath, 'app', 'Http', 'Controllers');
3105
+ if (!fs.existsSync(controllerDir)) fs.mkdirSync(controllerDir, { recursive: true });
3106
+ const controllerContent = `<?php
3107
+ namespace App\\Http\\Controllers;
3108
+
3109
+ use Illuminate\\Http\\Request;
3110
+ use Illuminate\\Support\Facades\\Process;
3111
+ use Illuminate\\Support\\Facades\\DB;
3112
+
3113
+ class DashboardController extends Controller {
3114
+ public function index() {
3115
+ $projects = DB::table('projects')->get();
3116
+ return view('dashboard', compact('projects'));
3117
+ }
3118
+
3119
+ public function create(Request $request) {
3120
+ $name = $request->input('name');
3121
+ $path = $request->input('path');
3122
+
3123
+ DB::table('projects')->insert([
3124
+ 'name' => $name,
3125
+ 'path' => $path,
3126
+ 'created_at' => now(),
3127
+ 'updated_at' => now(),
3128
+ ]);
3129
+
3130
+ // Run imhcode init
3131
+ Process::path($path)->run('imhcode');
3132
+
3133
+ return redirect()->route('dashboard');
3134
+ }
3135
+
3136
+ public function show($id) {
3137
+ $project = DB::table('projects')->where('id', $id)->first();
3138
+ if (!$project) abort(404);
3139
+
3140
+ // Scan folder for sprints
3141
+ $sprints = [];
3142
+ $docsPath = $project->path . '/docs';
3143
+ if (is_dir($docsPath)) {
3144
+ $files = scandir($docsPath);
3145
+ foreach ($files as $file) {
3146
+ if (preg_match('/^sprint-(\\d+)$/', $file, $matches)) {
3147
+ $sprints[] = [
3148
+ 'num' => $matches[1],
3149
+ 'name' => 'Sprint ' . $matches[1],
3150
+ ];
3151
+ }
3152
+ }
3153
+ }
3154
+
3155
+ // Get logs
3156
+ $logs = [];
3157
+ $sessionsPath = $project->path . '/.imhcode/sessions';
3158
+ if (is_dir($sessionsPath)) {
3159
+ $files = array_diff(scandir($sessionsPath), ['.', '..']);
3160
+ foreach ($files as $file) {
3161
+ $logs[] = $file;
3162
+ }
3163
+ }
3164
+
3165
+ return view('project', compact('project', 'sprints', 'logs'));
3166
+ }
3167
+
3168
+ public function execute($id, Request $request) {
3169
+ $project = DB::table('projects')->where('id', $id)->first();
3170
+ $sprint = $request->input('sprint');
3171
+
3172
+ $result = Process::path($project->path)
3173
+ ->timeout(600)
3174
+ ->run("imhcode execute {$sprint}");
3175
+
3176
+ return back()->with('output', $result->output());
3177
+ }
3178
+
3179
+ public function modify($id, Request $request) {
3180
+ $project = DB::table('projects')->where('id', $id)->first();
3181
+ $desc = $request->input('description');
3182
+
3183
+ $result = Process::path($project->path)
3184
+ ->timeout(600)
3185
+ ->run("imhcode modify \\"{$desc}\\" --live");
3186
+
3187
+ return back()->with('output', $result->output());
3188
+ }
3189
+ }
3190
+ `;
3191
+ fs.writeFileSync(path.join(controllerDir, 'DashboardController.php'), controllerContent, 'utf8');
3192
+
3193
+ // Generate web.php routes
3194
+ const routesDir = path.join(guiPath, 'routes');
3195
+ if (!fs.existsSync(routesDir)) fs.mkdirSync(routesDir, { recursive: true });
3196
+ const routesContent = `<?php
3197
+ use Illuminate\\Support\\Facades\\Route;
3198
+ use App\\Http\\Controllers\\DashboardController;
3199
+
3200
+ Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
3201
+ Route::post('/project/create', [DashboardController::class, 'create'])->name('project.create');
3202
+ Route::get('/project/{id}', [DashboardController::class, 'show'])->name('project.show');
3203
+ Route::post('/project/{id}/execute', [DashboardController::class, 'execute'])->name('project.execute');
3204
+ Route::post('/project/{id}/modify', [DashboardController::class, 'modify'])->name('project.modify');
3205
+ `;
3206
+ fs.writeFileSync(path.join(routesDir, 'web.php'), routesContent, 'utf8');
3207
+
3208
+ // Generate Migration: create_projects_table
3209
+ const migrationsDir = path.join(guiPath, 'database', 'migrations');
3210
+ if (!fs.existsSync(migrationsDir)) fs.mkdirSync(migrationsDir, { recursive: true });
3211
+ const migrationContent = `<?php
3212
+ use Illuminate\\Database\\Migrations\\Migration;
3213
+ use Illuminate\\Database\\Schema\\Blueprint;
3214
+ use Illuminate\\Support\\Facades\\Schema;
3215
+
3216
+ return new class extends Migration {
3217
+ public function up() {
3218
+ if (!Schema::hasTable('projects')) {
3219
+ Schema::create('projects', function (Blueprint $table) {
3220
+ $table->id();
3221
+ $table->string('name');
3222
+ $table->string('path');
3223
+ $table->string('frontend_stack')->nullable();
3224
+ $table->string('backend_stack')->nullable();
3225
+ $table->string('database')->nullable();
3226
+ $table->timestamps();
3227
+ });
3228
+ }
3229
+ }
3230
+ public function down() {
3231
+ Schema::dropIfExists('projects');
3232
+ }
3233
+ };
3234
+ `;
3235
+ fs.writeFileSync(path.join(migrationsDir, '2026_07_05_000000_create_imhcode_projects_table.php'), migrationContent, 'utf8');
3236
+
3237
+ // Generate Views
3238
+ const viewsDir = path.join(guiPath, 'resources', 'views');
3239
+ if (!fs.existsSync(viewsDir)) fs.mkdirSync(viewsDir, { recursive: true });
3240
+
3241
+ const dashboardView = `<!DOCTYPE html>
3242
+ <html lang="en" class="dark">
3243
+ <head>
3244
+ <meta charset="UTF-8">
3245
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3246
+ <title>IMH-Code Control Center</title>
3247
+ <script src="https://cdn.tailwindcss.com"></script>
3248
+ <script>
3249
+ tailwind.config = {
3250
+ darkMode: 'class',
3251
+ theme: {
3252
+ extend: {
3253
+ colors: {
3254
+ dark: {
3255
+ 50: '#f8fafc',
3256
+ 900: '#0f172a',
3257
+ 950: '#020617'
3258
+ }
3259
+ }
3260
+ }
3261
+ }
3262
+ }
3263
+ </script>
3264
+ </head>
3265
+ <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3266
+ <div class="flex">
3267
+ <!-- Sidebar -->
3268
+ <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3269
+ <div class="flex items-center gap-3 mb-8">
3270
+ <span class="text-2xl">🕌</span>
3271
+ <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3272
+ </div>
3273
+ <nav class="space-y-2">
3274
+ <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg bg-indigo-600/10 text-indigo-400 font-medium">
3275
+ Dashboard
3276
+ </a>
3277
+ </nav>
3278
+ </aside>
3279
+
3280
+ <!-- Main Content -->
3281
+ <main class="flex-1 p-10">
3282
+ <header class="flex justify-between items-center mb-10">
3283
+ <div>
3284
+ <h2 class="text-3xl font-bold text-white tracking-tight">Control Center</h2>
3285
+ <p class="text-slate-400 mt-1">Manage and orchestrate multi-agent coding harnesses.</p>
3286
+ </div>
3287
+ </header>
3288
+
3289
+ @if(session('output'))
3290
+ <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3291
+ <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3292
+ <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>
3293
+ </div>
3294
+ @endif
3295
+
3296
+ <!-- Grid -->
3297
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
3298
+ <!-- Project List -->
3299
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6">
3300
+ <h3 class="text-xl font-semibold text-white mb-6">Your Projects</h3>
3301
+ @if($projects->isEmpty())
3302
+ <div class="text-center py-12 text-slate-500">
3303
+ <p>No projects imported yet.</p>
3304
+ </div>
3305
+ @else
3306
+ <div class="space-y-4">
3307
+ @foreach($projects as $p)
3308
+ <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">
3309
+ <div>
3310
+ <h4 class="font-medium text-white">{{ $p->name }}</h4>
3311
+ <p class="text-xs text-slate-500 mt-1">{{ $p->path }}</p>
3312
+ </div>
3313
+ <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">
3314
+ Open
3315
+ </a>
3316
+ </div>
3317
+ @endforeach
3318
+ </div>
3319
+ @endif
3320
+ </div>
3321
+
3322
+ <!-- Create/Import Form -->
3323
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3324
+ <div>
3325
+ <h3 class="text-xl font-semibold text-white mb-2">Import Project</h3>
3326
+ <p class="text-sm text-slate-400">Initialize or import an existing project directory.</p>
3327
+ </div>
3328
+ <form action="/project/create" method="POST" class="space-y-4">
3329
+ @csrf
3330
+ <div>
3331
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Project Name</label>
3332
+ <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">
3333
+ </div>
3334
+ <div>
3335
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Local Path</label>
3336
+ <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">
3337
+ </div>
3338
+ <button type="submit" class="w-full py-3 bg-indigo-600 hover:bg-indigo-500 text-white font-medium rounded-lg text-sm transition">
3339
+ Initialize & Open
3340
+ </button>
3341
+ </form>
3342
+ </div>
3343
+ </div>
3344
+ </main>
3345
+ </div>
3346
+ </body>
3347
+ </html>
3348
+ `;
3349
+ fs.writeFileSync(path.join(viewsDir, 'dashboard.blade.php'), dashboardView, 'utf8');
3350
+
3351
+ const projectView = `<!DOCTYPE html>
3352
+ <html lang="en" class="dark">
3353
+ <head>
3354
+ <meta charset="UTF-8">
3355
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
3356
+ <title>Project: {{ $project->name }}</title>
3357
+ <script src="https://cdn.tailwindcss.com"></script>
3358
+ <script>
3359
+ tailwind.config = {
3360
+ darkMode: 'class',
3361
+ theme: {
3362
+ extend: {
3363
+ colors: {
3364
+ dark: {
3365
+ 50: '#f8fafc',
3366
+ 900: '#0f172a',
3367
+ 950: '#020617'
3368
+ }
3369
+ }
3370
+ }
3371
+ }
3372
+ }
3373
+ </script>
3374
+ </head>
3375
+ <body class="bg-dark-950 text-slate-200 min-h-screen font-sans">
3376
+ <div class="flex">
3377
+ <!-- Sidebar -->
3378
+ <aside class="w-64 bg-dark-900 border-r border-slate-800 min-h-screen p-6">
3379
+ <div class="flex items-center gap-3 mb-8">
3380
+ <span class="text-2xl">🕌</span>
3381
+ <h1 class="text-xl font-bold tracking-tight text-white">IMH-Code</h1>
3382
+ </div>
3383
+ <nav class="space-y-2">
3384
+ <a href="/" class="flex items-center gap-3 px-4 py-2.5 rounded-lg text-slate-400 font-medium hover:bg-slate-800">
3385
+ Dashboard
3386
+ </a>
3387
+ </nav>
3388
+ </aside>
3389
+
3390
+ <!-- Main Content -->
3391
+ <main class="flex-1 p-10">
3392
+ <header class="flex justify-between items-center mb-10">
3393
+ <div>
3394
+ <h2 class="text-3xl font-bold text-white tracking-tight">{{ $project->name }}</h2>
3395
+ <p class="text-slate-400 mt-1">{{ $project->path }}</p>
3396
+ </div>
3397
+ </header>
3398
+
3399
+ @if(session('output'))
3400
+ <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-xl">
3401
+ <h3 class="text-lg font-semibold text-white mb-3">Execution Console Output</h3>
3402
+ <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>
3403
+ </div>
3404
+ @endif
3405
+
3406
+ <!-- Action Grid -->
3407
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
3408
+ <!-- Sprints Kanban / Executor -->
3409
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 lg:col-span-2 space-y-6">
3410
+ <h3 class="text-xl font-semibold text-white">Sprints and Execution</h3>
3411
+ @if(empty($sprints))
3412
+ <div class="text-center py-12 text-slate-500">
3413
+ <p>No active sprints detected. Use the CLI or modification panel to start development.</p>
3414
+ </div>
3415
+ @else
3416
+ <div class="space-y-4">
3417
+ @foreach($sprints as $s)
3418
+ <div class="p-4 bg-slate-950/40 border border-slate-800 rounded-xl flex justify-between items-center">
3419
+ <div>
3420
+ <h4 class="font-semibold text-white">{{ $s['name'] }}</h4>
3421
+ </div>
3422
+ <form action="/project/{{ $project->id }}/execute" method="POST">
3423
+ @csrf
3424
+ <input type="hidden" name="sprint" value="{{ $s['num'] }}">
3425
+ <button type="submit" class="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg text-sm font-medium transition">
3426
+ Run Sprint
3427
+ </button>
3428
+ </form>
3429
+ </div>
3430
+ @endforeach
3431
+ </div>
3432
+ @endif
3433
+ </div>
3434
+
3435
+ <!-- Quick Modification Panel -->
3436
+ <div class="bg-dark-900 border border-slate-800 rounded-2xl p-6 space-y-6">
3437
+ <div>
3438
+ <h3 class="text-xl font-semibold text-white mb-2">Modify Project</h3>
3439
+ <p class="text-sm text-slate-400">Targeted, in-place codebase modifications.</p>
3440
+ </div>
3441
+ <form action="/project/{{ $project->id }}/modify" method="POST" class="space-y-4">
3442
+ @csrf
3443
+ <div>
3444
+ <label class="block text-xs uppercase tracking-wider text-slate-400 font-semibold mb-2">Describe Change</label>
3445
+ <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>
3446
+ </div>
3447
+ <button type="submit" class="w-full py-3 bg-emerald-600 hover:bg-emerald-500 text-white font-medium rounded-lg text-sm transition">
3448
+ Apply Modification
3449
+ </button>
3450
+ </form>
3451
+ </div>
3452
+ </div>
3453
+ </main>
3454
+ </div>
3455
+ </body>
3456
+ </html>
3457
+ `;
3458
+ fs.writeFileSync(path.join(viewsDir, 'project.blade.php'), projectView, 'utf8');
3459
+
3460
+ // Trigger migration
3461
+ try {
3462
+ execSync('php artisan migrate --force', { cwd: guiPath, stdio: 'ignore' });
3463
+ } catch (e) {
3464
+ // Suppress if DB is locked or already migrated
3465
+ }
3466
+ }
3467
+