imhcode 1.0.0 → 1.0.2
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/bin/imhcode.js +1256 -511
- package/package.json +1 -2
package/bin/imhcode.js
CHANGED
|
@@ -5,23 +5,24 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
7
|
* imhcode → Initialize framework in current directory
|
|
8
|
-
* imhcode plan → Smart: brainstorm OR sprint-plan based on state
|
|
8
|
+
* imhcode plan → Smart: brainstorm (LLM) OR sprint-plan (LLM) based on state
|
|
9
9
|
* imhcode execute [N] → Execute sprint N with intelligent model routing
|
|
10
10
|
* imhcode test → Run the testing/security/SEO sprint
|
|
11
|
+
* imhcode report → Generate PROJECT_REPORT.md for the team
|
|
11
12
|
* imhcode agent list → List all discovered agents
|
|
12
13
|
* imhcode agent inspect <id> → Show full manifest + loaded skills
|
|
13
14
|
* imhcode agent run <id> "<task>" → Run agent (dry-run by default)
|
|
14
15
|
* --engine <cli> → Override engine (claude|opencode|codex|agy|qwen|mimo)
|
|
15
|
-
* -m, --model <model>
|
|
16
|
-
* --live
|
|
17
|
-
* --output <path>
|
|
16
|
+
* -m, --model <model> → Override model
|
|
17
|
+
* --live → Call real LLM
|
|
18
|
+
* --output <path> → Custom session output directory
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
'use strict';
|
|
21
22
|
|
|
22
23
|
const fs = require('fs');
|
|
23
24
|
const path = require('path');
|
|
24
|
-
const { execSync } = require('child_process');
|
|
25
|
+
const { execSync, spawnSync } = require('child_process');
|
|
25
26
|
const os = require('os');
|
|
26
27
|
const readline = require('readline');
|
|
27
28
|
|
|
@@ -71,6 +72,11 @@ if (command === 'plan') {
|
|
|
71
72
|
console.error(err.message ?? err);
|
|
72
73
|
process.exit(1);
|
|
73
74
|
});
|
|
75
|
+
} else if (command === 'report') {
|
|
76
|
+
runReportCommand(args.slice(1)).catch(err => {
|
|
77
|
+
console.error(err.message ?? err);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
});
|
|
74
80
|
} else if (command === 'agent') {
|
|
75
81
|
runAgentCommand(subcommand, args.slice(2)).catch(err => {
|
|
76
82
|
console.error(err.message ?? err);
|
|
@@ -100,9 +106,10 @@ function printGeneralHelp() {
|
|
|
100
106
|
|
|
101
107
|
Usage:
|
|
102
108
|
${CLI_CMD} → Initialize project (engine scan + model routing setup)
|
|
103
|
-
${CLI_CMD} plan → Smart: brainstorm OR generate sprint plan
|
|
109
|
+
${CLI_CMD} plan → Smart: brainstorm (LLM) OR generate sprint plan (LLM)
|
|
104
110
|
${CLI_CMD} execute [N] → Execute sprint N with intelligent model routing
|
|
105
111
|
${CLI_CMD} test → Run final testing/security/SEO sprint
|
|
112
|
+
${CLI_CMD} report → Generate PROJECT_REPORT.md for the team
|
|
106
113
|
${CLI_CMD} agent list → List all 19 generic agents
|
|
107
114
|
${CLI_CMD} agent inspect <id> → Show agent manifest + loaded skills
|
|
108
115
|
${CLI_CMD} agent run <id> "<task>" → Run agent (dry-run by default)
|
|
@@ -112,58 +119,53 @@ function printGeneralHelp() {
|
|
|
112
119
|
--output <path> → Save session to custom path
|
|
113
120
|
|
|
114
121
|
Pipeline:
|
|
115
|
-
1. ${CLI_CMD} → Init project
|
|
116
|
-
2. Edit docs/start.md →
|
|
117
|
-
3. ${CLI_CMD} plan → Generates docs/brainstorming.md
|
|
122
|
+
1. ${CLI_CMD} → Init project (no frontend/backend dirs yet)
|
|
123
|
+
2. Edit docs/start.md → Answer scope questions + write your description
|
|
124
|
+
3. ${CLI_CMD} plan → Generates docs/brainstorming.md (via planning LLM)
|
|
118
125
|
4. Edit docs/brainstorming.md → Review/edit recommended answers
|
|
119
|
-
5. ${CLI_CMD} plan → Reads answers → generates sprint plans
|
|
120
|
-
6. ${CLI_CMD} execute 1 → Execute sprint 1 (
|
|
126
|
+
5. ${CLI_CMD} plan → Reads answers → generates sprint plans (via planning LLM)
|
|
127
|
+
6. ${CLI_CMD} execute 1 → Execute sprint 1 (each task uses its routed model)
|
|
121
128
|
7. ${CLI_CMD} execute 2 → Execute sprint 2
|
|
122
129
|
8. ${CLI_CMD} test → Final testing, security + SEO audit
|
|
130
|
+
9. ${CLI_CMD} report → Generate PROJECT_REPORT.md
|
|
123
131
|
|
|
124
132
|
Agents (19 generic — no persona names):
|
|
125
|
-
planner → Project planning, brainstorming, sprint coordination
|
|
126
|
-
designer → UX/UI design
|
|
127
|
-
nextjs-executor → Next.js full-stack
|
|
128
|
-
react-executor → React/Vite SPA
|
|
129
|
-
vue-executor → Vue 3 / Nuxt 4
|
|
130
|
-
laravel-executor → Laravel full-stack
|
|
131
|
-
python-executor → Python / Django / FastAPI
|
|
132
|
-
java-executor → Java / Spring Boot
|
|
133
|
-
flutter-executor → Flutter / Dart
|
|
134
|
-
react-native-executor → React Native / Expo
|
|
135
|
-
ios-executor → iOS / SwiftUI
|
|
136
|
-
android-executor → Android / Kotlin
|
|
137
|
-
systems-executor → Go / Rust / C++
|
|
138
|
-
web3-executor → Solidity / Web3
|
|
139
|
-
devops-executor → Docker / CI/CD
|
|
140
|
-
tester → QA + E2E testing
|
|
141
|
-
security-auditor → Security audit
|
|
142
|
-
seo-optimizer → SEO / Content
|
|
143
|
-
debugger → Debugging / Root cause
|
|
133
|
+
planner → Project planning, brainstorming, sprint coordination [planning model]
|
|
134
|
+
designer → UX/UI design [frontend model — Mimo v2.5 Pro preferred]
|
|
135
|
+
nextjs-executor → Next.js full-stack [frontend model — Mimo v2.5 Pro preferred]
|
|
136
|
+
react-executor → React/Vite SPA [frontend model]
|
|
137
|
+
vue-executor → Vue 3 / Nuxt 4 [frontend model]
|
|
138
|
+
laravel-executor → Laravel full-stack [backend model — DeepSeek V4 Pro preferred]
|
|
139
|
+
python-executor → Python / Django / FastAPI [backend model]
|
|
140
|
+
java-executor → Java / Spring Boot [backend model]
|
|
141
|
+
flutter-executor → Flutter / Dart [backend model]
|
|
142
|
+
react-native-executor → React Native / Expo [backend model]
|
|
143
|
+
ios-executor → iOS / SwiftUI [backend model]
|
|
144
|
+
android-executor → Android / Kotlin [backend model]
|
|
145
|
+
systems-executor → Go / Rust / C++ [backend model]
|
|
146
|
+
web3-executor → Solidity / Web3 [backend model]
|
|
147
|
+
devops-executor → Docker / CI/CD [backend model]
|
|
148
|
+
tester → QA + E2E testing [testing model — GPT-5.5 preferred]
|
|
149
|
+
security-auditor → Security audit [testing model — GPT-5.5 preferred]
|
|
150
|
+
seo-optimizer → SEO / Content [review model — GPT-5.5 preferred]
|
|
151
|
+
debugger → Debugging / Root cause [review model]
|
|
144
152
|
|
|
145
153
|
${CLI_CMD} --version, -v → Print version
|
|
146
154
|
${CLI_CMD} --help, -h → Print this help
|
|
147
155
|
`);
|
|
148
156
|
}
|
|
149
157
|
|
|
150
|
-
// ─── imhcode plan (Smart State Machine)
|
|
158
|
+
// ─── imhcode plan (Smart State Machine + LLM-Powered) ─────────────────────────
|
|
151
159
|
|
|
152
160
|
async function runPlanCommand(restArgs) {
|
|
153
161
|
const cwd = process.cwd();
|
|
154
|
-
const startMdPath
|
|
162
|
+
const startMdPath = path.join(cwd, START_MD);
|
|
155
163
|
const brainstormPath = path.join(cwd, BRAINSTORM_MD);
|
|
156
|
-
const
|
|
164
|
+
const hasSprintDocs = detectSprintDocs(cwd);
|
|
157
165
|
|
|
158
166
|
console.log(`\n🕌 ${PLATFORM_NAME} — Plan Command\n`);
|
|
159
167
|
|
|
160
|
-
|
|
161
|
-
const hasStartMd = fs.existsSync(startMdPath);
|
|
162
|
-
const hasBrainstorm = fs.existsSync(brainstormPath);
|
|
163
|
-
const hasBrief = fs.existsSync(briefPath);
|
|
164
|
-
const hasSprintDocs = detectSprintDocs(cwd);
|
|
165
|
-
|
|
166
|
-
if (!hasStartMd) {
|
|
168
|
+
if (!fs.existsSync(startMdPath)) {
|
|
167
169
|
console.log(`❌ docs/start.md not found.\n`);
|
|
168
170
|
console.log(` Create docs/start.md and write your project description inside it.`);
|
|
169
171
|
console.log(` Then run "imhcode plan" again.\n`);
|
|
@@ -171,9 +173,9 @@ async function runPlanCommand(restArgs) {
|
|
|
171
173
|
process.exit(1);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
// Read the start.md prompt
|
|
175
176
|
const startContent = fs.readFileSync(startMdPath, 'utf8');
|
|
176
|
-
const userPrompt
|
|
177
|
+
const userPrompt = extractPromptFromStartMd(startContent);
|
|
178
|
+
const scopeHints = extractScopeHints(startContent);
|
|
177
179
|
|
|
178
180
|
if (!userPrompt || userPrompt.trim().length < 10) {
|
|
179
181
|
console.log(`❌ docs/start.md has no project description.\n`);
|
|
@@ -184,31 +186,44 @@ async function runPlanCommand(restArgs) {
|
|
|
184
186
|
process.exit(1);
|
|
185
187
|
}
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
console.log(
|
|
189
|
+
const config = loadLocalConfig(cwd);
|
|
190
|
+
|
|
191
|
+
// ── State 1: No brainstorming yet → generate brainstorming.md via LLM ──────
|
|
192
|
+
if (!fs.existsSync(brainstormPath)) {
|
|
193
|
+
console.log(`📋 Phase 2: Generating brainstorming document via planning AI...\n`);
|
|
194
|
+
console.log(` Project: "${userPrompt.slice(0, 100)}${userPrompt.length > 100 ? '...' : ''}"`);
|
|
195
|
+
|
|
196
|
+
const planningRouting = config?.model_routing?.planning;
|
|
197
|
+
if (planningRouting) {
|
|
198
|
+
console.log(` Model: ${planningRouting.model} via ${planningRouting.engine}`);
|
|
199
|
+
} else {
|
|
200
|
+
console.log(` Model: (fallback static template — run imhcode to configure model routing)`);
|
|
201
|
+
}
|
|
202
|
+
console.log('');
|
|
192
203
|
|
|
193
|
-
await generateBrainstormingDoc(cwd, userPrompt);
|
|
204
|
+
await generateBrainstormingDoc(cwd, userPrompt, scopeHints, config);
|
|
194
205
|
|
|
195
206
|
console.log(`\n✅ Brainstorming document created: docs/brainstorming.md\n`);
|
|
196
207
|
console.log(`─`.repeat(60));
|
|
197
208
|
console.log(`\n📝 NEXT STEPS:\n`);
|
|
198
209
|
console.log(` 1. Open docs/brainstorming.md`);
|
|
199
|
-
console.log(` 2. Review the
|
|
210
|
+
console.log(` 2. Review the AI-recommended answers`);
|
|
200
211
|
console.log(` 3. Edit any answers you want to customize`);
|
|
201
212
|
console.log(` 4. Run "imhcode plan" again to generate sprint plans\n`);
|
|
202
213
|
console.log(`─`.repeat(60));
|
|
203
214
|
return;
|
|
204
215
|
}
|
|
205
216
|
|
|
206
|
-
// ── State 2: Brainstorming exists, no sprint plans → generate sprints
|
|
207
|
-
if (
|
|
208
|
-
console.log(`📋 Phase 3-4: Reading brainstorming answers → generating sprint plans...\n`);
|
|
217
|
+
// ── State 2: Brainstorming exists, no sprint plans → generate sprints via LLM
|
|
218
|
+
if (!hasSprintDocs) {
|
|
219
|
+
console.log(`📋 Phase 3-4: Reading brainstorming answers → generating sprint plans via AI...\n`);
|
|
209
220
|
|
|
210
221
|
const brainstormContent = fs.readFileSync(brainstormPath, 'utf8');
|
|
211
|
-
const
|
|
222
|
+
const planningRouting = config?.model_routing?.planning;
|
|
223
|
+
if (planningRouting) {
|
|
224
|
+
console.log(` Model: ${planningRouting.model} via ${planningRouting.engine}`);
|
|
225
|
+
}
|
|
226
|
+
console.log('');
|
|
212
227
|
|
|
213
228
|
await generateSprintPlans(cwd, userPrompt, brainstormContent, config);
|
|
214
229
|
|
|
@@ -218,27 +233,26 @@ async function runPlanCommand(restArgs) {
|
|
|
218
233
|
console.log(` 1. Review docs/sprint-1/plan.md`);
|
|
219
234
|
console.log(` 2. Run "imhcode execute 1" to start Sprint 1`);
|
|
220
235
|
console.log(` 3. Then "imhcode execute 2", "imhcode execute 3", etc.`);
|
|
221
|
-
console.log(` 4. Run "imhcode test" for the final testing sprint
|
|
236
|
+
console.log(` 4. Run "imhcode test" for the final testing sprint`);
|
|
237
|
+
console.log(` 5. Run "imhcode report" to generate the team report\n`);
|
|
222
238
|
console.log(`─`.repeat(60));
|
|
223
239
|
return;
|
|
224
240
|
}
|
|
225
241
|
|
|
226
242
|
// ── State 3: Sprints already exist ───────────────────────────────────────
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.log(` To re-plan from scratch, delete docs/brainstorming.md and run "imhcode plan".\n`);
|
|
233
|
-
}
|
|
243
|
+
const currentSprint = detectCurrentSprint(cwd);
|
|
244
|
+
console.log(`ℹ️ Sprint plans already exist (current sprint: Sprint ${currentSprint}).\n`);
|
|
245
|
+
console.log(` Use "imhcode execute ${currentSprint}" to run the current sprint.`);
|
|
246
|
+
console.log(` Use "imhcode test" to run the final testing sprint.\n`);
|
|
247
|
+
console.log(` To re-plan from scratch, delete docs/brainstorming.md and run "imhcode plan".\n`);
|
|
234
248
|
}
|
|
235
249
|
|
|
236
250
|
// ─── imhcode execute [N] ──────────────────────────────────────────────────────
|
|
237
251
|
|
|
238
252
|
async function runExecuteCommand(restArgs) {
|
|
239
253
|
const cwd = process.cwd();
|
|
240
|
-
const sprintNum
|
|
241
|
-
const sprintDir
|
|
254
|
+
const sprintNum = resolveSprintNum(restArgs, cwd);
|
|
255
|
+
const sprintDir = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`);
|
|
242
256
|
const masterScript = path.join(sprintDir, 'run_all_tasks.sh');
|
|
243
257
|
const progressPath = path.join(sprintDir, 'progress.md');
|
|
244
258
|
|
|
@@ -259,17 +273,17 @@ async function runExecuteCommand(restArgs) {
|
|
|
259
273
|
// Read plan to show what will be executed
|
|
260
274
|
const planPath = path.join(sprintDir, 'plan.md');
|
|
261
275
|
if (fs.existsSync(planPath)) {
|
|
262
|
-
const plan
|
|
276
|
+
const plan = fs.readFileSync(planPath, 'utf8');
|
|
263
277
|
const taskCount = (plan.match(/^##\s+Task/gm) || []).length;
|
|
264
278
|
console.log(` Sprint: ${sprintNum}`);
|
|
265
279
|
console.log(` Tasks: ${taskCount}`);
|
|
266
280
|
console.log(` Script: ${masterScript}\n`);
|
|
267
281
|
}
|
|
268
282
|
|
|
269
|
-
//
|
|
283
|
+
// Show active model routing
|
|
270
284
|
const config = loadLocalConfig(cwd);
|
|
271
285
|
if (config?.model_routing) {
|
|
272
|
-
console.log(` Model Routing:`);
|
|
286
|
+
console.log(` Model Routing (active):`);
|
|
273
287
|
for (const [cat, routing] of Object.entries(config.model_routing)) {
|
|
274
288
|
if (routing && typeof routing === 'object') {
|
|
275
289
|
console.log(` ${cat.padEnd(12)}: ${routing.model} (${routing.engine})`);
|
|
@@ -278,17 +292,13 @@ async function runExecuteCommand(restArgs) {
|
|
|
278
292
|
console.log('');
|
|
279
293
|
}
|
|
280
294
|
|
|
281
|
-
try {
|
|
282
|
-
fs.chmodSync(masterScript, 0o755);
|
|
283
|
-
} catch { /* ignore on Windows */ }
|
|
295
|
+
try { fs.chmodSync(masterScript, 0o755); } catch { /* ignore on Windows */ }
|
|
284
296
|
|
|
285
297
|
console.log(`─`.repeat(60));
|
|
286
298
|
try {
|
|
287
299
|
execSync(`sh "${masterScript}"`, { stdio: 'inherit', cwd });
|
|
288
300
|
console.log(`─`.repeat(60));
|
|
289
301
|
console.log(`\n🏁 Sprint ${sprintNum} execution complete!\n`);
|
|
290
|
-
|
|
291
|
-
// Update compact context after sprint
|
|
292
302
|
await updateCompactContext(cwd, sprintNum);
|
|
293
303
|
} catch (err) {
|
|
294
304
|
console.log(`─`.repeat(60));
|
|
@@ -301,48 +311,235 @@ async function runExecuteCommand(restArgs) {
|
|
|
301
311
|
// ─── imhcode test ─────────────────────────────────────────────────────────────
|
|
302
312
|
|
|
303
313
|
async function runTestCommand(restArgs) {
|
|
304
|
-
const cwd
|
|
305
|
-
const lastSprint
|
|
306
|
-
const
|
|
307
|
-
const testSprintDir
|
|
308
|
-
const testScript
|
|
314
|
+
const cwd = process.cwd();
|
|
315
|
+
const lastSprint = detectLastSprint(cwd);
|
|
316
|
+
const testSprintNum = lastSprint + 1;
|
|
317
|
+
const testSprintDir = path.join(cwd, DOCS_DIR, `sprint-${testSprintNum}`);
|
|
318
|
+
const testScript = path.join(testSprintDir, 'run_all_tasks.sh');
|
|
309
319
|
|
|
310
320
|
console.log(`\n🕌 ${PLATFORM_NAME} — Testing Sprint\n`);
|
|
311
321
|
|
|
312
322
|
if (fs.existsSync(testScript)) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
fs.chmodSync(testScript, 0o755);
|
|
317
|
-
} catch { /* ignore */ }
|
|
323
|
+
console.log(` Running auto-generated testing sprint (Sprint ${testSprintNum})...\n`);
|
|
324
|
+
try { fs.chmodSync(testScript, 0o755); } catch { /* ignore */ }
|
|
318
325
|
console.log(`─`.repeat(60));
|
|
319
326
|
try {
|
|
320
327
|
execSync(`sh "${testScript}"`, { stdio: 'inherit', cwd });
|
|
321
328
|
console.log(`─`.repeat(60));
|
|
322
|
-
console.log(`\n✅ Testing sprint complete! Check docs/sprint-${
|
|
329
|
+
console.log(`\n✅ Testing sprint complete! Check docs/sprint-${testSprintNum}/ for reports.\n`);
|
|
323
330
|
} catch {
|
|
324
331
|
console.error(`\n❌ Testing sprint failed. Check output above.\n`);
|
|
325
332
|
process.exit(1);
|
|
326
333
|
}
|
|
327
334
|
} else {
|
|
328
|
-
// Guide user to generate the test sprint
|
|
329
335
|
console.log(` ℹ️ No testing sprint found yet.\n`);
|
|
330
|
-
console.log(` The testing sprint is auto-generated when you run "imhcode plan"
|
|
331
|
-
console.log(` select "Fast Mode" or "Balanced Mode" for testing strategy.\n`);
|
|
336
|
+
console.log(` The testing sprint is auto-generated when you run "imhcode plan".\n`);
|
|
332
337
|
console.log(` To generate it now, run:\n`);
|
|
333
|
-
console.log(` imhcode agent run planner "Generate testing sprint for Sprint ${
|
|
338
|
+
console.log(` imhcode agent run planner "Generate testing sprint for Sprint ${testSprintNum}" --live\n`);
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
341
|
|
|
342
|
+
// ─── imhcode report ───────────────────────────────────────────────────────────
|
|
343
|
+
|
|
344
|
+
async function runReportCommand(restArgs) {
|
|
345
|
+
const cwd = process.cwd();
|
|
346
|
+
const reportPath = path.join(cwd, 'PROJECT_REPORT.md');
|
|
347
|
+
const config = loadLocalConfig(cwd);
|
|
348
|
+
const lastSprint = detectLastSprint(cwd);
|
|
349
|
+
|
|
350
|
+
console.log(`\n🕌 ${PLATFORM_NAME} — Generating Project Report\n`);
|
|
351
|
+
|
|
352
|
+
// Gather all project data
|
|
353
|
+
const briefContent = safeRead(path.join(cwd, BRIEF_MD));
|
|
354
|
+
const brainstormContent = safeRead(path.join(cwd, BRAINSTORM_MD));
|
|
355
|
+
const startContent = safeRead(path.join(cwd, START_MD));
|
|
356
|
+
const contextContent = safeRead(path.join(cwd, CONTEXT_MD));
|
|
357
|
+
|
|
358
|
+
const userPrompt = extractPromptFromStartMd(startContent || '');
|
|
359
|
+
|
|
360
|
+
// Gather sprint data
|
|
361
|
+
const sprintData = [];
|
|
362
|
+
for (let i = 1; i <= lastSprint + 1; i++) {
|
|
363
|
+
const sprintDir = path.join(cwd, DOCS_DIR, `sprint-${i}`);
|
|
364
|
+
const planMd = safeRead(path.join(sprintDir, 'plan.md'));
|
|
365
|
+
const progressMd = safeRead(path.join(sprintDir, 'progress.md'));
|
|
366
|
+
const deferredMd = safeRead(path.join(sprintDir, 'deferred.md'));
|
|
367
|
+
if (planMd || progressMd) {
|
|
368
|
+
sprintData.push({ sprintNum: i, plan: planMd, progress: progressMd, deferred: deferredMd });
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Build model routing summary
|
|
373
|
+
let modelRoutingSummary = '';
|
|
374
|
+
if (config?.model_routing) {
|
|
375
|
+
const rows = Object.entries(config.model_routing).map(([cat, r]) =>
|
|
376
|
+
`| ${cat.padEnd(10)} | ${(r?.engine || '?').padEnd(12)} | ${r?.model || '?'} |`
|
|
377
|
+
).join('\n');
|
|
378
|
+
modelRoutingSummary = `| Category | Engine | Model |\n|------------|--------------|-------|\n${rows}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Build sprint log table
|
|
382
|
+
const sprintLogTable = sprintData.map(s => {
|
|
383
|
+
const titleMatch = s.plan?.match(/^# Sprint \d+: (.+)$/m);
|
|
384
|
+
const title = titleMatch ? titleMatch[1] : `Sprint ${s.sprintNum}`;
|
|
385
|
+
const tasksDone = (s.progress?.match(/- \[x\]/g) || []).length;
|
|
386
|
+
const tasksTotal = (s.progress?.match(/- \[/g) || []).length;
|
|
387
|
+
const status = tasksTotal > 0 && tasksDone === tasksTotal ? '✅ Complete' :
|
|
388
|
+
tasksDone > 0 ? `🔄 In Progress (${tasksDone}/${tasksTotal})` : '⬜ Pending';
|
|
389
|
+
return `| ${s.sprintNum} | ${title} | ${status} |`;
|
|
390
|
+
}).join('\n');
|
|
391
|
+
|
|
392
|
+
// Build agent usage table from sprint plans
|
|
393
|
+
const agentUsage = {};
|
|
394
|
+
for (const s of sprintData) {
|
|
395
|
+
if (!s.plan) continue;
|
|
396
|
+
const agentMatches = s.plan.matchAll(/`([a-z-]+-executor|planner|designer|tester|security-auditor|seo-optimizer|debugger)`/g);
|
|
397
|
+
for (const m of agentMatches) {
|
|
398
|
+
agentUsage[m[1]] = (agentUsage[m[1]] || 0) + 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const agentTable = Object.entries(agentUsage)
|
|
402
|
+
.sort((a, b) => b[1] - a[1])
|
|
403
|
+
.map(([agent, count]) => `| \`${agent}\` | ${getAgentCategory(agent)} | ${count} tasks |`)
|
|
404
|
+
.join('\n');
|
|
405
|
+
|
|
406
|
+
// Build deferred items list
|
|
407
|
+
const deferredItems = sprintData
|
|
408
|
+
.filter(s => s.deferred && !s.deferred.includes('None yet'))
|
|
409
|
+
.map(s => `\n### Sprint ${s.sprintNum} Deferred\n\n${s.deferred}`)
|
|
410
|
+
.join('');
|
|
411
|
+
|
|
412
|
+
// Detect stack from brainstorm/context
|
|
413
|
+
const stack = [];
|
|
414
|
+
if (brainstormContent || contextContent) {
|
|
415
|
+
const combined = (brainstormContent || '') + (contextContent || '');
|
|
416
|
+
if (/next\.?js/i.test(combined)) stack.push('Next.js');
|
|
417
|
+
if (/vue/i.test(combined)) stack.push('Vue 3 / Nuxt 4');
|
|
418
|
+
if (/react\s*native/i.test(combined)) stack.push('React Native');
|
|
419
|
+
if (/flutter/i.test(combined)) stack.push('Flutter');
|
|
420
|
+
if (/laravel/i.test(combined)) stack.push('Laravel');
|
|
421
|
+
if (/django|fastapi/i.test(combined)) stack.push('Python / FastAPI');
|
|
422
|
+
if (/spring\s*boot/i.test(combined)) stack.push('Java / Spring Boot');
|
|
423
|
+
if (/postgres/i.test(combined)) stack.push('PostgreSQL');
|
|
424
|
+
if (/redis/i.test(combined)) stack.push('Redis');
|
|
425
|
+
if (/docker/i.test(combined)) stack.push('Docker');
|
|
426
|
+
if (/tailwind/i.test(combined)) stack.push('Tailwind CSS');
|
|
427
|
+
if (/shadcn/i.test(combined)) stack.push('shadcn/ui');
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const report = `# 📋 IMH-Code — Project Report
|
|
431
|
+
|
|
432
|
+
> **${PLATFORM_FULL}**
|
|
433
|
+
> Generated: ${new Date().toLocaleString()}
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## 🎯 Project Summary
|
|
438
|
+
|
|
439
|
+
${userPrompt || '*(See PROJECT_BRIEF.md for details)*'}
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## 🏗️ Technology Stack
|
|
444
|
+
|
|
445
|
+
${stack.length > 0 ? stack.map(s => `- ${s}`).join('\n') : '*(Stack detected from brainstorming and sprint plans)*'}
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 🤖 AI Model Routing Used
|
|
450
|
+
|
|
451
|
+
${modelRoutingSummary || '*(No imhcode.config.json found)*'}
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## 🏃 Sprint Execution Log
|
|
456
|
+
|
|
457
|
+
| Sprint | Title | Status |
|
|
458
|
+
|--------|-------|--------|
|
|
459
|
+
${sprintLogTable || '| — | No sprints found | — |'}
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
## 🔧 Agent Execution Summary
|
|
464
|
+
|
|
465
|
+
| Agent | Category | Usage |
|
|
466
|
+
|-------|----------|-------|
|
|
467
|
+
${agentTable || '*(No sprint plans found)*'}
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## 🚀 How to Run This Project
|
|
472
|
+
|
|
473
|
+
\`\`\`bash
|
|
474
|
+
# 1. Install dependencies
|
|
475
|
+
${stack.includes('Next.js') || stack.includes('Vue 3 / Nuxt 4') ? 'cd frontend && npm install' : ''}
|
|
476
|
+
${stack.includes('Laravel') ? 'cd backend && composer install && php artisan key:generate' : ''}
|
|
477
|
+
${stack.includes('Python / FastAPI') ? 'cd backend && pip install -r requirements.txt' : ''}
|
|
478
|
+
|
|
479
|
+
# 2. Set environment variables
|
|
480
|
+
cp .env.example .env # Edit with your config
|
|
481
|
+
|
|
482
|
+
# 3. Run development server
|
|
483
|
+
${stack.includes('Next.js') ? 'cd frontend && npm run dev # → http://localhost:3000' : ''}
|
|
484
|
+
${stack.includes('Vue 3 / Nuxt 4') ? 'cd frontend && npm run dev # → http://localhost:3000' : ''}
|
|
485
|
+
${stack.includes('Laravel') ? 'cd backend && php artisan serve # → http://localhost:8000' : ''}
|
|
486
|
+
${stack.includes('Python / FastAPI') ? 'cd backend && uvicorn main:app --reload' : ''}
|
|
487
|
+
\`\`\`
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## 📂 Project Structure
|
|
492
|
+
|
|
493
|
+
\`\`\`
|
|
494
|
+
${path.basename(cwd)}/
|
|
495
|
+
├── docs/ ← Sprint plans & documentation
|
|
496
|
+
│ ├── start.md ← Project description (input)
|
|
497
|
+
│ ├── brainstorming.md ← Brainstorming Q&A
|
|
498
|
+
│ └── sprint-*/ ← Sprint plans, tasks, progress
|
|
499
|
+
├── ${fs.existsSync(path.join(cwd, 'frontend')) ? 'frontend/ ← Frontend application\n├── ' : ''}${fs.existsSync(path.join(cwd, 'backend')) ? 'backend/ ← Backend application\n├── ' : ''}.imhcode/ ← IMH-Code context & sessions
|
|
500
|
+
├── imhcode.config.json ← Engine & model routing config
|
|
501
|
+
└── PROJECT_BRIEF.md ← Central project context
|
|
502
|
+
\`\`\`
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## 📌 Known Issues & Deferred Items
|
|
507
|
+
|
|
508
|
+
${deferredItems || '*(No deferred items logged)*'}
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## 📊 Development Metrics
|
|
513
|
+
|
|
514
|
+
| Metric | Value |
|
|
515
|
+
|--------|-------|
|
|
516
|
+
| Total Sprints | ${sprintData.length} |
|
|
517
|
+
| Agents Used | ${Object.keys(agentUsage).length} |
|
|
518
|
+
| Testing Mode | ${config?.testing_mode || 'fast'} |
|
|
519
|
+
| Primary Engine | ${config?.primary_engine || 'N/A'} |
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
*Report generated by ${PLATFORM_NAME} — ${PLATFORM_FULL}*
|
|
524
|
+
`;
|
|
525
|
+
|
|
526
|
+
fs.writeFileSync(reportPath, report, 'utf8');
|
|
527
|
+
console.log(`✅ PROJECT_REPORT.md created!\n`);
|
|
528
|
+
console.log(` Path: ${reportPath}`);
|
|
529
|
+
console.log(` Sprints covered: ${sprintData.length}`);
|
|
530
|
+
console.log(` Agents referenced: ${Object.keys(agentUsage).length}\n`);
|
|
531
|
+
console.log(` Share this file with your team for a complete project overview.\n`);
|
|
532
|
+
}
|
|
533
|
+
|
|
337
534
|
// ─── imhcode agent ────────────────────────────────────────────────────────────
|
|
338
535
|
|
|
339
536
|
async function runAgentCommand(subcommand, restArgs) {
|
|
340
537
|
const orchestrator = loadOrchestrator();
|
|
341
538
|
|
|
342
539
|
switch (subcommand) {
|
|
343
|
-
case 'list':
|
|
540
|
+
case 'list': return cmdList(orchestrator, restArgs);
|
|
344
541
|
case 'inspect': return cmdInspect(orchestrator, restArgs);
|
|
345
|
-
case 'run':
|
|
542
|
+
case 'run': return cmdRun(orchestrator, restArgs);
|
|
346
543
|
default:
|
|
347
544
|
printAgentHelp();
|
|
348
545
|
process.exit(0);
|
|
@@ -391,49 +588,33 @@ async function cmdList(orc, _args) {
|
|
|
391
588
|
return;
|
|
392
589
|
}
|
|
393
590
|
|
|
591
|
+
const config = loadLocalConfig(cwd);
|
|
592
|
+
|
|
394
593
|
const rows = [...agents.values()].map(a => ({
|
|
395
|
-
id:
|
|
396
|
-
role:
|
|
397
|
-
model: a.manifest.default_model,
|
|
594
|
+
id: a.manifest.id,
|
|
595
|
+
role: a.manifest.role,
|
|
398
596
|
category: getAgentCategory(a.manifest.id),
|
|
597
|
+
model: config?.model_routing?.[getAgentCategory(a.manifest.id)]?.model || a.manifest.default_model,
|
|
598
|
+
engine: config?.model_routing?.[getAgentCategory(a.manifest.id)]?.engine || '?',
|
|
399
599
|
}));
|
|
400
600
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
console.log('├──────────────────────────┼────────────────┼──────────────┼──────────────────┤');
|
|
601
|
+
const colId = Math.max(...rows.map(r => r.id.length), 2);
|
|
602
|
+
const colRole = Math.max(...rows.map(r => r.role.length), 4);
|
|
603
|
+
const colCat = Math.max(...rows.map(r => r.category.length), 8);
|
|
604
|
+
const colEng = Math.max(...rows.map(r => r.engine.length), 6);
|
|
406
605
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const cat = row.category.padEnd(14).slice(0, 14);
|
|
410
|
-
const mdl = row.model.padEnd(12).slice(0, 12);
|
|
411
|
-
const rol = row.role.padEnd(16).slice(0, 16);
|
|
412
|
-
console.log(`│ ${id} │ ${cat} │ ${mdl} │ ${rol} │`);
|
|
413
|
-
}
|
|
606
|
+
console.log(` ${'ID'.padEnd(colId)} ${'Role'.padEnd(colRole)} ${'Category'.padEnd(colCat)} ${'Engine'.padEnd(colEng)} Model`);
|
|
607
|
+
console.log(` ${'─'.repeat(colId)} ${'─'.repeat(colRole)} ${'─'.repeat(colCat)} ${'─'.repeat(colEng)} ${'─'.repeat(32)}`);
|
|
414
608
|
|
|
415
|
-
|
|
416
|
-
|
|
609
|
+
for (const r of rows) {
|
|
610
|
+
console.log(` ${r.id.padEnd(colId)} ${r.role.padEnd(colRole)} ${r.category.padEnd(colCat)} ${r.engine.padEnd(colEng)} ${r.model}`);
|
|
611
|
+
}
|
|
417
612
|
|
|
418
613
|
if (errors.length > 0) {
|
|
419
|
-
console.log(
|
|
420
|
-
|
|
421
|
-
console.log(` ❌ ${agentId}: ${error.split('\n')[0]}\n`);
|
|
422
|
-
}
|
|
614
|
+
console.log(`\n ⚠️ ${errors.length} agent(s) failed to load:`);
|
|
615
|
+
errors.forEach(e => console.log(` • ${e.agentId}: ${e.error}`));
|
|
423
616
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
function getAgentCategory(agentId) {
|
|
427
|
-
const map = {
|
|
428
|
-
'planner': 'planning', 'designer': 'frontend',
|
|
429
|
-
'nextjs-executor': 'frontend', 'react-executor': 'frontend', 'vue-executor': 'frontend',
|
|
430
|
-
'laravel-executor': 'backend', 'python-executor': 'backend', 'java-executor': 'backend',
|
|
431
|
-
'flutter-executor': 'backend', 'react-native-executor': 'backend', 'ios-executor': 'backend',
|
|
432
|
-
'android-executor': 'backend', 'systems-executor': 'backend', 'web3-executor': 'backend',
|
|
433
|
-
'devops-executor': 'backend', 'tester': 'testing', 'security-auditor': 'testing',
|
|
434
|
-
'seo-optimizer': 'review', 'debugger': 'review',
|
|
435
|
-
};
|
|
436
|
-
return map[agentId] || 'backend';
|
|
617
|
+
console.log('');
|
|
437
618
|
}
|
|
438
619
|
|
|
439
620
|
async function cmdInspect(orc, args) {
|
|
@@ -444,7 +625,7 @@ async function cmdInspect(orc, args) {
|
|
|
444
625
|
}
|
|
445
626
|
|
|
446
627
|
const agentsDir = resolveAgentsDir();
|
|
447
|
-
const cwd
|
|
628
|
+
const cwd = process.cwd();
|
|
448
629
|
console.log(`\n🔍 Loading agent: ${agentId}\n`);
|
|
449
630
|
|
|
450
631
|
const { agents, errors } = await orc.loadRegistry(agentsDir, cwd);
|
|
@@ -459,36 +640,47 @@ async function cmdInspect(orc, args) {
|
|
|
459
640
|
const { manifest, systemPrompt, skills, dir } = agent;
|
|
460
641
|
const divider = '─'.repeat(72);
|
|
461
642
|
|
|
462
|
-
|
|
463
|
-
const config = loadLocalConfig(cwd);
|
|
643
|
+
const config = loadLocalConfig(cwd);
|
|
464
644
|
const category = getAgentCategory(manifest.id);
|
|
465
|
-
const routing
|
|
645
|
+
const routing = config?.model_routing?.[category];
|
|
466
646
|
|
|
467
647
|
console.log(divider);
|
|
468
648
|
console.log(` ${manifest.name} (v${manifest.version})`);
|
|
469
649
|
console.log(divider);
|
|
470
|
-
console.log(` ID:
|
|
471
|
-
console.log(` Role:
|
|
472
|
-
console.log(` Category:
|
|
473
|
-
console.log(` Description:
|
|
474
|
-
console.log(` Directory:
|
|
650
|
+
console.log(` ID: ${manifest.id}`);
|
|
651
|
+
console.log(` Role: ${manifest.role}`);
|
|
652
|
+
console.log(` Category: ${category}`);
|
|
653
|
+
console.log(` Description: ${manifest.description.replace(/\n\s*/g, ' ')}`);
|
|
654
|
+
console.log(` Directory: ${dir}`);
|
|
475
655
|
console.log(` Default Model: ${manifest.default_model}`);
|
|
476
656
|
if (routing) {
|
|
477
|
-
console.log(` Routed Model:
|
|
657
|
+
console.log(` Routed Model: ${routing.model} via ${routing.engine} (from imhcode.config.json) ✅`);
|
|
658
|
+
} else {
|
|
659
|
+
console.log(` Routed Model: (none — run imhcode to configure model routing)`);
|
|
478
660
|
}
|
|
479
|
-
console.log(` Fallbacks:
|
|
661
|
+
console.log(` Fallbacks: ${manifest.fallback_engines.join(', ') || '(none)'}`);
|
|
480
662
|
console.log(`\n Core Skills: ${skills.length} loaded`);
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
console.log(`\n${
|
|
485
|
-
console.log(
|
|
486
|
-
|
|
663
|
+
skills.forEach(s => console.log(` • ${s.name} — ${s.description?.slice(0, 80) ?? ''}`));
|
|
664
|
+
|
|
665
|
+
const promptLines = systemPrompt.split('\n').length;
|
|
666
|
+
console.log(`\n System Prompt: ${promptLines} lines\n`);
|
|
667
|
+
console.log(divider + '\n');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function printAgentHelp() {
|
|
671
|
+
console.log(`\n ${PLATFORM_NAME} — Agent Commands\n`);
|
|
672
|
+
console.log(` imhcode agent list → List all agents with routed models`);
|
|
673
|
+
console.log(` imhcode agent inspect <id> → Show agent details + routing`);
|
|
674
|
+
console.log(` imhcode agent run <id> "<task>" → Dry-run (no LLM call)`);
|
|
675
|
+
console.log(` imhcode agent run <id> "<task>" --live → Call real LLM with routed model`);
|
|
676
|
+
console.log(` --engine <cli> → Override engine`);
|
|
677
|
+
console.log(` -m, --model <m> → Override model`);
|
|
678
|
+
console.log(` --output <path> → Save session to custom path\n`);
|
|
487
679
|
}
|
|
488
680
|
|
|
489
681
|
async function cmdRun(orc, args) {
|
|
490
682
|
const agentId = args[0];
|
|
491
|
-
const task
|
|
683
|
+
const task = args[1];
|
|
492
684
|
|
|
493
685
|
if (!agentId || !task) {
|
|
494
686
|
console.error(`❌ Usage: imhcode agent run <agent-id> "<task description>" [options]`);
|
|
@@ -499,23 +691,27 @@ async function cmdRun(orc, args) {
|
|
|
499
691
|
process.exit(1);
|
|
500
692
|
}
|
|
501
693
|
|
|
502
|
-
const restArgs
|
|
503
|
-
const live
|
|
504
|
-
const engineIdx
|
|
505
|
-
const engine
|
|
506
|
-
const mIndex
|
|
507
|
-
const shortMIndex
|
|
508
|
-
const modelIdx
|
|
509
|
-
const model
|
|
510
|
-
const criteriaIdx
|
|
511
|
-
const criteria
|
|
512
|
-
const outputIdx
|
|
513
|
-
const outputDir
|
|
694
|
+
const restArgs = args.slice(2);
|
|
695
|
+
const live = restArgs.includes('--live');
|
|
696
|
+
const engineIdx = restArgs.indexOf('--engine');
|
|
697
|
+
const engine = engineIdx >= 0 ? restArgs[engineIdx + 1] : undefined;
|
|
698
|
+
const mIndex = restArgs.indexOf('--model');
|
|
699
|
+
const shortMIndex = restArgs.indexOf('-m');
|
|
700
|
+
const modelIdx = mIndex >= 0 ? mIndex : shortMIndex;
|
|
701
|
+
const model = modelIdx >= 0 ? restArgs[modelIdx + 1] : undefined;
|
|
702
|
+
const criteriaIdx = restArgs.indexOf('--criteria');
|
|
703
|
+
const criteria = criteriaIdx >= 0 ? restArgs[criteriaIdx + 1] : undefined;
|
|
704
|
+
const outputIdx = restArgs.indexOf('--output');
|
|
705
|
+
const outputDir = outputIdx >= 0 ? restArgs[outputIdx + 1] : undefined;
|
|
514
706
|
|
|
515
707
|
const agentsDir = resolveAgentsDir();
|
|
516
|
-
const cwd
|
|
517
|
-
const config
|
|
518
|
-
const category
|
|
708
|
+
const cwd = process.cwd();
|
|
709
|
+
const config = loadLocalConfig(cwd);
|
|
710
|
+
const category = getAgentCategory(agentId);
|
|
711
|
+
|
|
712
|
+
// Fix 0b Part A: Auto-inject routing when no explicit override provided
|
|
713
|
+
const routedEngine = engine ?? config?.model_routing?.[category]?.engine;
|
|
714
|
+
const routedModel = model ?? config?.model_routing?.[category]?.model;
|
|
519
715
|
|
|
520
716
|
console.log(`\n🕌 ${PLATFORM_NAME} — Agent Execution`);
|
|
521
717
|
console.log(` Agent: ${agentId}`);
|
|
@@ -523,12 +719,10 @@ async function cmdRun(orc, args) {
|
|
|
523
719
|
console.log(` Task: ${task}`);
|
|
524
720
|
console.log(` Mode: ${live ? '🔴 LIVE (CLI execution)' : '🟡 DRY-RUN (no CLI execution)'}`);
|
|
525
721
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
console.log(` Model: ${
|
|
530
|
-
} else if (model) {
|
|
531
|
-
console.log(` Model: ${model} (explicit override)`);
|
|
722
|
+
if (engine || model) {
|
|
723
|
+
console.log(` Model: ${routedModel} via ${routedEngine} (explicit override)`);
|
|
724
|
+
} else if (routedEngine && routedModel) {
|
|
725
|
+
console.log(` Model: ${routedModel} via ${routedEngine} (auto from imhcode.config.json ✅)`);
|
|
532
726
|
}
|
|
533
727
|
console.log('');
|
|
534
728
|
|
|
@@ -540,62 +734,43 @@ async function cmdRun(orc, args) {
|
|
|
540
734
|
process.exit(1);
|
|
541
735
|
}
|
|
542
736
|
|
|
543
|
-
const agent
|
|
544
|
-
|
|
737
|
+
const agent = orc.getAgent(agents, agentId);
|
|
738
|
+
|
|
739
|
+
// Pass the routed engine + model to runAgent (Fix 0b Part A)
|
|
740
|
+
const result = await orc.runAgent(
|
|
741
|
+
agent, task,
|
|
742
|
+
{ dryRun: !live, engine: routedEngine, model: routedModel, outputDir, cwd },
|
|
743
|
+
criteria
|
|
744
|
+
);
|
|
545
745
|
|
|
546
746
|
console.log('\n' + '─'.repeat(72));
|
|
547
747
|
if (result.errors.length > 0 && !result.dryRun) {
|
|
548
748
|
console.error(`\n❌ [IMH-Code] Execution failed with errors:`);
|
|
549
749
|
result.errors.forEach(e => console.error(` • ${e}`));
|
|
550
|
-
|
|
551
|
-
// Check if the error is a limit/failover exhaustion
|
|
750
|
+
|
|
552
751
|
const isLimitExhausted = result.errors.some(e => e.includes('limits') && e.includes('exhausted'));
|
|
553
752
|
if (isLimitExhausted) {
|
|
554
753
|
const currentSprint = detectCurrentSprint(cwd);
|
|
555
754
|
console.error(`\n💡 [IMH-Code] All available assistant engines hit rate/token/quota limits.`);
|
|
556
|
-
console.error(`
|
|
557
|
-
console.error(`
|
|
755
|
+
console.error(` Context saved in: ${result.sessionDir || 'sessions/'}`);
|
|
756
|
+
console.error(` Wait for limits to reset, then resume with:`);
|
|
558
757
|
console.error(` imhcode execute ${currentSprint}\n`);
|
|
559
758
|
}
|
|
560
759
|
process.exit(1);
|
|
561
760
|
}
|
|
562
761
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
Subcommands:
|
|
576
|
-
list List all 19 generic agents
|
|
577
|
-
inspect <id> Show agent manifest, skills, and routing
|
|
578
|
-
run <id> "<task>" [flags] Build prompt and run
|
|
579
|
-
|
|
580
|
-
Flags for "run":
|
|
581
|
-
--live Run live LLM execution
|
|
582
|
-
--engine <cli> Override engine (claude|opencode|codex|agy|qwen|mimo)
|
|
583
|
-
-m, --model <model> Override model
|
|
584
|
-
--criteria "<string>" Acceptance criteria
|
|
585
|
-
--output <path> Custom session directory
|
|
586
|
-
|
|
587
|
-
Agent IDs (short aliases work too):
|
|
588
|
-
planner (alias: plan, pm, orchestrator)
|
|
589
|
-
nextjs-executor (alias: nextjs, next)
|
|
590
|
-
laravel-executor (alias: laravel, php)
|
|
591
|
-
react-executor (alias: react, vite)
|
|
592
|
-
vue-executor (alias: vue, nuxt)
|
|
593
|
-
python-executor (alias: python, django, fastapi)
|
|
594
|
-
tester (alias: qa, test)
|
|
595
|
-
security-auditor (alias: security, audit)
|
|
596
|
-
debugger (alias: debug, fix)
|
|
597
|
-
... and more (run imhcode agent list)
|
|
598
|
-
`);
|
|
762
|
+
if (result.dryRun) {
|
|
763
|
+
console.log(`\n🟡 [DRY-RUN] Prompt built successfully — ${result.prompt.split('\n').length} lines`);
|
|
764
|
+
console.log(` Would execute with: ${routedModel || 'default'} via ${routedEngine || 'default'}`);
|
|
765
|
+
console.log(` Use --live to run with the real LLM.\n`);
|
|
766
|
+
if (result.output) console.log(result.output);
|
|
767
|
+
} else {
|
|
768
|
+
console.log(`\n✅ [IMH-Code] Task executed successfully`);
|
|
769
|
+
console.log(` Model: ${result.model}`);
|
|
770
|
+
console.log(` Duration: ${(result.durationMs / 1000).toFixed(1)}s`);
|
|
771
|
+
if (result.sessionDir) console.log(` Session: ${result.sessionDir}`);
|
|
772
|
+
console.log('');
|
|
773
|
+
}
|
|
599
774
|
}
|
|
600
775
|
|
|
601
776
|
// ─── Legacy Sprint Commands ────────────────────────────────────────────────────
|
|
@@ -612,7 +787,7 @@ async function runSprintLegacyCommand(subcommand, restArgs) {
|
|
|
612
787
|
}
|
|
613
788
|
|
|
614
789
|
async function cmdSprintDesign(restArgs) {
|
|
615
|
-
const cwd
|
|
790
|
+
const cwd = process.cwd();
|
|
616
791
|
const sprintNum = resolveSprintNum(restArgs, cwd);
|
|
617
792
|
const sprintDir = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`);
|
|
618
793
|
|
|
@@ -649,9 +824,9 @@ async function runInit() {
|
|
|
649
824
|
console.log(`\n🕌 Initializing ${PLATFORM_NAME}...\n`);
|
|
650
825
|
console.log(` ${PLATFORM_FULL}\n`);
|
|
651
826
|
|
|
652
|
-
const packageRoot
|
|
653
|
-
const imhcodeScriptPath
|
|
654
|
-
const cwd
|
|
827
|
+
const packageRoot = path.join(__dirname, '..');
|
|
828
|
+
const imhcodeScriptPath = path.join(packageRoot, 'bin', 'imhcode.js');
|
|
829
|
+
const cwd = process.cwd();
|
|
655
830
|
|
|
656
831
|
// Ensure global ~/.imhcode exists
|
|
657
832
|
if (!fs.existsSync(GLOBAL_DIR)) {
|
|
@@ -659,14 +834,14 @@ async function runInit() {
|
|
|
659
834
|
}
|
|
660
835
|
|
|
661
836
|
const itemsToCopy = [
|
|
662
|
-
{ src: '.github',
|
|
663
|
-
{ src: 'AGENTS.md',
|
|
664
|
-
{ src: 'CLAUDE.md',
|
|
665
|
-
{ src: '.gitignore.template', dest: '.gitignore',
|
|
666
|
-
{ src: 'SETUP.md',
|
|
667
|
-
{ src: 'USER_MANUAL.md',
|
|
668
|
-
{ src: 'agents',
|
|
669
|
-
{ src: 'skills',
|
|
837
|
+
{ src: '.github', dest: '.github', desc: 'GitHub integration files' },
|
|
838
|
+
{ src: 'AGENTS.md', dest: 'AGENTS.md', desc: 'Agent manifest' },
|
|
839
|
+
{ src: 'CLAUDE.md', dest: 'CLAUDE.md', desc: 'Claude/Cursor/Copilot integration' },
|
|
840
|
+
{ src: '.gitignore.template', dest: '.gitignore', desc: 'Auto-generated gitignore' },
|
|
841
|
+
{ src: 'SETUP.md', dest: 'SETUP.md', desc: 'Setup guide' },
|
|
842
|
+
{ src: 'USER_MANUAL.md', dest: 'USER_MANUAL.md', desc: 'User manual' },
|
|
843
|
+
{ src: 'agents', dest: 'agents', desc: 'YAML-driven agent manifests' },
|
|
844
|
+
{ src: 'skills', dest: 'skills', desc: 'Skill library (SKILL.md files)' },
|
|
670
845
|
];
|
|
671
846
|
|
|
672
847
|
// Copy to global ~/.imhcode (unconditionally overwrite to apply updates)
|
|
@@ -709,9 +884,11 @@ async function runInit() {
|
|
|
709
884
|
}
|
|
710
885
|
}
|
|
711
886
|
|
|
712
|
-
// Create local
|
|
887
|
+
// Fix 1: Create only ESSENTIAL local directories at init time.
|
|
888
|
+
// frontend/, backend/, .worktrees/ are created LATER in generateSprintPlans()
|
|
889
|
+
// after brainstorming answers confirm what the user actually needs.
|
|
713
890
|
const dirsToCreate = [
|
|
714
|
-
|
|
891
|
+
DOCS_DIR,
|
|
715
892
|
LOCAL_DIR_NAME,
|
|
716
893
|
path.join(LOCAL_DIR_NAME, 'commands'),
|
|
717
894
|
path.join(LOCAL_DIR_NAME, 'sessions'),
|
|
@@ -724,39 +901,52 @@ async function runInit() {
|
|
|
724
901
|
}
|
|
725
902
|
}
|
|
726
903
|
|
|
727
|
-
// Create docs/start.md template
|
|
904
|
+
// Fix 2: Create docs/start.md template with scope questions
|
|
728
905
|
const startMdPath = path.join(cwd, START_MD);
|
|
729
906
|
if (!fs.existsSync(startMdPath)) {
|
|
730
|
-
const startMdContent =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
>
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
907
|
+
const startMdContent = [
|
|
908
|
+
'# 🕌 IMH-Code — Project Start',
|
|
909
|
+
'',
|
|
910
|
+
'> **Imam Hussain Coding Harness Platform**',
|
|
911
|
+
'> Answer the scope questions below, write your description, then run `imhcode plan`.',
|
|
912
|
+
'',
|
|
913
|
+
'---',
|
|
914
|
+
'',
|
|
915
|
+
'## ⚡ Quick Scope Check',
|
|
916
|
+
'',
|
|
917
|
+
'**Do you need a backend API / server-side logic?**',
|
|
918
|
+
'> **Answer:** yes *(change to: yes / no / unsure)*',
|
|
919
|
+
'',
|
|
920
|
+
'**Do you need a mobile app (iOS/Android)?**',
|
|
921
|
+
'> **Answer:** no *(change to: yes / no)*',
|
|
922
|
+
'',
|
|
923
|
+
'---',
|
|
924
|
+
'',
|
|
925
|
+
'## 📝 Your Project Description',
|
|
926
|
+
'',
|
|
927
|
+
'Write your complete project idea below. Be as detailed as possible.',
|
|
928
|
+
'Include: what you\'re building, who it\'s for, key features, preferred stack,',
|
|
929
|
+
'design preferences, integrations needed, business constraints.',
|
|
930
|
+
'',
|
|
931
|
+
'<!-- WRITE_PROMPT_HERE -->',
|
|
932
|
+
'I want to build a SaaS dashboard for managing hotel room bookings with real-time availability,',
|
|
933
|
+
'a Next.js frontend, Laravel API backend, and PostgreSQL database. Target users are hotel managers.',
|
|
934
|
+
'Key features: room calendar, booking CRUD, guest management, reporting, email notifications.',
|
|
935
|
+
'<!-- END_PROMPT -->',
|
|
936
|
+
'',
|
|
937
|
+
'---',
|
|
938
|
+
'',
|
|
939
|
+
'## 🚀 Next Step',
|
|
940
|
+
'',
|
|
941
|
+
'After filling in the scope and your description, run:',
|
|
942
|
+
'',
|
|
943
|
+
'```bash',
|
|
944
|
+
'imhcode plan',
|
|
945
|
+
'```',
|
|
946
|
+
'',
|
|
947
|
+
'IMH-Code will invoke your configured **planning AI model** (e.g. Claude Opus, GPT-5.5)',
|
|
948
|
+
'to generate `docs/brainstorming.md` with smart, project-specific questions and answers.',
|
|
949
|
+
].join('\n');
|
|
760
950
|
fs.writeFileSync(startMdPath, startMdContent, 'utf8');
|
|
761
951
|
console.log(` Created docs/start.md (write your project here)`);
|
|
762
952
|
}
|
|
@@ -776,7 +966,7 @@ smart questions about your project, with recommended answers you can review and
|
|
|
776
966
|
|
|
777
967
|
// ── Interactive Engine & Model Setup ────────────────────────────────────────
|
|
778
968
|
console.log('\n🔍 Scanning for local coding assistant CLIs...');
|
|
779
|
-
const engines
|
|
969
|
+
const engines = scanAssistantCLIs();
|
|
780
970
|
const foundEngines = Object.keys(engines).filter(k => engines[k].path);
|
|
781
971
|
|
|
782
972
|
if (foundEngines.length === 0) {
|
|
@@ -835,22 +1025,22 @@ smart questions about your project, with recommended answers you can review and
|
|
|
835
1025
|
console.log(`✅ Default model: ${defaultModel}`);
|
|
836
1026
|
}
|
|
837
1027
|
|
|
838
|
-
//
|
|
1028
|
+
// Fix 4: Smart Model Routing Setup with ranked scoring
|
|
839
1029
|
const modelRouting = await setupModelRouting(engines, foundEngines, isInteractive);
|
|
840
1030
|
|
|
841
1031
|
// Write config
|
|
842
1032
|
const configPath = path.join(cwd, CONFIG_FILE);
|
|
843
1033
|
const configData = {
|
|
844
1034
|
primary_engine: primaryEngine,
|
|
845
|
-
default_model:
|
|
846
|
-
testing_mode:
|
|
847
|
-
model_routing:
|
|
1035
|
+
default_model: defaultModel || undefined,
|
|
1036
|
+
testing_mode: 'fast',
|
|
1037
|
+
model_routing: modelRouting,
|
|
848
1038
|
available_engines: {},
|
|
849
1039
|
};
|
|
850
1040
|
|
|
851
1041
|
for (const key of foundEngines) {
|
|
852
1042
|
configData.available_engines[key] = {
|
|
853
|
-
path:
|
|
1043
|
+
path: engines[key].path,
|
|
854
1044
|
models: engines[key].models,
|
|
855
1045
|
};
|
|
856
1046
|
}
|
|
@@ -862,100 +1052,163 @@ smart questions about your project, with recommended answers you can review and
|
|
|
862
1052
|
console.log(`\n✅ ${PLATFORM_NAME} initialized successfully!`);
|
|
863
1053
|
console.log(`─`.repeat(60));
|
|
864
1054
|
console.log(`\n🕌 HOW TO BUILD WITH IMH-CODE:\n`);
|
|
865
|
-
console.log(` 1. Open docs/start.md →
|
|
1055
|
+
console.log(` 1. Open docs/start.md → Answer scope questions + write your description`);
|
|
866
1056
|
console.log(` 2. Run: imhcode plan`);
|
|
867
|
-
console.log(` →
|
|
868
|
-
console.log(` 3. Open docs/brainstorming.md → Review/edit answers`);
|
|
1057
|
+
console.log(` → Your planning AI (${modelRouting?.planning?.model || 'configured model'}) generates brainstorming.md`);
|
|
1058
|
+
console.log(` 3. Open docs/brainstorming.md → Review/edit AI-recommended answers`);
|
|
869
1059
|
console.log(` 4. Run: imhcode plan`);
|
|
870
|
-
console.log(` →
|
|
871
|
-
console.log(` 5. Run: imhcode execute 1 →
|
|
872
|
-
console.log(` 6. Run: imhcode execute 2 →
|
|
1060
|
+
console.log(` → Your planning AI generates sprint plans with correct agent routing`);
|
|
1061
|
+
console.log(` 5. Run: imhcode execute 1 → Sprint 1 (frontend tasks → ${modelRouting?.frontend?.model || 'frontend model'})`);
|
|
1062
|
+
console.log(` 6. Run: imhcode execute 2 → Sprint 2 (backend tasks → ${modelRouting?.backend?.model || 'backend model'})`);
|
|
873
1063
|
console.log(` 7. Run: imhcode test → Final testing + security + SEO`);
|
|
1064
|
+
console.log(` 8. Run: imhcode report → Generate PROJECT_REPORT.md`);
|
|
874
1065
|
console.log(`\n Run "imhcode --help" for all commands.`);
|
|
875
1066
|
console.log(`─`.repeat(60));
|
|
876
1067
|
console.log('');
|
|
877
1068
|
}
|
|
878
1069
|
|
|
879
|
-
// ─── Model Routing Setup Wizard
|
|
1070
|
+
// ─── Fix 4: Model Routing Setup Wizard (Ranked Scoring Algorithm) ─────────────
|
|
1071
|
+
|
|
1072
|
+
/**
|
|
1073
|
+
* Ranked priority lists per category.
|
|
1074
|
+
* The algorithm scores each available model against these lists
|
|
1075
|
+
* and selects the highest-ranked match found in the installed engines.
|
|
1076
|
+
*/
|
|
1077
|
+
const MODEL_PRIORITY_RANKS = {
|
|
1078
|
+
frontend: [
|
|
1079
|
+
// Engine Model substring to match (lowercase, no hyphens/dots)
|
|
1080
|
+
['mimo', 'mimov25pro'],
|
|
1081
|
+
['mimo', 'mimov25'],
|
|
1082
|
+
['opencode', 'mimov25pro'],
|
|
1083
|
+
['opencode', 'mimov25'],
|
|
1084
|
+
['codex', 'gpt55'],
|
|
1085
|
+
['codex', 'gpt5'],
|
|
1086
|
+
['agy', 'claudeopus46'],
|
|
1087
|
+
['agy', 'claudeopus'],
|
|
1088
|
+
['opencode', 'gemini35flash'],
|
|
1089
|
+
['opencode', 'gemini25pro'],
|
|
1090
|
+
['claude', 'claudeopus46'],
|
|
1091
|
+
['claude', 'opus'],
|
|
1092
|
+
],
|
|
1093
|
+
backend: [
|
|
1094
|
+
['opencode', 'deepseekv4pro'],
|
|
1095
|
+
['opencode', 'deepseekv4'],
|
|
1096
|
+
['opencode', 'kimik27code'],
|
|
1097
|
+
['opencode', 'kimik2'],
|
|
1098
|
+
['opencode', 'qwen3coder480b'],
|
|
1099
|
+
['opencode', 'qwen3coder'],
|
|
1100
|
+
['codex', 'gpt55'],
|
|
1101
|
+
['codex', 'gpt5'],
|
|
1102
|
+
['opencode', 'gptoss120b'],
|
|
1103
|
+
['qwen', 'qwen3codermax'],
|
|
1104
|
+
['qwen', 'qwen3coder'],
|
|
1105
|
+
['claude', 'claudesonnet46'],
|
|
1106
|
+
['claude', 'sonnet'],
|
|
1107
|
+
],
|
|
1108
|
+
planning: [
|
|
1109
|
+
['agy', 'claudeopus46'],
|
|
1110
|
+
['agy', 'claudeopus'],
|
|
1111
|
+
['claude', 'claudeopus46'],
|
|
1112
|
+
['claude', 'opus'],
|
|
1113
|
+
['codex', 'gpt55'],
|
|
1114
|
+
['codex', 'gpt5'],
|
|
1115
|
+
['opencode', 'gemini31propreview'],
|
|
1116
|
+
['opencode', 'gemini31pro'],
|
|
1117
|
+
['opencode', 'deepseekv4pro'],
|
|
1118
|
+
],
|
|
1119
|
+
testing: [
|
|
1120
|
+
['codex', 'gpt55'],
|
|
1121
|
+
['codex', 'gpt5'],
|
|
1122
|
+
['agy', 'gptoss120bmedium'],
|
|
1123
|
+
['agy', 'gptoss120b'],
|
|
1124
|
+
['agy', 'claudeopus46'],
|
|
1125
|
+
['agy', 'claudeopus'],
|
|
1126
|
+
['opencode', 'deepseekv4pro'],
|
|
1127
|
+
['claude', 'claudeopus46'],
|
|
1128
|
+
],
|
|
1129
|
+
review: [
|
|
1130
|
+
['codex', 'gpt55'],
|
|
1131
|
+
['codex', 'gpt5'],
|
|
1132
|
+
['agy', 'claudesonnet46'],
|
|
1133
|
+
['agy', 'claudesonnet'],
|
|
1134
|
+
['opencode', 'gptoss120b'],
|
|
1135
|
+
['claude', 'claudesonnet46'],
|
|
1136
|
+
['opencode', 'deepseekv4pro'],
|
|
1137
|
+
],
|
|
1138
|
+
fast: [
|
|
1139
|
+
['opencode', 'deepseekv4flash'],
|
|
1140
|
+
['opencode', 'deepseekv4flash'],
|
|
1141
|
+
['opencode', 'gemini35flash'],
|
|
1142
|
+
['codex', 'gpt54mini'],
|
|
1143
|
+
['codex', 'gptmini'],
|
|
1144
|
+
['qwen', 'qwen3coderflash'],
|
|
1145
|
+
['claude', 'claudehaiku35'],
|
|
1146
|
+
['claude', 'haiku'],
|
|
1147
|
+
],
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
/**
|
|
1151
|
+
* Normalize a model/engine string for fuzzy matching:
|
|
1152
|
+
* lowercase, remove hyphens, dots, underscores, spaces.
|
|
1153
|
+
*/
|
|
1154
|
+
function normalizeForMatch(s) {
|
|
1155
|
+
return (s || '').toLowerCase().replace(/[-._\s]/g, '');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Select the best model for a category from available engines using ranked scoring.
|
|
1160
|
+
* Returns { engine, model } or null if nothing found.
|
|
1161
|
+
*/
|
|
1162
|
+
function selectBestModel(category, engines) {
|
|
1163
|
+
const ranks = MODEL_PRIORITY_RANKS[category] || [];
|
|
1164
|
+
for (const [preferredEngine, modelSubstring] of ranks) {
|
|
1165
|
+
const engData = engines[preferredEngine];
|
|
1166
|
+
if (!engData?.path || !engData.models?.length) continue;
|
|
1167
|
+
const match = engData.models.find(m => normalizeForMatch(m).includes(modelSubstring));
|
|
1168
|
+
if (match) return { engine: preferredEngine, model: match };
|
|
1169
|
+
}
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
880
1172
|
|
|
881
1173
|
async function setupModelRouting(engines, foundEngines, isInteractive) {
|
|
882
1174
|
const categories = {
|
|
883
|
-
frontend: {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
},
|
|
889
|
-
backend: {
|
|
890
|
-
label: 'Backend (APIs, database, business logic)',
|
|
891
|
-
note: 'DeepSeek V4 Pro preferred → GPT-5.5 → Qwen Coder',
|
|
892
|
-
preferredEngines: ['opencode', 'codex', 'qwen', 'claude'],
|
|
893
|
-
preferredModels: ['deepseek-v4-pro', 'gpt-5.5', 'qwen3-coder-max', 'claude-sonnet-4-5'],
|
|
894
|
-
},
|
|
895
|
-
planning: {
|
|
896
|
-
label: 'Planning (brainstorming, sprint planning, architecture)',
|
|
897
|
-
note: 'Claude Opus preferred → GPT-5.5',
|
|
898
|
-
preferredEngines: ['claude', 'codex', 'opencode'],
|
|
899
|
-
preferredModels: ['claude-opus-4-6', 'gpt-5.5', 'deepseek-v4-pro'],
|
|
900
|
-
},
|
|
901
|
-
testing: {
|
|
902
|
-
label: 'Testing (QA, security audit, E2E)',
|
|
903
|
-
note: 'GPT-5.5 preferred → Claude Opus → DeepSeek',
|
|
904
|
-
preferredEngines: ['codex', 'claude', 'opencode'],
|
|
905
|
-
preferredModels: ['gpt-5.5', 'claude-opus-4-6', 'deepseek-v4-pro'],
|
|
906
|
-
},
|
|
907
|
-
review: {
|
|
908
|
-
label: 'Review (SEO, debugging, code review)',
|
|
909
|
-
note: 'GPT-5.5 preferred → Claude Sonnet',
|
|
910
|
-
preferredEngines: ['codex', 'claude', 'opencode'],
|
|
911
|
-
preferredModels: ['gpt-5.5', 'claude-sonnet-4-5', 'deepseek-v4'],
|
|
912
|
-
},
|
|
913
|
-
fast: {
|
|
914
|
-
label: 'Fast (boilerplate, config, simple tasks)',
|
|
915
|
-
note: 'DeepSeek V4 Flash preferred → Gemini Flash → GPT Mini',
|
|
916
|
-
preferredEngines: ['opencode', 'codex', 'qwen', 'claude'],
|
|
917
|
-
preferredModels: ['deepseek-v4-flash', 'gpt-5.4-mini', 'qwen3-coder-flash', 'claude-haiku-3-5'],
|
|
918
|
-
},
|
|
1175
|
+
frontend: { label: 'Frontend (UI/UX, components, animations)', note: 'Mimo v2.5 Pro → GPT-5.5 → Claude Opus' },
|
|
1176
|
+
backend: { label: 'Backend (APIs, database, business logic)', note: 'DeepSeek V4 Pro → Kimi K2.7 → Qwen3 Coder → GPT-5.5' },
|
|
1177
|
+
planning: { label: 'Planning (brainstorming, sprint planning)', note: 'Claude Opus 4.6 → GPT-5.5 → Gemini 3.1 Pro' },
|
|
1178
|
+
testing: { label: 'Testing (QA, security audit, E2E)', note: 'GPT-5.5 → GPT-OSS 120B → Claude Opus' },
|
|
1179
|
+
review: { label: 'Review (SEO, debugging, code review)', note: 'GPT-5.5 → Claude Sonnet → GPT-OSS 120B' },
|
|
1180
|
+
fast: { label: 'Fast (boilerplate, config, simple tasks)', note: 'DeepSeek V4 Flash → Gemini 3.5 Flash → GPT Mini' },
|
|
919
1181
|
};
|
|
920
1182
|
|
|
921
|
-
const routing = {};
|
|
922
|
-
|
|
923
|
-
// Build recommended routing from available engines
|
|
924
1183
|
const recommended = {};
|
|
925
|
-
for (const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
if (engData?.path && engData.models.length > 0) {
|
|
932
|
-
const match = engData.models.find(m => m.toLowerCase().includes(mdl.replace(/-/g, '').replace(/\./g, '')));
|
|
933
|
-
recommended[cat] = { engine: eng, model: match || engData.models[0] };
|
|
934
|
-
found = true;
|
|
935
|
-
break;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
if (!found && foundEngines.length > 0) {
|
|
939
|
-
// Fallback to primary engine
|
|
1184
|
+
for (const cat of Object.keys(categories)) {
|
|
1185
|
+
const best = selectBestModel(cat, engines);
|
|
1186
|
+
if (best) {
|
|
1187
|
+
recommended[cat] = best;
|
|
1188
|
+
} else if (foundEngines.length > 0) {
|
|
1189
|
+
// Fallback: primary engine first model
|
|
940
1190
|
const fe = foundEngines[0];
|
|
941
1191
|
recommended[cat] = { engine: fe, model: engines[fe].models[0] || 'default' };
|
|
942
1192
|
}
|
|
943
1193
|
}
|
|
944
1194
|
|
|
945
1195
|
// Show recommended routing table
|
|
946
|
-
console.log('\n' + '─'.repeat(
|
|
947
|
-
console.log('🧠 Recommended Model Routing (
|
|
948
|
-
console.log(' Category │ Engine
|
|
949
|
-
console.log('
|
|
1196
|
+
console.log('\n' + '─'.repeat(70));
|
|
1197
|
+
console.log('🧠 Recommended Model Routing (ranked by quality for each category):\n');
|
|
1198
|
+
console.log(' Category │ Engine │ Model');
|
|
1199
|
+
console.log(' ──────────────┼───────────────┼───────────────────────────────────────');
|
|
950
1200
|
for (const [cat, rec] of Object.entries(recommended)) {
|
|
951
1201
|
const catLabel = cat.padEnd(12);
|
|
952
|
-
const eng
|
|
953
|
-
const mdl
|
|
954
|
-
const
|
|
955
|
-
console.log(` ${catLabel} │ ${eng}
|
|
1202
|
+
const eng = (rec.engine || '?').padEnd(13);
|
|
1203
|
+
const mdl = rec.model || '?';
|
|
1204
|
+
const note = categories[cat]?.note || '';
|
|
1205
|
+
console.log(` ${catLabel} │ ${eng} │ ${mdl}`);
|
|
956
1206
|
}
|
|
957
|
-
console.log(' ' + '─'.repeat(
|
|
958
|
-
console.log(`\n
|
|
1207
|
+
console.log(' ' + '─'.repeat(67));
|
|
1208
|
+
console.log(`\n Priority: Mimo v2.5 Pro (frontend) | DeepSeek V4 Pro (backend)`);
|
|
1209
|
+
console.log(` Claude Opus (planning) | GPT-5.5 (testing/review)`);
|
|
1210
|
+
|
|
1211
|
+
const routing = {};
|
|
959
1212
|
|
|
960
1213
|
if (isInteractive) {
|
|
961
1214
|
const answer = await askQuestion('\nAccept recommended routing? [Y/n] ');
|
|
@@ -963,9 +1216,8 @@ async function setupModelRouting(engines, foundEngines, isInteractive) {
|
|
|
963
1216
|
// Let user customize each category
|
|
964
1217
|
for (const [cat, cfg] of Object.entries(categories)) {
|
|
965
1218
|
console.log(`\n Configure model for [${cat}] — ${cfg.label}`);
|
|
966
|
-
console.log(`
|
|
1219
|
+
console.log(` Priority: ${cfg.note}`);
|
|
967
1220
|
|
|
968
|
-
// Collect all available models across all engines
|
|
969
1221
|
const allModels = [];
|
|
970
1222
|
for (const eng of foundEngines) {
|
|
971
1223
|
for (const m of engines[eng].models) {
|
|
@@ -979,9 +1231,9 @@ async function setupModelRouting(engines, foundEngines, isInteractive) {
|
|
|
979
1231
|
}
|
|
980
1232
|
|
|
981
1233
|
allModels.forEach((item, i) => {
|
|
982
|
-
const rec
|
|
1234
|
+
const rec = recommended[cat];
|
|
983
1235
|
const isRec = rec && rec.engine === item.engine && rec.model === item.model;
|
|
984
|
-
console.log(` [${i + 1}] ${item.model} (${item.engine})${isRec ? ' ← Recommended' : ''}`);
|
|
1236
|
+
console.log(` [${i + 1}] ${item.model} (${item.engine})${isRec ? ' ← Recommended ✅' : ''}`);
|
|
985
1237
|
});
|
|
986
1238
|
|
|
987
1239
|
let selectedIdx = allModels.findIndex(m => {
|
|
@@ -1011,27 +1263,208 @@ async function setupModelRouting(engines, foundEngines, isInteractive) {
|
|
|
1011
1263
|
return routing;
|
|
1012
1264
|
}
|
|
1013
1265
|
|
|
1014
|
-
// ───
|
|
1266
|
+
// ─── Fix 0: LLM-Powered Planning ─────────────────────────────────────────────
|
|
1015
1267
|
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1268
|
+
/**
|
|
1269
|
+
* Invoke the configured planning engine to generate brainstorming or sprint plans.
|
|
1270
|
+
* Uses the orchestrator's runAgent() so failover, logging, and routing all work.
|
|
1271
|
+
*
|
|
1272
|
+
* @param {string} promptText - The full system prompt to send to the planning LLM
|
|
1273
|
+
* @param {object} config - Loaded imhcode.config.json
|
|
1274
|
+
* @param {string} cwd - Current working directory
|
|
1275
|
+
* @returns {string} The LLM output text, or null if unavailable
|
|
1276
|
+
*/
|
|
1277
|
+
async function invokePlanningLLM(promptText, config, cwd) {
|
|
1278
|
+
// Check if orchestrator is built
|
|
1279
|
+
const distPath = path.join(__dirname, '..', 'dist', 'orchestrator', 'index.js');
|
|
1280
|
+
if (!fs.existsSync(distPath)) {
|
|
1281
|
+
console.warn(' ⚠️ Orchestrator not built — falling back to static template.');
|
|
1282
|
+
console.warn(' Run "npm run build" in the imhcode package to enable LLM-powered planning.');
|
|
1283
|
+
return null;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const planningEngine = config?.model_routing?.planning?.engine;
|
|
1287
|
+
const planningModel = config?.model_routing?.planning?.model;
|
|
1288
|
+
|
|
1289
|
+
if (!planningEngine || !planningModel) {
|
|
1290
|
+
console.warn(' ⚠️ No planning model configured — falling back to static template.');
|
|
1291
|
+
console.warn(' Run "imhcode" to set up model routing.');
|
|
1292
|
+
return null;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
try {
|
|
1296
|
+
const orc = require(distPath);
|
|
1297
|
+
const agentsDir = (() => {
|
|
1298
|
+
const g = path.join(GLOBAL_DIR, 'agents');
|
|
1299
|
+
if (fs.existsSync(g)) return g;
|
|
1300
|
+
const l = path.join(cwd, 'agents');
|
|
1301
|
+
if (fs.existsSync(l)) return l;
|
|
1302
|
+
const p = path.join(__dirname, '..', 'agents');
|
|
1303
|
+
if (fs.existsSync(p)) return p;
|
|
1304
|
+
return null;
|
|
1305
|
+
})();
|
|
1306
|
+
|
|
1307
|
+
if (!agentsDir) {
|
|
1308
|
+
console.warn(' ⚠️ No agents directory found — falling back to static template.');
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1019
1311
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
p.includes('next') || p.includes('react') || p.includes('vue');
|
|
1023
|
-
const hasBackend = p.includes('backend') || p.includes('api') || p.includes('database') ||
|
|
1024
|
-
p.includes('server') || p.includes('laravel') || p.includes('django') ||
|
|
1025
|
-
p.includes('fastapi') || p.includes('auth') || p.includes('data');
|
|
1026
|
-
const hasMobile = p.includes('mobile') || p.includes('flutter') || p.includes('ios') ||
|
|
1027
|
-
p.includes('android') || p.includes('react native') || p.includes('expo');
|
|
1312
|
+
const { agents } = await orc.loadRegistry(agentsDir, cwd);
|
|
1313
|
+
const plannerAgent = orc.getAgent(agents, 'planner');
|
|
1028
1314
|
|
|
1029
|
-
|
|
1030
|
-
const defaultFrontend = hasFrontend ? (p.includes('vue') ? 'Vue 3 + Nuxt 4' : 'Next.js 15') : 'Next.js 15';
|
|
1031
|
-
const defaultBackend = hasBackend ? (p.includes('python') || p.includes('django') ? 'Python/FastAPI' :
|
|
1032
|
-
p.includes('java') ? 'Java/Spring Boot' : 'Laravel 11') : 'Laravel 11';
|
|
1315
|
+
console.log(`\n 🤖 Invoking planning AI: ${planningModel} via ${planningEngine}...`);
|
|
1033
1316
|
|
|
1034
|
-
|
|
1317
|
+
const result = await orc.runAgent(
|
|
1318
|
+
plannerAgent,
|
|
1319
|
+
promptText,
|
|
1320
|
+
{
|
|
1321
|
+
dryRun: false,
|
|
1322
|
+
engine: planningEngine,
|
|
1323
|
+
model: planningModel,
|
|
1324
|
+
outputDir: path.join(cwd, LOCAL_DIR_NAME, 'sessions'),
|
|
1325
|
+
cwd,
|
|
1326
|
+
}
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
if (result.errors.length > 0) {
|
|
1330
|
+
console.warn(` ⚠️ Planning LLM returned errors — falling back to static template.`);
|
|
1331
|
+
result.errors.forEach(e => console.warn(` ${e}`));
|
|
1332
|
+
return null;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
return result.output || null;
|
|
1336
|
+
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
console.warn(` ⚠️ Planning LLM invocation failed: ${err.message}`);
|
|
1339
|
+
console.warn(' Falling back to static template.');
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// ─── Fix 0 + Fix 3: Brainstorming Generator (LLM-Powered + Professional) ──────
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* Extract scope hints from start.md (backend? mobile? worktrees?)
|
|
1348
|
+
*/
|
|
1349
|
+
function extractScopeHints(startContent) {
|
|
1350
|
+
const lines = startContent.toLowerCase();
|
|
1351
|
+
|
|
1352
|
+
const needsBackend = (() => {
|
|
1353
|
+
const m = startContent.match(/do you need a backend.*?\n>\s*\*\*Answer:\*\*\s*(.+)/i);
|
|
1354
|
+
if (m) {
|
|
1355
|
+
const ans = m[1].toLowerCase().trim();
|
|
1356
|
+
return !ans.startsWith('no');
|
|
1357
|
+
}
|
|
1358
|
+
// Infer from description
|
|
1359
|
+
return /backend|api|database|server|laravel|django|fastapi|express|spring/i.test(lines);
|
|
1360
|
+
})();
|
|
1361
|
+
|
|
1362
|
+
const needsMobile = (() => {
|
|
1363
|
+
const m = startContent.match(/do you need a mobile.*?\n>\s*\*\*Answer:\*\*\s*(.+)/i);
|
|
1364
|
+
if (m) {
|
|
1365
|
+
const ans = m[1].toLowerCase().trim();
|
|
1366
|
+
return ans.startsWith('yes');
|
|
1367
|
+
}
|
|
1368
|
+
return /mobile|flutter|ios|android|react native|expo/i.test(lines);
|
|
1369
|
+
})();
|
|
1370
|
+
|
|
1371
|
+
return { needsBackend, needsMobile };
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
async function generateBrainstormingDoc(cwd, userPrompt, scopeHints, config) {
|
|
1375
|
+
const { needsBackend, needsMobile } = scopeHints || {};
|
|
1376
|
+
|
|
1377
|
+
// Build the LLM prompt for brainstorming
|
|
1378
|
+
const llmPrompt = `You are the IMH-Code Planner — an expert software architect and technical product manager.
|
|
1379
|
+
|
|
1380
|
+
Your task is to analyze the following project description and generate a comprehensive, professional brainstorming document in Markdown format.
|
|
1381
|
+
|
|
1382
|
+
## Project Description
|
|
1383
|
+
|
|
1384
|
+
${userPrompt}
|
|
1385
|
+
|
|
1386
|
+
## Scope Hints
|
|
1387
|
+
- Backend required: ${needsBackend ? 'YES' : 'NO'}
|
|
1388
|
+
- Mobile required: ${needsMobile ? 'YES' : 'NO'}
|
|
1389
|
+
|
|
1390
|
+
## Instructions
|
|
1391
|
+
|
|
1392
|
+
Generate 25-35 smart, project-specific brainstorming questions with intelligent recommended answers. The questions must be:
|
|
1393
|
+
1. **Tailored to this exact project** — not generic
|
|
1394
|
+
2. **Professional and thorough** — cover all aspects a senior engineer would consider
|
|
1395
|
+
3. **Formatted exactly** as shown below
|
|
1396
|
+
|
|
1397
|
+
### Required Sections (include all that apply based on scope):
|
|
1398
|
+
|
|
1399
|
+
1. **General Requirements** — goals, users, scale, timeline, success metrics
|
|
1400
|
+
2. **Frontend** (ALWAYS include if web app) — must include:
|
|
1401
|
+
- Framework choice (Next.js / Vue 3+Nuxt / React+Vite / SvelteKit)
|
|
1402
|
+
- UI component library (shadcn/ui / Radix / Chakra / Mantine / custom)
|
|
1403
|
+
- **Color palette style** (Vibrant SaaS / Dark Premium / Minimal Neutral / Pastel Soft / Corporate Blue / Bold Brand)
|
|
1404
|
+
- **Design aesthetic** (Glassmorphism / Neumorphism / Brutalism / Claymorphism / Minimalist Clean / Modern SaaS)
|
|
1405
|
+
- **Typography preference** (Modern sans-serif like Inter / Serif editorial / Mono-accented / System fonts)
|
|
1406
|
+
- **Animation preference** (GSAP ScrollTrigger / Framer Motion / CSS transitions / None)
|
|
1407
|
+
- Dark mode strategy (system/toggle/always dark/light only)
|
|
1408
|
+
- Mobile responsive approach (mobile-first / desktop-first / equal)
|
|
1409
|
+
- **Git worktree snapshots** per sprint? (yes / no — creates .worktrees/ for branch snapshots)
|
|
1410
|
+
- Accessibility level (WCAG AA / basic / none)
|
|
1411
|
+
3. **Backend** (only if needsBackend=YES) — framework, database, auth, API style, real-time, payments, file uploads, rate limiting
|
|
1412
|
+
4. **Mobile** (only if needsMobile=YES) — platform, framework, offline support, push notifications
|
|
1413
|
+
5. **Deployment** — platform, Docker, CI/CD, CDN
|
|
1414
|
+
6. **Testing Strategy** — Fast/Balanced/Strict TDD, testing timing
|
|
1415
|
+
7. **Security** — audit timing, OWASP, compliance needs
|
|
1416
|
+
8. **SEO** (if web) — requirements, timing
|
|
1417
|
+
|
|
1418
|
+
### CRITICAL Format — each question MUST follow this exact format:
|
|
1419
|
+
|
|
1420
|
+
**Q[N]: [Question text]?**
|
|
1421
|
+
> **Recommended:** [Smart, specific recommendation based on the project]
|
|
1422
|
+
> **Your Answer:** *(edit if needed)*
|
|
1423
|
+
|
|
1424
|
+
### Output
|
|
1425
|
+
|
|
1426
|
+
Output ONLY the Markdown content. Start with:
|
|
1427
|
+
# 🧠 IMH-Code — Project Brainstorming
|
|
1428
|
+
|
|
1429
|
+
Do NOT include any preamble, explanation, or commentary outside the Markdown document.`;
|
|
1430
|
+
|
|
1431
|
+
// Try LLM first (Fix 0)
|
|
1432
|
+
const llmOutput = await invokePlanningLLM(llmPrompt, config, cwd);
|
|
1433
|
+
|
|
1434
|
+
let content;
|
|
1435
|
+
if (llmOutput && llmOutput.trim().length > 200) {
|
|
1436
|
+
// Ensure it starts correctly
|
|
1437
|
+
content = llmOutput.trim();
|
|
1438
|
+
if (!content.startsWith('#')) {
|
|
1439
|
+
content = `# 🧠 IMH-Code — Project Brainstorming\n\n` + content;
|
|
1440
|
+
}
|
|
1441
|
+
content += `\n\n---\n\n*Generated by ${PLATFORM_NAME} planning AI — ${new Date().toLocaleDateString()}*\n`;
|
|
1442
|
+
content += `\n*When ready, run \`imhcode plan\` to generate sprint plans from your answers.*\n`;
|
|
1443
|
+
console.log(`\n ✅ Brainstorming document generated by planning AI (${config?.model_routing?.planning?.model || 'LLM'})`);
|
|
1444
|
+
} else {
|
|
1445
|
+
// Fix 3: Professional static fallback with design questions
|
|
1446
|
+
content = generateStaticBrainstorming(userPrompt, scopeHints);
|
|
1447
|
+
console.log(`\n 📋 Brainstorming document generated (static template)`);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const brainstormPath = path.join(cwd, BRAINSTORM_MD);
|
|
1451
|
+
fs.mkdirSync(path.join(cwd, DOCS_DIR), { recursive: true });
|
|
1452
|
+
fs.writeFileSync(brainstormPath, content, 'utf8');
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* Fix 3: Professional static brainstorming fallback (used when LLM unavailable)
|
|
1457
|
+
*/
|
|
1458
|
+
function generateStaticBrainstorming(userPrompt, scopeHints) {
|
|
1459
|
+
const { needsBackend, needsMobile } = scopeHints || {};
|
|
1460
|
+
const p = userPrompt.toLowerCase();
|
|
1461
|
+
|
|
1462
|
+
const hasFrontend = true; // Always assume frontend
|
|
1463
|
+
const defaultFrontend = p.includes('vue') ? 'Vue 3 + Nuxt 4' : p.includes('react') && !p.includes('next') ? 'React + Vite' : 'Next.js 15';
|
|
1464
|
+
const defaultBackend = p.includes('python') || p.includes('django') ? 'Python / FastAPI' :
|
|
1465
|
+
p.includes('java') ? 'Java / Spring Boot' : 'Laravel 11';
|
|
1466
|
+
|
|
1467
|
+
return `# 🧠 IMH-Code — Project Brainstorming
|
|
1035
1468
|
|
|
1036
1469
|
> Auto-generated by \`imhcode plan\` on ${new Date().toLocaleDateString()}
|
|
1037
1470
|
> Review and edit the "**Your Answer**" fields below, then run \`imhcode plan\` again.
|
|
@@ -1052,96 +1485,129 @@ async function generateBrainstormingDoc(cwd, userPrompt) {
|
|
|
1052
1485
|
> **Recommended:** Small-medium (< 1,000 users initially)
|
|
1053
1486
|
> **Your Answer:** *(edit if needed)*
|
|
1054
1487
|
|
|
1055
|
-
**Q4: What is the timeline?**
|
|
1488
|
+
**Q4: What is the timeline goal?**
|
|
1056
1489
|
> **Recommended:** MVP in 2-4 sprints (4-8 weeks)
|
|
1057
1490
|
> **Your Answer:** *(edit if needed)*
|
|
1058
1491
|
|
|
1492
|
+
**Q5: What are the key success metrics?**
|
|
1493
|
+
> **Recommended:** User signups, feature completion, performance benchmarks
|
|
1494
|
+
> **Your Answer:** *(edit if needed)*
|
|
1495
|
+
|
|
1059
1496
|
---
|
|
1060
|
-
|
|
1497
|
+
|
|
1061
1498
|
## 🎨 Frontend
|
|
1062
1499
|
|
|
1063
|
-
**
|
|
1500
|
+
**Q6: Which frontend framework?**
|
|
1064
1501
|
> **Recommended:** ${defaultFrontend}
|
|
1065
1502
|
> **Your Answer:** *(edit if needed)*
|
|
1066
1503
|
|
|
1067
|
-
**
|
|
1504
|
+
**Q7: UI component library?**
|
|
1068
1505
|
> **Recommended:** shadcn/ui + Tailwind CSS v4
|
|
1069
|
-
> **Your Answer:** *(
|
|
1506
|
+
> **Your Answer:** *(shadcn/ui / Radix / Chakra / Mantine / custom)*
|
|
1070
1507
|
|
|
1071
|
-
**
|
|
1072
|
-
> **Recommended:**
|
|
1073
|
-
> **Your Answer:** *(
|
|
1508
|
+
**Q8: Color palette style?**
|
|
1509
|
+
> **Recommended:** Dark Premium (deep backgrounds with vibrant accents)
|
|
1510
|
+
> **Your Answer:** *(Vibrant SaaS / Dark Premium / Minimal Neutral / Pastel Soft / Corporate Blue / Bold Brand)*
|
|
1074
1511
|
|
|
1075
|
-
**
|
|
1076
|
-
> **Recommended:**
|
|
1077
|
-
> **Your Answer:** *(
|
|
1512
|
+
**Q9: Design aesthetic / style?**
|
|
1513
|
+
> **Recommended:** Modern SaaS (glassmorphism elements, clean cards, subtle gradients)
|
|
1514
|
+
> **Your Answer:** *(Glassmorphism / Neumorphism / Brutalism / Claymorphism / Minimalist Clean / Modern SaaS)*
|
|
1515
|
+
|
|
1516
|
+
**Q10: Typography preference?**
|
|
1517
|
+
> **Recommended:** Modern sans-serif (Inter + Outfit)
|
|
1518
|
+
> **Your Answer:** *(Modern sans-serif / Serif editorial / Mono-accented / System fonts)*
|
|
1519
|
+
|
|
1520
|
+
**Q11: Animation & interaction style?**
|
|
1521
|
+
> **Recommended:** Subtle micro-animations (Framer Motion for components, CSS transitions)
|
|
1522
|
+
> **Your Answer:** *(GSAP ScrollTrigger / Framer Motion / CSS transitions / None)*
|
|
1078
1523
|
|
|
1079
|
-
**
|
|
1080
|
-
> **Recommended:**
|
|
1524
|
+
**Q12: Dark mode strategy?**
|
|
1525
|
+
> **Recommended:** System preference + manual toggle
|
|
1526
|
+
> **Your Answer:** *(system toggle / always dark / light only)*
|
|
1527
|
+
|
|
1528
|
+
**Q13: Mobile responsive approach?**
|
|
1529
|
+
> **Recommended:** Mobile-first responsive design
|
|
1530
|
+
> **Your Answer:** *(mobile-first / desktop-first / equal)*
|
|
1531
|
+
|
|
1532
|
+
**Q14: Git worktree snapshots per sprint?**
|
|
1533
|
+
> **Recommended:** Yes (creates .worktrees/ for branch snapshots at each sprint)
|
|
1081
1534
|
> **Your Answer:** *(yes / no)*
|
|
1082
1535
|
|
|
1083
|
-
**
|
|
1084
|
-
> **Recommended:**
|
|
1085
|
-
> **Your Answer:** *(
|
|
1086
|
-
|
|
1087
|
-
|
|
1536
|
+
**Q15: Accessibility level?**
|
|
1537
|
+
> **Recommended:** WCAG 2.1 AA
|
|
1538
|
+
> **Your Answer:** *(WCAG AA / basic / none)*
|
|
1539
|
+
|
|
1540
|
+
---
|
|
1541
|
+
${needsBackend ? `
|
|
1088
1542
|
## 🔧 Backend
|
|
1089
1543
|
|
|
1090
|
-
**
|
|
1544
|
+
**Q16: Which backend framework?**
|
|
1091
1545
|
> **Recommended:** ${defaultBackend}
|
|
1092
1546
|
> **Your Answer:** *(edit if needed)*
|
|
1093
1547
|
|
|
1094
|
-
**
|
|
1548
|
+
**Q17: Database?**
|
|
1095
1549
|
> **Recommended:** PostgreSQL + Redis (caching)
|
|
1096
1550
|
> **Your Answer:** *(edit if needed)*
|
|
1097
1551
|
|
|
1098
|
-
**
|
|
1552
|
+
**Q18: Authentication system?**
|
|
1099
1553
|
> **Recommended:** JWT tokens + email verification
|
|
1100
1554
|
> **Your Answer:** *(JWT / session / OAuth / other)*
|
|
1101
1555
|
|
|
1102
|
-
**
|
|
1103
|
-
> **Recommended:** RESTful API
|
|
1556
|
+
**Q19: API architecture?**
|
|
1557
|
+
> **Recommended:** RESTful API with versioning (/api/v1)
|
|
1104
1558
|
> **Your Answer:** *(REST / GraphQL / both)*
|
|
1105
1559
|
|
|
1106
|
-
**
|
|
1560
|
+
**Q20: File uploads?**
|
|
1107
1561
|
> **Recommended:** Yes (S3-compatible storage)
|
|
1108
1562
|
> **Your Answer:** *(yes / no)*
|
|
1109
1563
|
|
|
1110
|
-
**
|
|
1564
|
+
**Q21: Real-time features?**
|
|
1111
1565
|
> **Recommended:** No (for MVP)
|
|
1112
1566
|
> **Your Answer:** *(yes — specify / no)*
|
|
1113
1567
|
|
|
1114
|
-
**
|
|
1568
|
+
**Q22: Payment integration?**
|
|
1115
1569
|
> **Recommended:** No (for MVP)
|
|
1116
1570
|
> **Your Answer:** *(yes — specify provider / no)*
|
|
1571
|
+
|
|
1572
|
+
**Q23: Rate limiting & API security?**
|
|
1573
|
+
> **Recommended:** Yes (throttle 60 req/min per IP, API key auth for external)
|
|
1574
|
+
> **Your Answer:** *(yes / no)*
|
|
1575
|
+
|
|
1576
|
+
---
|
|
1117
1577
|
` : ''}
|
|
1118
|
-
${
|
|
1578
|
+
${needsMobile ? `
|
|
1119
1579
|
## 📱 Mobile
|
|
1120
1580
|
|
|
1121
|
-
**
|
|
1581
|
+
**Q24: Target platforms?**
|
|
1122
1582
|
> **Recommended:** iOS + Android (cross-platform)
|
|
1123
1583
|
> **Your Answer:** *(iOS only / Android only / both)*
|
|
1124
1584
|
|
|
1125
|
-
**
|
|
1585
|
+
**Q25: Mobile framework?**
|
|
1126
1586
|
> **Recommended:** Flutter
|
|
1127
1587
|
> **Your Answer:** *(Flutter / React Native / both)*
|
|
1128
1588
|
|
|
1129
|
-
**
|
|
1589
|
+
**Q26: Offline support?**
|
|
1130
1590
|
> **Recommended:** No (for MVP)
|
|
1131
1591
|
> **Your Answer:** *(yes / no)*
|
|
1592
|
+
|
|
1593
|
+
**Q27: Push notifications?**
|
|
1594
|
+
> **Recommended:** Yes (Firebase Cloud Messaging)
|
|
1595
|
+
> **Your Answer:** *(yes / no)*
|
|
1596
|
+
|
|
1597
|
+
---
|
|
1132
1598
|
` : ''}
|
|
1133
1599
|
|
|
1134
1600
|
## 🚀 Deployment
|
|
1135
1601
|
|
|
1136
|
-
**
|
|
1137
|
-
> **Recommended:** Vercel (frontend) +
|
|
1602
|
+
**Q28: Deployment platform?**
|
|
1603
|
+
> **Recommended:** Vercel (frontend) + Railway or DigitalOcean (backend)
|
|
1138
1604
|
> **Your Answer:** *(edit if needed)*
|
|
1139
1605
|
|
|
1140
|
-
**
|
|
1606
|
+
**Q29: Docker containerization?**
|
|
1141
1607
|
> **Recommended:** Yes
|
|
1142
1608
|
> **Your Answer:** *(yes / no)*
|
|
1143
1609
|
|
|
1144
|
-
**
|
|
1610
|
+
**Q30: CI/CD pipeline?**
|
|
1145
1611
|
> **Recommended:** GitHub Actions
|
|
1146
1612
|
> **Your Answer:** *(yes / no)*
|
|
1147
1613
|
|
|
@@ -1149,18 +1615,12 @@ ${hasMobile ? `
|
|
|
1149
1615
|
|
|
1150
1616
|
## 🧪 Testing Strategy
|
|
1151
1617
|
|
|
1152
|
-
**
|
|
1618
|
+
**Q31: How should testing be handled?**
|
|
1153
1619
|
|
|
1154
1620
|
Options:
|
|
1155
|
-
- **[A] Fast Mode (Recommended for MVPs)** — No testing during
|
|
1156
|
-
A dedicated final sprint handles all testing, security audit, SEO, and browser testing.
|
|
1157
|
-
3-5x faster for initial development.
|
|
1158
|
-
|
|
1621
|
+
- **[A] Fast Mode (Recommended for MVPs)** — No testing during dev sprints. Final sprint handles all testing, security, SEO, browser testing. 3-5x faster.
|
|
1159
1622
|
- **[B] Balanced Mode** — Basic smoke tests per sprint. Full test suite at end.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
- **[C] Strict TDD Mode** — Full Red-Green-Refactor on every task.
|
|
1163
|
-
Slowest but highest quality. Only for healthcare, finance, compliance projects.
|
|
1623
|
+
- **[C] Strict TDD Mode** — Red-Green-Refactor on every task. Healthcare/finance only.
|
|
1164
1624
|
|
|
1165
1625
|
> **Recommended:** A (Fast Mode)
|
|
1166
1626
|
> **Your Answer:** A *(change to B or C if needed)*
|
|
@@ -1169,15 +1629,15 @@ Options:
|
|
|
1169
1629
|
|
|
1170
1630
|
## 🔒 Security
|
|
1171
1631
|
|
|
1172
|
-
**
|
|
1632
|
+
**Q32: Security audit timing?**
|
|
1173
1633
|
> **Recommended:** Final sprint only (OWASP Top 10 + dependency scan)
|
|
1174
1634
|
> **Your Answer:** *(final sprint only / every sprint)*
|
|
1175
1635
|
|
|
1176
1636
|
---
|
|
1177
1637
|
|
|
1178
|
-
## 🌐 SEO
|
|
1638
|
+
## 🌐 SEO
|
|
1179
1639
|
|
|
1180
|
-
**
|
|
1640
|
+
**Q33: SEO requirements?**
|
|
1181
1641
|
> **Recommended:** Technical SEO in final sprint (meta tags, Core Web Vitals, structured data)
|
|
1182
1642
|
> **Your Answer:** *(final sprint only / every sprint / not needed)*
|
|
1183
1643
|
|
|
@@ -1193,125 +1653,337 @@ Write any additional requirements, constraints, or preferences here:
|
|
|
1193
1653
|
|
|
1194
1654
|
*When ready, run \`imhcode plan\` to generate sprint plans from your answers.*
|
|
1195
1655
|
`;
|
|
1196
|
-
|
|
1197
|
-
const brainstormPath = path.join(cwd, BRAINSTORM_MD);
|
|
1198
|
-
fs.mkdirSync(path.join(cwd, DOCS_DIR), { recursive: true });
|
|
1199
|
-
fs.writeFileSync(brainstormPath, content, 'utf8');
|
|
1200
1656
|
}
|
|
1201
1657
|
|
|
1202
|
-
// ─── Sprint Plan Generator
|
|
1658
|
+
// ─── Fix 0 + Fix 5: Sprint Plan Generator (LLM-Powered + Stack-Aware) ─────────
|
|
1203
1659
|
|
|
1204
1660
|
async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
|
|
1205
1661
|
// Parse answers from brainstorming
|
|
1206
|
-
const
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const
|
|
1662
|
+
const testingModeRaw = extractAnswer(brainstormContent, 'Q31', 'A').trim().toUpperCase().charAt(0);
|
|
1663
|
+
const detectedTesting = testingModeRaw === 'B' ? 'balanced' : testingModeRaw === 'C' ? 'strict' : 'fast';
|
|
1664
|
+
|
|
1665
|
+
// Detect scope from brainstorm content
|
|
1666
|
+
const b = brainstormContent.toLowerCase();
|
|
1667
|
+
const hasFrontend = true; // Always assume frontend
|
|
1668
|
+
const hasBackend = (() => {
|
|
1669
|
+
// Check Q31-equivalent backend answer or keyword detection
|
|
1670
|
+
const backendAnswer = extractAnswer(brainstormContent, 'Q16', '').trim();
|
|
1671
|
+
if (backendAnswer && backendAnswer !== '*(edit if needed)*') return true;
|
|
1672
|
+
return /laravel|django|fastapi|express|spring\s*boot|node\.js.*api|postgresql|mysql|redis/i.test(brainstormContent);
|
|
1673
|
+
})();
|
|
1674
|
+
const hasMobile = /flutter|react native|ios.*app|android.*app|expo/i.test(brainstormContent);
|
|
1675
|
+
const needsWorktrees = /worktree.*yes|yes.*worktree/i.test(brainstormContent);
|
|
1676
|
+
const hasPayments = /stripe|paypal|payment|yes.*payment/i.test(brainstormContent);
|
|
1677
|
+
const hasRealtime = /websocket|real-time.*yes|yes.*real-time|socket\.io/i.test(brainstormContent);
|
|
1678
|
+
const designStyle = (() => {
|
|
1679
|
+
const m = brainstormContent.match(/Your Answer:\s*\*(.*?(?:glassmorphism|neumorphism|brutalism|claymorphism|minimalist|modern saas).*?)\*/i);
|
|
1680
|
+
return m ? m[1].trim() : 'Modern SaaS';
|
|
1681
|
+
})();
|
|
1682
|
+
const colorStyle = (() => {
|
|
1683
|
+
const m = brainstormContent.match(/Your Answer:\s*\*(.*?(?:vibrant|dark premium|minimal neutral|pastel|corporate|bold).*?)\*/i);
|
|
1684
|
+
return m ? m[1].trim() : 'Dark Premium';
|
|
1685
|
+
})();
|
|
1211
1686
|
|
|
1212
1687
|
// Save testing mode to config
|
|
1213
1688
|
if (config) {
|
|
1214
1689
|
config.testing_mode = detectedTesting;
|
|
1215
1690
|
const configPath = path.join(cwd, CONFIG_FILE);
|
|
1216
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
1691
|
+
try { fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8'); } catch {}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// Fix 5: Create workspace directories lazily based on brainstorm answers
|
|
1695
|
+
if (hasFrontend) {
|
|
1696
|
+
const frontendDir = path.join(cwd, 'frontend');
|
|
1697
|
+
if (!fs.existsSync(frontendDir)) {
|
|
1698
|
+
fs.mkdirSync(frontendDir, { recursive: true });
|
|
1699
|
+
console.log(` 📁 Created frontend/ (scope confirmed in brainstorming)`);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (hasBackend) {
|
|
1703
|
+
const backendDir = path.join(cwd, 'backend');
|
|
1704
|
+
if (!fs.existsSync(backendDir)) {
|
|
1705
|
+
fs.mkdirSync(backendDir, { recursive: true });
|
|
1706
|
+
console.log(` 📁 Created backend/ (scope confirmed in brainstorming)`);
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
if (needsWorktrees) {
|
|
1710
|
+
const wtDir = path.join(cwd, '.worktrees');
|
|
1711
|
+
if (!fs.existsSync(wtDir)) {
|
|
1712
|
+
fs.mkdirSync(wtDir, { recursive: true });
|
|
1713
|
+
console.log(` 📁 Created .worktrees/ (git worktree snapshots enabled)`);
|
|
1714
|
+
}
|
|
1217
1715
|
}
|
|
1218
1716
|
|
|
1219
1717
|
// Generate PROJECT_BRIEF.md
|
|
1220
|
-
const briefContent = generateProjectBrief(userPrompt, brainstormContent
|
|
1718
|
+
const briefContent = generateProjectBrief(userPrompt, brainstormContent, {
|
|
1719
|
+
hasFrontend, hasBackend, hasMobile, designStyle, colorStyle, detectedTesting
|
|
1720
|
+
});
|
|
1221
1721
|
fs.writeFileSync(path.join(cwd, BRIEF_MD), briefContent, 'utf8');
|
|
1222
1722
|
console.log(` ✅ Created PROJECT_BRIEF.md`);
|
|
1223
1723
|
|
|
1224
|
-
//
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
{
|
|
1228
|
-
|
|
1229
|
-
...(hasFrontend ? [
|
|
1230
|
-
{ task: 'Set up frontend project with design system and component library', agent: 'nextjs-executor', tier: 'standard', deps: [1] },
|
|
1231
|
-
{ task: 'Build authentication UI pages (login, register, forgot password)', agent: 'nextjs-executor', tier: 'standard', deps: [3, 4] },
|
|
1232
|
-
] : []),
|
|
1233
|
-
], detectedTesting);
|
|
1724
|
+
// Try LLM-powered sprint generation first (Fix 0)
|
|
1725
|
+
const llmSprintResult = await tryLLMSprintGeneration(
|
|
1726
|
+
cwd, userPrompt, brainstormContent, config,
|
|
1727
|
+
{ hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting, needsWorktrees }
|
|
1728
|
+
);
|
|
1234
1729
|
|
|
1235
|
-
|
|
1236
|
-
await generateSprint(cwd, 2, 'Core Features', [
|
|
1237
|
-
{ task: 'Build main API endpoints for core business logic', agent: hasBackend ? 'laravel-executor' : 'python-executor', tier: 'complex', deps: [] },
|
|
1238
|
-
...(hasFrontend ? [
|
|
1239
|
-
{ task: 'Build dashboard layout with navigation and sidebar', agent: 'nextjs-executor', tier: 'standard', deps: [] },
|
|
1240
|
-
{ task: 'Implement main feature pages and components', agent: 'nextjs-executor', tier: 'complex', deps: [2] },
|
|
1241
|
-
{ task: 'Connect frontend to backend API with error handling', agent: 'nextjs-executor', tier: 'standard', deps: [1, 3] },
|
|
1242
|
-
] : []),
|
|
1243
|
-
{ task: 'Implement user settings and profile management', agent: hasBackend ? 'laravel-executor' : 'python-executor', tier: 'standard', deps: [1] },
|
|
1244
|
-
], detectedTesting);
|
|
1730
|
+
let lastSprintNum;
|
|
1245
1731
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1732
|
+
if (llmSprintResult) {
|
|
1733
|
+
console.log(` ✅ Sprint plans generated by planning AI`);
|
|
1734
|
+
lastSprintNum = llmSprintResult.sprintCount;
|
|
1735
|
+
} else {
|
|
1736
|
+
// Fix 5: Fallback to smart static generation
|
|
1737
|
+
lastSprintNum = await generateStaticSprintPlans(
|
|
1738
|
+
cwd,
|
|
1739
|
+
{ hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting },
|
|
1740
|
+
config
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1248
1743
|
|
|
1249
|
-
//
|
|
1744
|
+
// Auto-generate testing sprint if fast/balanced mode
|
|
1250
1745
|
if (detectedTesting !== 'strict') {
|
|
1251
1746
|
const testingSprintNum = lastSprintNum + 1;
|
|
1252
|
-
await generateTestingSprint(cwd, testingSprintNum, hasFrontend, hasBackend);
|
|
1253
|
-
console.log(` ✅ Created
|
|
1747
|
+
await generateTestingSprint(cwd, testingSprintNum, hasFrontend, hasBackend, config);
|
|
1748
|
+
console.log(` ✅ Created Testing Sprint ${testingSprintNum} (security + SEO + E2E)`);
|
|
1254
1749
|
}
|
|
1255
1750
|
|
|
1256
1751
|
// Update compact context
|
|
1257
|
-
const contextContent =
|
|
1752
|
+
const contextContent = buildContextContent(userPrompt, {
|
|
1753
|
+
hasFrontend, hasBackend, hasMobile, detectedTesting, lastSprintNum, designStyle, colorStyle
|
|
1754
|
+
});
|
|
1755
|
+
fs.mkdirSync(path.join(cwd, LOCAL_DIR_NAME), { recursive: true });
|
|
1756
|
+
fs.writeFileSync(path.join(cwd, CONTEXT_MD), contextContent, 'utf8');
|
|
1757
|
+
console.log(` ✅ Created .imhcode/context.md`);
|
|
1758
|
+
}
|
|
1258
1759
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1760
|
+
/**
|
|
1761
|
+
* Attempt LLM-generated sprint plans. Returns { sprintCount } on success, null on failure.
|
|
1762
|
+
*/
|
|
1763
|
+
async function tryLLMSprintGeneration(cwd, userPrompt, brainstormContent, config, scope) {
|
|
1764
|
+
const { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting } = scope;
|
|
1263
1765
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
-
|
|
1268
|
-
-
|
|
1766
|
+
const agentList = [
|
|
1767
|
+
'planner (planning)', 'designer (frontend)', 'nextjs-executor (frontend)', 'react-executor (frontend)',
|
|
1768
|
+
'vue-executor (frontend)', 'laravel-executor (backend)', 'python-executor (backend)',
|
|
1769
|
+
'java-executor (backend)', 'flutter-executor (backend)', 'react-native-executor (backend)',
|
|
1770
|
+
'ios-executor (backend)', 'android-executor (backend)', 'systems-executor (backend)',
|
|
1771
|
+
'web3-executor (backend)', 'devops-executor (backend)', 'tester (testing)',
|
|
1772
|
+
'security-auditor (testing)', 'seo-optimizer (review)', 'debugger (review)',
|
|
1773
|
+
].join('\n ');
|
|
1269
1774
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1775
|
+
const modelRouting = config?.model_routing || {};
|
|
1776
|
+
const routingContext = Object.entries(modelRouting).map(([cat, r]) =>
|
|
1777
|
+
` ${cat}: ${r?.model} via ${r?.engine}`
|
|
1778
|
+
).join('\n');
|
|
1274
1779
|
|
|
1275
|
-
|
|
1276
|
-
Sprint 1 (not started)
|
|
1780
|
+
const llmPrompt = `You are the IMH-Code Sprint Planner — an expert software architect.
|
|
1277
1781
|
|
|
1278
|
-
|
|
1279
|
-
${hasFrontend ? '- frontend/app/ — Next.js pages' : ''}
|
|
1280
|
-
${hasBackend ? '- backend/routes/api.php — API routes' : ''}
|
|
1281
|
-
${hasBackend ? '- backend/app/Models/ — Eloquent models' : ''}
|
|
1282
|
-
`;
|
|
1782
|
+
Generate a complete sprint plan for the following project. Output ONLY valid JSON, nothing else.
|
|
1283
1783
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1784
|
+
## Project Description
|
|
1785
|
+
${userPrompt}
|
|
1786
|
+
|
|
1787
|
+
## Brainstorming Answers
|
|
1788
|
+
${brainstormContent}
|
|
1789
|
+
|
|
1790
|
+
## Scope
|
|
1791
|
+
- Frontend: ${hasFrontend ? 'YES' : 'NO'} (Design style: ${designStyle}, Colors: ${colorStyle})
|
|
1792
|
+
- Backend: ${hasBackend ? 'YES' : 'NO'}
|
|
1793
|
+
- Mobile: ${hasMobile ? 'YES' : 'NO'}
|
|
1794
|
+
- Payments: ${hasPayments ? 'YES' : 'NO'}
|
|
1795
|
+
- Realtime: ${hasRealtime ? 'YES' : 'NO'}
|
|
1796
|
+
- Testing: ${detectedTesting}
|
|
1797
|
+
|
|
1798
|
+
## Available Agents
|
|
1799
|
+
${agentList}
|
|
1800
|
+
|
|
1801
|
+
## Model Routing (each agent will use its category model)
|
|
1802
|
+
${routingContext}
|
|
1803
|
+
|
|
1804
|
+
## Output Format
|
|
1805
|
+
|
|
1806
|
+
Return ONLY this JSON structure (no markdown, no explanation):
|
|
1807
|
+
|
|
1808
|
+
{
|
|
1809
|
+
"sprints": [
|
|
1810
|
+
{
|
|
1811
|
+
"num": 1,
|
|
1812
|
+
"title": "Foundation & Setup",
|
|
1813
|
+
"tasks": [
|
|
1814
|
+
{
|
|
1815
|
+
"num": 1,
|
|
1816
|
+
"task": "Detailed task description that a senior engineer would understand",
|
|
1817
|
+
"agent": "agent-id",
|
|
1818
|
+
"tier": "light|standard|complex",
|
|
1819
|
+
"deps": []
|
|
1820
|
+
}
|
|
1821
|
+
]
|
|
1822
|
+
}
|
|
1823
|
+
]
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
## Rules
|
|
1827
|
+
1. Generate 2-4 sprints based on project complexity (NOT counting the testing sprint)
|
|
1828
|
+
2. Sprint 1 = Foundation (project setup, DB schema, auth, design system)
|
|
1829
|
+
3. Sprint 2 = Core Features (main functionality)
|
|
1830
|
+
4. Sprint 3+ = Additional features, integrations (only if needed)
|
|
1831
|
+
5. Task descriptions must be SPECIFIC to this project, not generic
|
|
1832
|
+
6. Choose the RIGHT agent for each task:
|
|
1833
|
+
- nextjs-executor/react-executor/vue-executor/designer → frontend tasks
|
|
1834
|
+
- laravel-executor/python-executor/java-executor → backend tasks
|
|
1835
|
+
- devops-executor → infrastructure, Docker, CI/CD
|
|
1836
|
+
- planner → complex planning/architecture tasks
|
|
1837
|
+
7. Respect dependencies (dep task numbers that must complete first)
|
|
1838
|
+
8. Max 6 tasks per sprint for clean execution
|
|
1839
|
+
9. If design style is Glassmorphism/Neumorphism — include a designer task for design tokens in Sprint 1
|
|
1840
|
+
10. If payments needed — include payment integration task in a sprint
|
|
1841
|
+
11. If realtime needed — include WebSocket/SSE task in a sprint`;
|
|
1842
|
+
|
|
1843
|
+
const llmOutput = await invokePlanningLLM(llmPrompt, config, cwd);
|
|
1844
|
+
if (!llmOutput) return null;
|
|
1845
|
+
|
|
1846
|
+
// Parse JSON from LLM output
|
|
1847
|
+
let parsed;
|
|
1848
|
+
try {
|
|
1849
|
+
// Strip markdown code fences if present
|
|
1850
|
+
const cleaned = llmOutput.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
|
|
1851
|
+
parsed = JSON.parse(cleaned);
|
|
1852
|
+
} catch (err) {
|
|
1853
|
+
// Try to extract JSON object from the output
|
|
1854
|
+
const jsonMatch = llmOutput.match(/\{[\s\S]*"sprints"[\s\S]*\}/);
|
|
1855
|
+
if (!jsonMatch) {
|
|
1856
|
+
console.warn(' ⚠️ Planning AI returned non-JSON output — falling back to static template');
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
try {
|
|
1860
|
+
parsed = JSON.parse(jsonMatch[0]);
|
|
1861
|
+
} catch {
|
|
1862
|
+
console.warn(' ⚠️ Could not parse planning AI JSON — falling back to static template');
|
|
1863
|
+
return null;
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
if (!parsed?.sprints?.length) return null;
|
|
1868
|
+
|
|
1869
|
+
// Generate sprint files from LLM-provided structure
|
|
1870
|
+
for (const sprint of parsed.sprints) {
|
|
1871
|
+
await generateSprint(cwd, sprint.num, sprint.title, sprint.tasks, detectedTesting, config);
|
|
1872
|
+
console.log(` ✅ Created Sprint ${sprint.num}: ${sprint.title} (${sprint.tasks.length} tasks)`);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
return { sprintCount: parsed.sprints.length };
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* Fix 5: Smart static sprint plan generator (fallback when LLM unavailable)
|
|
1880
|
+
* Generates tailored sprints based on detected scope.
|
|
1881
|
+
*/
|
|
1882
|
+
async function generateStaticSprintPlans(cwd, scope, config) {
|
|
1883
|
+
const { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting } = scope;
|
|
1884
|
+
|
|
1885
|
+
const frontendAgent = 'nextjs-executor';
|
|
1886
|
+
const backendAgent = hasBackend ? 'laravel-executor' : 'python-executor';
|
|
1887
|
+
|
|
1888
|
+
const needsDesignSprint = /glassmorphism|neumorphism|brutalism|claymorphism/i.test(designStyle);
|
|
1889
|
+
|
|
1890
|
+
let sprintNum = 1;
|
|
1891
|
+
const sprints = [];
|
|
1892
|
+
|
|
1893
|
+
// Sprint 1: Foundation
|
|
1894
|
+
const sprint1Tasks = [
|
|
1895
|
+
{ task: 'Initialize monorepo structure, configure environments (.env, Docker compose, GitHub Actions skeleton)', agent: 'devops-executor', tier: 'light', deps: [] },
|
|
1896
|
+
...(hasBackend ? [
|
|
1897
|
+
{ task: `Design and implement database schema with migrations for core entities. Set up ${/laravel/i.test(backendAgent) ? 'Eloquent models' : 'ORM models'} with relationships, indexes, and seed factories`, agent: backendAgent, tier: 'standard', deps: [1] },
|
|
1898
|
+
{ task: 'Implement full authentication system: user registration, login, logout, password reset, email verification, JWT/session management', agent: backendAgent, tier: 'complex', deps: [2] },
|
|
1899
|
+
] : []),
|
|
1900
|
+
...(needsDesignSprint ? [
|
|
1901
|
+
{ task: `Design system tokens: ${colorStyle} color palette, typography scale, spacing system, ${designStyle} component patterns (glassmorphism/shadows/borders). Generate CSS custom properties and Tailwind config`, agent: 'designer', tier: 'standard', deps: [1] },
|
|
1902
|
+
] : []),
|
|
1903
|
+
...(hasFrontend ? [
|
|
1904
|
+
{ task: `Set up ${frontendAgent.replace('-executor', '')} project with shadcn/ui, Tailwind CSS v4, ${needsDesignSprint ? designStyle + ' design tokens,' : ''} ESLint, Prettier, and base layout components`, agent: frontendAgent, tier: 'standard', deps: [1] },
|
|
1905
|
+
{ task: 'Build authentication UI pages: login, register, forgot password, email verification. Connect to backend auth API with error handling and loading states', agent: frontendAgent, tier: 'standard', deps: hasBackend ? [3, needsDesignSprint ? 4 : 4] : [needsDesignSprint ? 4 : 4] },
|
|
1906
|
+
] : []),
|
|
1907
|
+
];
|
|
1908
|
+
sprints.push({ num: sprintNum++, title: 'Foundation & Setup', tasks: sprint1Tasks });
|
|
1909
|
+
|
|
1910
|
+
// Sprint 2: Core Features
|
|
1911
|
+
const sprint2Tasks = [
|
|
1912
|
+
...(hasBackend ? [
|
|
1913
|
+
{ task: 'Build main REST API endpoints for core business logic. Include request validation, error responses, pagination, and comprehensive API documentation', agent: backendAgent, tier: 'complex', deps: [] },
|
|
1914
|
+
{ task: 'Implement user profile, settings management, role-based permissions, and admin panel endpoints', agent: backendAgent, tier: 'standard', deps: [1] },
|
|
1915
|
+
] : []),
|
|
1916
|
+
...(hasFrontend ? [
|
|
1917
|
+
{ task: 'Build main dashboard layout: sidebar navigation, header with user menu, breadcrumbs, responsive grid. Implement route structure and page transitions', agent: frontendAgent, tier: 'standard', deps: [] },
|
|
1918
|
+
{ task: 'Implement core feature pages and reusable data components (tables, forms, modals, filters). Connect to backend APIs with optimistic UI updates', agent: frontendAgent, tier: 'complex', deps: [hasBackend ? 3 : 1] },
|
|
1919
|
+
{ task: 'Build user settings, profile management, and notification preference pages. Implement theme toggle and responsive mobile navigation', agent: frontendAgent, tier: 'standard', deps: [hasBackend ? 4 : 2] },
|
|
1920
|
+
] : []),
|
|
1921
|
+
];
|
|
1922
|
+
sprints.push({ num: sprintNum++, title: 'Core Features', tasks: sprint2Tasks });
|
|
1923
|
+
|
|
1924
|
+
// Sprint 3: Advanced Features (if payments, realtime, or mobile needed)
|
|
1925
|
+
const sprint3Tasks = [];
|
|
1926
|
+
if (hasPayments) {
|
|
1927
|
+
sprint3Tasks.push({ task: 'Integrate Stripe payment processing: subscription plans, checkout flow, webhooks for payment events, billing portal, invoice management', agent: backendAgent, tier: 'complex', deps: [] });
|
|
1928
|
+
if (hasFrontend) {
|
|
1929
|
+
sprint3Tasks.push({ task: 'Build pricing page, checkout UI with Stripe Elements, subscription management dashboard, billing history', agent: frontendAgent, tier: 'complex', deps: [1] });
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
if (hasRealtime) {
|
|
1933
|
+
sprint3Tasks.push({ task: 'Implement WebSocket/SSE real-time features: live data updates, presence indicators, notification broadcasting. Set up Redis pub/sub', agent: backendAgent, tier: 'complex', deps: [] });
|
|
1934
|
+
if (hasFrontend) {
|
|
1935
|
+
sprint3Tasks.push({ task: 'Build real-time UI components: live activity feed, online users indicator, real-time notifications toast system', agent: frontendAgent, tier: 'standard', deps: [hasPayments ? 2 : 1] });
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
if (hasMobile) {
|
|
1939
|
+
sprint3Tasks.push({ task: 'Build core mobile screens: auth flow, home/dashboard, main feature screens. Set up navigation, state management, and API integration', agent: 'flutter-executor', tier: 'complex', deps: [] });
|
|
1940
|
+
sprint3Tasks.push({ task: 'Implement push notifications, offline support, biometric auth, and app store metadata preparation', agent: 'flutter-executor', tier: 'standard', deps: [sprint3Tasks.length] });
|
|
1941
|
+
}
|
|
1942
|
+
if (sprint3Tasks.length > 0) {
|
|
1943
|
+
sprints.push({ num: sprintNum++, title: 'Advanced Features', tasks: sprint3Tasks });
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// Generate all sprint files
|
|
1947
|
+
for (const sprint of sprints) {
|
|
1948
|
+
await generateSprint(cwd, sprint.num, sprint.title, sprint.tasks, detectedTesting, config);
|
|
1949
|
+
console.log(` ✅ Created Sprint ${sprint.num}: ${sprint.title} (${sprint.tasks.length} tasks)`);
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
return sprintNum - 1; // last sprint number
|
|
1287
1953
|
}
|
|
1288
1954
|
|
|
1289
|
-
|
|
1955
|
+
// ─── Fix 0b Part B: Sprint File Generator (injects --engine/--model) ──────────
|
|
1956
|
+
|
|
1957
|
+
async function generateSprint(cwd, sprintNum, title, tasks, testingMode, config) {
|
|
1290
1958
|
const sprintDir = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`);
|
|
1291
1959
|
const tasksDir = path.join(sprintDir, 'tasks');
|
|
1292
1960
|
fs.mkdirSync(tasksDir, { recursive: true });
|
|
1293
1961
|
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
'**Testing Mode: Fast** — No tests during this sprint. Tests handled in final sprint.';
|
|
1962
|
+
const tddNote = testingMode === 'strict' ? '**Testing Mode: Strict TDD** — Write failing tests first, then implement.' :
|
|
1963
|
+
testingMode === 'balanced' ? '**Testing Mode: Balanced** — Add basic smoke tests per task.' :
|
|
1964
|
+
'**Testing Mode: Fast** — No tests during this sprint. Final sprint handles all testing.';
|
|
1298
1965
|
|
|
1966
|
+
// plan.md
|
|
1299
1967
|
let planMd = `# Sprint ${sprintNum}: ${title}
|
|
1300
1968
|
|
|
1301
1969
|
> ${tddNote}
|
|
1302
1970
|
|
|
1303
1971
|
## Task Table
|
|
1304
1972
|
|
|
1305
|
-
| # | Task | Agent | Tier | Depends On |
|
|
1306
|
-
|
|
1307
|
-
${tasks.map((t, i) =>
|
|
1973
|
+
| # | Task | Agent | Category | Model | Tier | Depends On |
|
|
1974
|
+
|---|------|-------|----------|-------|------|-----------|
|
|
1975
|
+
${tasks.map((t, i) => {
|
|
1976
|
+
const cat = getAgentCategory(t.agent);
|
|
1977
|
+
const model = config?.model_routing?.[cat]?.model || t.agent;
|
|
1978
|
+
return `| ${i+1} | ${t.task} | \`${t.agent}\` | ${cat} | ${model} | ${t.tier} | ${t.deps?.length ? t.deps.join(', ') : '—'} |`;
|
|
1979
|
+
}).join('\n')}
|
|
1308
1980
|
|
|
1309
1981
|
## Sprint Goals
|
|
1310
1982
|
- Complete all ${tasks.length} tasks in dependency order
|
|
1983
|
+
- Each task uses its category-optimized AI model
|
|
1311
1984
|
- Update PROJECT_BRIEF.md after completion
|
|
1312
1985
|
- Update .imhcode/context.md with what was built
|
|
1313
1986
|
`;
|
|
1314
|
-
|
|
1315
1987
|
fs.writeFileSync(path.join(sprintDir, 'plan.md'), planMd, 'utf8');
|
|
1316
1988
|
|
|
1317
1989
|
// progress.md
|
|
@@ -1322,49 +1994,57 @@ Start: —
|
|
|
1322
1994
|
End: —
|
|
1323
1995
|
|
|
1324
1996
|
## Task Status
|
|
1325
|
-
${tasks.map((t, i) => `- [ ] Task ${i+1}: ${t.task}`).join('\n')}
|
|
1997
|
+
${tasks.map((t, i) => `- [ ] Task ${i+1}: ${t.task} [\`${t.agent}\`]`).join('\n')}
|
|
1326
1998
|
`;
|
|
1327
1999
|
fs.writeFileSync(path.join(sprintDir, 'progress.md'), progressMd, 'utf8');
|
|
1328
|
-
|
|
1329
|
-
// deferred.md
|
|
1330
2000
|
fs.writeFileSync(path.join(sprintDir, 'deferred.md'), `# Sprint ${sprintNum} Deferred Items\n\nNone yet.\n`, 'utf8');
|
|
1331
2001
|
|
|
1332
|
-
// Individual task scripts
|
|
2002
|
+
// Fix 0b Part B: Individual task scripts with --engine/--model injected
|
|
1333
2003
|
for (let i = 0; i < tasks.length; i++) {
|
|
1334
|
-
const t
|
|
2004
|
+
const t = tasks[i];
|
|
1335
2005
|
const taskNum = i + 1;
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
2006
|
+
const category = getAgentCategory(t.agent);
|
|
2007
|
+
const routedEngine = config?.model_routing?.[category]?.engine || '';
|
|
2008
|
+
const routedModel = config?.model_routing?.[category]?.model || '';
|
|
2009
|
+
|
|
2010
|
+
const engineFlag = routedEngine ? `--engine ${routedEngine}` : '';
|
|
2011
|
+
const modelFlag = routedModel ? `--model "${routedModel}"` : '';
|
|
2012
|
+
|
|
2013
|
+
const taskDesc = testingMode === 'strict' ? `[STRICT TDD] ${t.task}: Write failing tests first, then implement.` :
|
|
2014
|
+
testingMode === 'balanced' ? `[BALANCED] ${t.task}: Add basic smoke tests.` :
|
|
2015
|
+
t.task;
|
|
1339
2016
|
|
|
1340
2017
|
const taskScript = `#!/bin/bash
|
|
1341
2018
|
# IMH-Code — Sprint ${sprintNum} Task ${taskNum}
|
|
1342
|
-
# Task:
|
|
1343
|
-
# Agent:
|
|
1344
|
-
#
|
|
1345
|
-
|
|
2019
|
+
# Task: ${t.task}
|
|
2020
|
+
# Agent: ${t.agent} (${category})
|
|
2021
|
+
# Model: ${routedModel || 'default'} via ${routedEngine || 'default'}
|
|
2022
|
+
# Tier: ${t.tier}
|
|
2023
|
+
CWD="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
1346
2024
|
cd "$CWD/../../../.."
|
|
1347
2025
|
|
|
1348
|
-
${
|
|
2026
|
+
TASK="${taskDesc.replace(/"/g, '\\"')}"
|
|
1349
2027
|
|
|
1350
2028
|
echo "📋 Running Task ${taskNum}: ${t.task}"
|
|
1351
|
-
echo " Agent:
|
|
2029
|
+
echo " Agent: ${t.agent}"
|
|
2030
|
+
echo " Model: ${routedModel || 'default'} via ${routedEngine || 'default'}"
|
|
2031
|
+
echo " Tier: ${t.tier}"
|
|
1352
2032
|
|
|
1353
2033
|
if command -v imhcode >/dev/null 2>&1; then
|
|
1354
|
-
imhcode agent run ${t.agent} "$TASK" --live
|
|
2034
|
+
imhcode agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag}
|
|
1355
2035
|
else
|
|
1356
|
-
node "$(npm root -g)/imhcode/bin/imhcode.js" agent run ${t.agent} "$TASK" --live
|
|
2036
|
+
node "$(npm root -g)/imhcode/bin/imhcode.js" agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag}
|
|
1357
2037
|
fi
|
|
1358
2038
|
`;
|
|
1359
2039
|
fs.writeFileSync(path.join(tasksDir, `task_${taskNum}.sh`), taskScript, { mode: 0o755 });
|
|
1360
2040
|
}
|
|
1361
2041
|
|
|
1362
|
-
// run_all_tasks.sh
|
|
2042
|
+
// run_all_tasks.sh
|
|
1363
2043
|
const masterScript = `#!/bin/bash
|
|
1364
2044
|
# IMH-Code — Sprint ${sprintNum}: ${title}
|
|
1365
|
-
# Run all tasks in dependency order
|
|
2045
|
+
# Run all tasks in dependency order with correct AI model routing
|
|
1366
2046
|
set -e
|
|
1367
|
-
CWD="$(cd "$(dirname "
|
|
2047
|
+
CWD="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
1368
2048
|
TASKS_DIR="$CWD/tasks"
|
|
1369
2049
|
|
|
1370
2050
|
echo ""
|
|
@@ -1374,8 +2054,11 @@ echo ""
|
|
|
1374
2054
|
|
|
1375
2055
|
${tasks.map((t, i) => {
|
|
1376
2056
|
const taskNum = i + 1;
|
|
2057
|
+
const category = getAgentCategory(t.agent);
|
|
2058
|
+
const model = config?.model_routing?.[category]?.model || 'default';
|
|
1377
2059
|
return `echo "\\n─────────────────────────────────────────────────────────────"
|
|
1378
2060
|
echo "📋 Task ${taskNum}/${tasks.length}: ${t.task}"
|
|
2061
|
+
echo " Agent: ${t.agent} → ${model}"
|
|
1379
2062
|
echo "─────────────────────────────────────────────────────────────"
|
|
1380
2063
|
bash "$TASKS_DIR/task_${taskNum}.sh"`;
|
|
1381
2064
|
}).join('\n\n')}
|
|
@@ -1386,32 +2069,30 @@ echo " Run: imhcode execute ${sprintNum + 1}"
|
|
|
1386
2069
|
echo ""
|
|
1387
2070
|
`;
|
|
1388
2071
|
fs.writeFileSync(path.join(sprintDir, 'run_all_tasks.sh'), masterScript, { mode: 0o755 });
|
|
1389
|
-
|
|
1390
|
-
console.log(` ✅ Created Sprint ${sprintNum}: ${title} (${tasks.length} tasks)`);
|
|
1391
2072
|
}
|
|
1392
2073
|
|
|
1393
|
-
async function generateTestingSprint(cwd, sprintNum, hasFrontend, hasBackend) {
|
|
2074
|
+
async function generateTestingSprint(cwd, sprintNum, hasFrontend, hasBackend, config) {
|
|
1394
2075
|
const tasks = [
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
{ task: 'Write component tests for all frontend pages and components', agent: 'tester', tier: 'standard', deps: [] },
|
|
1399
|
-
{ task: 'Run E2E browser tests with Playwright (all critical user flows)', agent: 'tester', tier: 'complex', deps: [3] },
|
|
2076
|
+
...(hasBackend ? [
|
|
2077
|
+
{ task: 'Write comprehensive unit tests for all backend API endpoints, service layer, and utility functions. Target 80%+ coverage', agent: 'tester', tier: 'standard', deps: [] },
|
|
2078
|
+
{ task: 'Write integration tests for core API flows: auth, main CRUD operations, edge cases, and error handling', agent: 'tester', tier: 'complex', deps: [1] },
|
|
1400
2079
|
] : []),
|
|
1401
|
-
{ task: 'Run security audit: OWASP Top 10, dependency
|
|
2080
|
+
{ task: 'Run full security audit: OWASP Top 10, dependency vulnerability scan (npm audit / composer audit), auth testing, SQL injection, XSS checks', agent: 'security-auditor', tier: 'complex', deps: [] },
|
|
1402
2081
|
...(hasFrontend ? [
|
|
1403
|
-
{ task: '
|
|
1404
|
-
{ task: 'Run
|
|
2082
|
+
{ task: 'Write Playwright E2E tests covering all critical user flows: registration, login, core features, payment flow (if applicable)', agent: 'tester', tier: 'complex', deps: hasBackend ? [2] : [] },
|
|
2083
|
+
{ task: 'Run SEO audit: Core Web Vitals (LCP, CLS, INP), meta tags, structured data, sitemap.xml, robots.txt, Open Graph tags', agent: 'seo-optimizer', tier: 'standard', deps: [] },
|
|
2084
|
+
{ task: 'Run accessibility audit (WCAG 2.1 AA): screen reader compatibility, keyboard navigation, color contrast, ARIA labels. Fix all critical issues', agent: 'tester', tier: 'standard', deps: [] },
|
|
1405
2085
|
] : []),
|
|
1406
|
-
{ task: 'Generate final test coverage report
|
|
2086
|
+
{ task: 'Generate final test coverage report. Fix any failing tests. Document known issues in PROJECT_REPORT.md', agent: 'tester', tier: 'standard', deps: [] },
|
|
1407
2087
|
];
|
|
1408
2088
|
|
|
1409
|
-
await generateSprint(cwd, sprintNum, 'Testing, Security & SEO Audit', tasks, 'fast');
|
|
2089
|
+
await generateSprint(cwd, sprintNum, 'Testing, Security & SEO Audit', tasks, 'fast', config);
|
|
1410
2090
|
}
|
|
1411
2091
|
|
|
1412
2092
|
// ─── Helper Functions ─────────────────────────────────────────────────────────
|
|
1413
2093
|
|
|
1414
|
-
function generateProjectBrief(userPrompt, brainstormContent) {
|
|
2094
|
+
function generateProjectBrief(userPrompt, brainstormContent, scope) {
|
|
2095
|
+
const { hasFrontend, hasBackend, hasMobile, designStyle, colorStyle, detectedTesting } = scope || {};
|
|
1415
2096
|
return `# PROJECT_BRIEF.md
|
|
1416
2097
|
|
|
1417
2098
|
> **IMH-Code — Imam Hussain Coding Harness Platform**
|
|
@@ -1425,9 +2106,20 @@ ${userPrompt}
|
|
|
1425
2106
|
## Status
|
|
1426
2107
|
|
|
1427
2108
|
- **Current Sprint**: Sprint 1
|
|
1428
|
-
- **Testing Mode**:
|
|
2109
|
+
- **Testing Mode**: ${detectedTesting || 'fast'} (final sprint only)
|
|
1429
2110
|
- **Generated**: ${new Date().toLocaleDateString()}
|
|
1430
2111
|
|
|
2112
|
+
## Design Specification
|
|
2113
|
+
|
|
2114
|
+
- **Design Style**: ${designStyle || 'Modern SaaS'}
|
|
2115
|
+
- **Color Palette**: ${colorStyle || 'Dark Premium'}
|
|
2116
|
+
|
|
2117
|
+
## Scope
|
|
2118
|
+
|
|
2119
|
+
- **Frontend**: ${hasFrontend ? '✅ Yes' : '❌ No'}
|
|
2120
|
+
- **Backend**: ${hasBackend ? '✅ Yes' : '❌ No'}
|
|
2121
|
+
- **Mobile**: ${hasMobile ? '✅ Yes' : '❌ No'}
|
|
2122
|
+
|
|
1431
2123
|
## Key Architecture Decisions
|
|
1432
2124
|
|
|
1433
2125
|
*(Will be updated as sprints complete)*
|
|
@@ -1442,21 +2134,71 @@ ${userPrompt}
|
|
|
1442
2134
|
`;
|
|
1443
2135
|
}
|
|
1444
2136
|
|
|
2137
|
+
function buildContextContent(userPrompt, scope) {
|
|
2138
|
+
const { hasFrontend, hasBackend, hasMobile, detectedTesting, designStyle, colorStyle } = scope || {};
|
|
2139
|
+
return `# IMH-Code Project Context
|
|
2140
|
+
|
|
2141
|
+
Generated: ${new Date().toLocaleDateString()}
|
|
2142
|
+
Project: ${userPrompt.slice(0, 150)}...
|
|
2143
|
+
Testing Mode: ${detectedTesting}
|
|
2144
|
+
Design Style: ${designStyle} / ${colorStyle}
|
|
2145
|
+
|
|
2146
|
+
## Stack
|
|
2147
|
+
${hasFrontend ? '- Frontend: Next.js 15 + shadcn/ui + Tailwind CSS v4' : ''}
|
|
2148
|
+
${hasBackend ? '- Backend: Laravel 11 + PostgreSQL + Redis' : ''}
|
|
2149
|
+
${hasMobile ? '- Mobile: Flutter' : ''}
|
|
2150
|
+
- Auth: JWT tokens
|
|
2151
|
+
- Deployment: Docker + GitHub Actions
|
|
2152
|
+
|
|
2153
|
+
## Directory Structure
|
|
2154
|
+
${hasFrontend ? '- frontend/ — All frontend code' : ''}
|
|
2155
|
+
${hasBackend ? '- backend/ — All backend code' : ''}
|
|
2156
|
+
- docs/ — Sprint plans and documentation
|
|
2157
|
+
|
|
2158
|
+
## Current Sprint
|
|
2159
|
+
Sprint 1 (not started)
|
|
2160
|
+
`;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
1445
2163
|
function extractPromptFromStartMd(content) {
|
|
1446
2164
|
const match = content.match(/<!-- WRITE_PROMPT_HERE -->([\s\S]*?)<!-- END_PROMPT -->/);
|
|
1447
2165
|
if (match) return match[1].trim();
|
|
1448
|
-
// Fallback: return everything after the first heading
|
|
1449
2166
|
const lines = content.split('\n').filter(l => !l.startsWith('#') && l.trim().length > 0);
|
|
1450
2167
|
return lines.join(' ').slice(0, 500);
|
|
1451
2168
|
}
|
|
1452
2169
|
|
|
1453
2170
|
function extractAnswer(brainstormContent, questionKey, defaultAnswer) {
|
|
1454
|
-
const regex = new RegExp(`\\*\\*${questionKey}.*?\\*\\*Your Answer
|
|
2171
|
+
const regex = new RegExp(`\\*\\*${questionKey}.*?\\*\\*Your Answer:\\*\\*\\s*([^\\n*]+)`, 's');
|
|
1455
2172
|
const match = brainstormContent.match(regex);
|
|
1456
|
-
if (match) return match[1].trim();
|
|
2173
|
+
if (match) return match[1].replace(/\*\(.*?\)\*/g, '').trim();
|
|
1457
2174
|
return defaultAnswer;
|
|
1458
2175
|
}
|
|
1459
2176
|
|
|
2177
|
+
function getAgentCategory(agentId) {
|
|
2178
|
+
const map = {
|
|
2179
|
+
'planner': 'planning',
|
|
2180
|
+
'designer': 'frontend',
|
|
2181
|
+
'nextjs-executor': 'frontend',
|
|
2182
|
+
'react-executor': 'frontend',
|
|
2183
|
+
'vue-executor': 'frontend',
|
|
2184
|
+
'laravel-executor': 'backend',
|
|
2185
|
+
'python-executor': 'backend',
|
|
2186
|
+
'java-executor': 'backend',
|
|
2187
|
+
'flutter-executor': 'backend',
|
|
2188
|
+
'react-native-executor': 'backend',
|
|
2189
|
+
'ios-executor': 'backend',
|
|
2190
|
+
'android-executor': 'backend',
|
|
2191
|
+
'systems-executor': 'backend',
|
|
2192
|
+
'web3-executor': 'backend',
|
|
2193
|
+
'devops-executor': 'backend',
|
|
2194
|
+
'tester': 'testing',
|
|
2195
|
+
'security-auditor': 'testing',
|
|
2196
|
+
'seo-optimizer': 'review',
|
|
2197
|
+
'debugger': 'review',
|
|
2198
|
+
};
|
|
2199
|
+
return map[agentId] || 'backend';
|
|
2200
|
+
}
|
|
2201
|
+
|
|
1460
2202
|
function detectSprintDocs(cwd) {
|
|
1461
2203
|
const docsDir = path.join(cwd, DOCS_DIR);
|
|
1462
2204
|
if (!fs.existsSync(docsDir)) return false;
|
|
@@ -1484,14 +2226,11 @@ function resolveSprintNum(restArgs, cwd) {
|
|
|
1484
2226
|
const n = parseInt(numStr, 10);
|
|
1485
2227
|
if (!isNaN(n)) return n;
|
|
1486
2228
|
}
|
|
1487
|
-
|
|
1488
|
-
// Try PROJECT_BRIEF.md
|
|
1489
2229
|
const briefPath = path.join(cwd, BRIEF_MD);
|
|
1490
2230
|
if (fs.existsSync(briefPath)) {
|
|
1491
2231
|
const m = fs.readFileSync(briefPath, 'utf8').match(/Current Sprint:\s*Sprint\s*(\d+)/i);
|
|
1492
2232
|
if (m) return parseInt(m[1], 10);
|
|
1493
2233
|
}
|
|
1494
|
-
|
|
1495
2234
|
return detectCurrentSprint(cwd);
|
|
1496
2235
|
}
|
|
1497
2236
|
|
|
@@ -1503,13 +2242,19 @@ function loadLocalConfig(cwd) {
|
|
|
1503
2242
|
return null;
|
|
1504
2243
|
}
|
|
1505
2244
|
|
|
2245
|
+
function safeRead(filePath) {
|
|
2246
|
+
try { return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null; } catch { return null; }
|
|
2247
|
+
}
|
|
2248
|
+
|
|
1506
2249
|
async function updateCompactContext(cwd, completedSprint) {
|
|
1507
2250
|
const contextPath = path.join(cwd, CONTEXT_MD);
|
|
1508
2251
|
if (!fs.existsSync(contextPath)) return;
|
|
1509
|
-
|
|
1510
2252
|
try {
|
|
1511
2253
|
let content = fs.readFileSync(contextPath, 'utf8');
|
|
1512
|
-
content = content.replace(
|
|
2254
|
+
content = content.replace(
|
|
2255
|
+
/Current Sprint\nSprint \d+ \(not started\)/,
|
|
2256
|
+
`Current Sprint\nSprint ${completedSprint + 1}`
|
|
2257
|
+
);
|
|
1513
2258
|
fs.writeFileSync(contextPath, content, 'utf8');
|
|
1514
2259
|
} catch { /* non-critical */ }
|
|
1515
2260
|
}
|
|
@@ -1523,12 +2268,12 @@ function askQuestion(query) {
|
|
|
1523
2268
|
|
|
1524
2269
|
function scanAssistantCLIs() {
|
|
1525
2270
|
const engines = {
|
|
1526
|
-
claude: { name: 'Claude Code',
|
|
1527
|
-
opencode: { name: 'OpenCode',
|
|
1528
|
-
codex: { name: 'Codex (OpenAI)',
|
|
1529
|
-
agy: { name: 'Antigravity CLI (agy)',
|
|
1530
|
-
qwen: { name: 'QwenCode (qwen)',
|
|
1531
|
-
mimo: { name: 'MimoCode (mimo)',
|
|
2271
|
+
claude: { name: 'Claude Code', path: resolveBinary('claude', ['~/.local/bin/claude', '/usr/local/bin/claude', '/opt/homebrew/bin/claude']), models: [], modelsCmd: b => `"${b}" --list-models 2>/dev/null || echo ''` },
|
|
2272
|
+
opencode: { name: 'OpenCode', path: resolveBinary('opencode', ['~/.opencode/bin/opencode', '/usr/local/bin/opencode']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null` },
|
|
2273
|
+
codex: { name: 'Codex (OpenAI)', path: resolveBinary('codex', ['~/Library/PhpWebStudy/env/node/bin/codex', '/usr/local/bin/codex']), models: [], modelsCmd: b => `"${b}" debug models 2>/dev/null` },
|
|
2274
|
+
agy: { name: 'Antigravity CLI (agy)', path: resolveBinary('agy', ['~/.local/bin/agy', '/usr/local/bin/agy', '/opt/homebrew/bin/agy']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null || echo ''` },
|
|
2275
|
+
qwen: { name: 'QwenCode (qwen)', path: resolveBinary('qwen', ['~/.local/bin/qwen', '/usr/local/bin/qwen', '/opt/homebrew/bin/qwen']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null || echo ''` },
|
|
2276
|
+
mimo: { name: 'MimoCode (mimo)', path: resolveBinary('mimo', ['~/.local/bin/mimo', '/usr/local/bin/mimo', '/opt/homebrew/bin/mimo', '~/.mimo/bin/mimo']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null || echo ''` },
|
|
1532
2277
|
};
|
|
1533
2278
|
|
|
1534
2279
|
for (const [key, eng] of Object.entries(engines)) {
|
|
@@ -1593,7 +2338,7 @@ function registerCliGlobally(imhcodeScriptPath) {
|
|
|
1593
2338
|
} else {
|
|
1594
2339
|
const localBinDir = path.join(os.homedir(), '.local', 'bin');
|
|
1595
2340
|
const imhBinDir = path.join(GLOBAL_DIR, 'bin');
|
|
1596
|
-
[localBinDir, imhBinDir].forEach(dir => { if (!fs.existsSync(dir)) { try { fs.mkdirSync(dir, { recursive: true }); } catch {
|
|
2341
|
+
[localBinDir, imhBinDir].forEach(dir => { if (!fs.existsSync(dir)) { try { fs.mkdirSync(dir, { recursive: true }); } catch {} } });
|
|
1597
2342
|
|
|
1598
2343
|
const shimContent = `#!/bin/sh\nexec node "${imhcodeScriptPath}" "$@"\n`;
|
|
1599
2344
|
[path.join(localBinDir, 'imhcode'), path.join(imhBinDir, 'imhcode')].forEach(t => {
|