imhcode 1.1.1 → 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.
- package/README.md +18 -0
- package/USER_MANUAL.md +29 -0
- package/bin/imhcode.js +798 -0
- package/dist/orchestrator/builder.d.ts.map +1 -1
- package/dist/orchestrator/builder.js +27 -4
- package/dist/orchestrator/builder.js.map +1 -1
- package/dist/orchestrator/context-scanner.d.ts +23 -0
- package/dist/orchestrator/context-scanner.d.ts.map +1 -0
- package/dist/orchestrator/context-scanner.js +248 -0
- package/dist/orchestrator/context-scanner.js.map +1 -0
- package/dist/orchestrator/import-engine.d.ts +18 -0
- package/dist/orchestrator/import-engine.d.ts.map +1 -0
- package/dist/orchestrator/import-engine.js +116 -0
- package/dist/orchestrator/import-engine.js.map +1 -0
- package/dist/orchestrator/index.d.ts +4 -0
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/index.js +13 -1
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/modification-engine.d.ts +12 -0
- package/dist/orchestrator/modification-engine.d.ts.map +1 -0
- package/dist/orchestrator/modification-engine.js +109 -0
- package/dist/orchestrator/modification-engine.js.map +1 -0
- package/dist/orchestrator/project-scanner.d.ts +16 -0
- package/dist/orchestrator/project-scanner.d.ts.map +1 -0
- package/dist/orchestrator/project-scanner.js +164 -0
- package/dist/orchestrator/project-scanner.js.map +1 -0
- package/package.json +1 -1
- package/skills/typeui-main/test/orchestrator.test.ts +88 -41
- package/src/orchestrator/builder.ts +29 -4
- package/src/orchestrator/context-scanner.ts +221 -0
- package/src/orchestrator/import-engine.ts +106 -0
- package/src/orchestrator/index.ts +8 -0
- package/src/orchestrator/modification-engine.ts +108 -0
- 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,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
|
+
|