imhcode 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/imhcode.js +114 -67
  2. package/package.json +1 -1
package/bin/imhcode.js CHANGED
@@ -2377,15 +2377,37 @@ function detectSprintDocs(cwd) {
2377
2377
  return fs.readdirSync(docsDir).some(f => /^sprint-\d+$/i.test(f));
2378
2378
  }
2379
2379
 
2380
+ function isSprintComplete(cwd, sprintNum) {
2381
+ const progressPath = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`, 'progress.md');
2382
+ if (!fs.existsSync(progressPath)) return false;
2383
+ try {
2384
+ const content = fs.readFileSync(progressPath, 'utf8');
2385
+ return !/-\s*\[\s*\]/i.test(content);
2386
+ } catch {
2387
+ return false;
2388
+ }
2389
+ }
2390
+
2380
2391
  function detectCurrentSprint(cwd) {
2381
2392
  const docsDir = path.join(cwd, DOCS_DIR);
2382
2393
  if (!fs.existsSync(docsDir)) return 1;
2383
- let max = 0;
2394
+
2395
+ const sprintNums = [];
2384
2396
  for (const f of fs.readdirSync(docsDir)) {
2385
2397
  const m = f.match(/^sprint-(\d+)$/i);
2386
- if (m) max = Math.max(max, parseInt(m[1], 10));
2398
+ if (m) sprintNums.push(parseInt(m[1], 10));
2399
+ }
2400
+
2401
+ if (sprintNums.length === 0) return 1;
2402
+ sprintNums.sort((a, b) => a - b);
2403
+
2404
+ for (const num of sprintNums) {
2405
+ if (!isSprintComplete(cwd, num)) {
2406
+ return num;
2407
+ }
2387
2408
  }
2388
- return max || 1;
2409
+
2410
+ return sprintNums[sprintNums.length - 1];
2389
2411
  }
2390
2412
 
2391
2413
  function detectLastSprint(cwd) {
@@ -3063,9 +3085,9 @@ async function runTuiCommand() {
3063
3085
 
3064
3086
  // Step 2: Requirements
3065
3087
  if (state.hasConfig && !state.hasStart) {
3066
- items.push({ id: 'write', name: 'Write Requirements', desc: '⚠️ Edit docs/start.md (Required)', highlight: true });
3088
+ items.push({ id: 'write', name: 'Write Requirements', desc: '⚠️ Input requirements & scope (Required)', highlight: true });
3067
3089
  } else if (state.hasConfig) {
3068
- items.push({ id: 'write', name: 'Write Requirements', desc: 'Edit docs/start.md requirements' });
3090
+ items.push({ id: 'write', name: 'Write Requirements', desc: 'Input requirements & scope answers' });
3069
3091
  }
3070
3092
 
3071
3093
  // Step 3: Plan & Brainstorm
@@ -3170,14 +3192,14 @@ async function runTuiCommand() {
3170
3192
  const infoLeft = ` Project: ${projectName}`;
3171
3193
  const infoRight = `Path: ${cwd.length > 35 ? '...' + cwd.slice(-32) : cwd} `;
3172
3194
  const paddingInfo = width - 4 - infoLeft.length - infoRight.length;
3173
- console.log(ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + infoLeft + ' '.repeat(paddingInfo) + ANSI.dim + infoRight + ANSI.cyan + `│` + ANSI.reset);
3195
+ console.log(ANSI.cyan + ` │` + ANSI.reset + ANSI.bold + infoLeft + ' '.repeat(Math.max(0, paddingInfo)) + ANSI.dim + infoRight + ANSI.cyan + `│` + ANSI.reset);
3174
3196
 
3175
3197
  const statusLine = ` Status: ${statusStr}`;
3176
3198
  const engineLine = `Engine: ${engineStr} `;
3177
3199
  const statusClean = statusLine.replace(/\x1b\[[0-9;]*m/g, '');
3178
3200
  const engineClean = engineLine.replace(/\x1b\[[0-9;]*m/g, '');
3179
3201
  const paddingStatus = width - 4 - statusClean.length - engineClean.length;
3180
- console.log(ANSI.cyan + ` │` + ANSI.reset + statusLine + ' '.repeat(paddingStatus) + ANSI.dim + engineLine + ANSI.cyan + `│` + ANSI.reset);
3202
+ console.log(ANSI.cyan + ` │` + ANSI.reset + statusLine + ' '.repeat(Math.max(0, paddingStatus)) + ANSI.dim + engineLine + ANSI.cyan + `│` + ANSI.reset);
3181
3203
 
3182
3204
  console.log(ANSI.cyan + ` ├${'─'.repeat(width - 4)}┤` + ANSI.reset);
3183
3205
 
@@ -3225,7 +3247,7 @@ async function runTuiCommand() {
3225
3247
  function centerText(text, width) {
3226
3248
  const cleanText = text.replace(/\x1b\[[0-9;]*m/g, '');
3227
3249
  const padding = Math.max(0, Math.floor((width - cleanText.length) / 2));
3228
- return ' '.repeat(padding) + text + ' '.repeat(width - cleanText.length - padding);
3250
+ return ' '.repeat(padding) + text + ' '.repeat(Math.max(0, width - cleanText.length - padding));
3229
3251
  }
3230
3252
 
3231
3253
  // Set up standard input listener
@@ -3346,69 +3368,94 @@ async function runTuiCommand() {
3346
3368
  process.stdin.pause();
3347
3369
  process.stdout.write(ANSI.showCursor + '\n');
3348
3370
 
3349
- // Let user choose how they want to edit
3350
- console.log('Requirements Editor Options:');
3351
- console.log(' [1] Open docs/start.md in default terminal editor (nano, vi, notepad)');
3352
- console.log(' [2] Open docs/start.md in VS Code (code)');
3353
- console.log(' [3] Edit project description directly in terminal');
3371
+ console.log(ANSI.cyan + '\n🕌 IMH-Code Write Requirements' + ANSI.reset);
3372
+ console.log('Enter your project description below (press Enter on empty line to finish):\n');
3354
3373
 
3355
- const rlEdit = readline.createInterface({ input: process.stdin, output: process.stdout });
3356
- const choice = await new Promise(res => rlEdit.question('\nSelect option [1-3] (default: 1): ', res));
3357
- rlEdit.close();
3358
-
3359
- const startMdPath = path.join(cwd, START_MD);
3374
+ const lines = [];
3375
+ const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
3360
3376
 
3361
- if (choice === '2') {
3362
- try {
3363
- execSync(`code "${startMdPath}"`, { stdio: 'inherit' });
3364
- console.log('📂 Opened docs/start.md in VS Code.');
3365
- } catch {
3366
- console.log('⚠️ Failed to launch VS Code ("code" CLI not in path). Fallback to standard editor.');
3367
- const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3368
- execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3369
- }
3370
- } else if (choice === '3') {
3371
- console.log('\nEnter your new project description below (press Enter on empty line to finish):\n');
3372
- const lines = [];
3373
- const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
3374
- while (true) {
3375
- const line = await new Promise(res => rlInput.question('> ', res));
3376
- if (line.trim() === '') break;
3377
- lines.push(line);
3378
- }
3377
+ while (true) {
3378
+ const line = await new Promise(res => rlInput.question('> ', res));
3379
+ if (line.trim() === '') break;
3380
+ lines.push(line);
3381
+ }
3382
+
3383
+ const desc = lines.join('\n');
3384
+ if (desc.trim() === '') {
3385
+ console.log('⚠️ No requirements entered. Aborting.');
3379
3386
  rlInput.close();
3380
-
3381
- if (lines.length > 0) {
3382
- // Write to docs/start.md within markers
3383
- let startContent = '';
3384
- if (fs.existsSync(startMdPath)) {
3385
- startContent = fs.readFileSync(startMdPath, 'utf8');
3386
- }
3387
-
3388
- const desc = lines.join('\n');
3389
- const markerStart = '<!-- WRITE_PROMPT_HERE -->';
3390
- const markerEnd = '<!-- END_PROMPT -->';
3391
-
3392
- if (startContent.includes(markerStart) && startContent.includes(markerEnd)) {
3393
- const before = startContent.split(markerStart)[0];
3394
- const after = startContent.split(markerEnd)[1];
3395
- const newContent = `${before}${markerStart}\n${desc}\n${markerEnd}${after}`;
3396
- fs.writeFileSync(startMdPath, newContent, 'utf8');
3397
- } else {
3398
- // Write a simple template with markers
3399
- const newContent = `# 🕌 IMH-Code — Project Start\n\n${markerStart}\n${desc}\n${markerEnd}\n`;
3400
- fs.writeFileSync(startMdPath, newContent, 'utf8');
3401
- }
3402
- console.log(' Requirements updated successfully in docs/start.md!');
3403
- }
3404
- } else {
3405
- const editor = process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');
3406
- try {
3407
- execSync(`${editor} "${startMdPath}"`, { stdio: 'inherit' });
3408
- } catch (err) {
3409
- console.error(`❌ Failed to launch editor: ${err.message}`);
3410
- }
3387
+ inSubMode = false;
3388
+ if (isRaw) process.stdin.setRawMode(true);
3389
+ process.stdin.resume();
3390
+ renderMenu();
3391
+ break;
3392
+ }
3393
+
3394
+ // Ask for backend
3395
+ let backend = 'yes';
3396
+ while (true) {
3397
+ const ans = await new Promise(res => rlInput.question('\nDo you need a backend API / server-side logic? (yes/no/unsure) [default: yes]: ', res));
3398
+ const cleanAns = ans.trim().toLowerCase();
3399
+ if (cleanAns === '') { backend = 'yes'; break; }
3400
+ if (['yes', 'no', 'unsure'].includes(cleanAns)) { backend = cleanAns; break; }
3401
+ console.log('❌ Invalid input. Please enter yes, no, or unsure.');
3402
+ }
3403
+
3404
+ // Ask for mobile
3405
+ let mobile = 'no';
3406
+ while (true) {
3407
+ const ans = await new Promise(res => rlInput.question('Do you need a mobile app (iOS/Android)? (yes/no) [default: no]: ', res));
3408
+ const cleanAns = ans.trim().toLowerCase();
3409
+ if (cleanAns === '') { mobile = 'no'; break; }
3410
+ if (['yes', 'no'].includes(cleanAns)) { mobile = cleanAns; break; }
3411
+ console.log('❌ Invalid input. Please enter yes or no.');
3412
+ }
3413
+
3414
+ rlInput.close();
3415
+
3416
+ // Write start.md template
3417
+ const startMdPath = path.join(cwd, START_MD);
3418
+ const startMdContent = [
3419
+ '# 🕌 IMH-Code — Project Start',
3420
+ '',
3421
+ '> **Imam Hussain Coding Harness Platform**',
3422
+ '',
3423
+ '---',
3424
+ '',
3425
+ '## ⚡ Quick Scope Check',
3426
+ '',
3427
+ '**Do you need a backend API / server-side logic?**',
3428
+ `> **Answer:** ${backend}`,
3429
+ '',
3430
+ '**Do you need a mobile app (iOS/Android)?**',
3431
+ `> **Answer:** ${mobile}`,
3432
+ '',
3433
+ '---',
3434
+ '',
3435
+ '## 📝 Your Project Description',
3436
+ '',
3437
+ '<!-- WRITE_PROMPT_HERE -->',
3438
+ desc,
3439
+ '<!-- END_PROMPT -->',
3440
+ '',
3441
+ '---',
3442
+ '',
3443
+ '## 🚀 Next Step',
3444
+ '',
3445
+ 'After filling in the scope and your description, run:',
3446
+ '',
3447
+ '```bash',
3448
+ 'imhcode plan',
3449
+ '```'
3450
+ ].join('\n');
3451
+
3452
+ // Ensure directories exist
3453
+ const docsDir = path.dirname(startMdPath);
3454
+ if (!fs.existsSync(docsDir)) {
3455
+ fs.mkdirSync(docsDir, { recursive: true });
3411
3456
  }
3457
+ fs.writeFileSync(startMdPath, startMdContent, 'utf8');
3458
+ console.log('\n✅ Requirements updated successfully in docs/start.md!');
3412
3459
 
3413
3460
  inSubMode = false;
3414
3461
  if (isRaw) process.stdin.setRawMode(true);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imhcode",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "IMH-Code — Imam Hussain Coding Harness Platform. A fast-first multi-agent AI coding framework with intelligent model routing. 19 generic role-based agents (planner, nextjs-executor, laravel-executor, etc.), configurable testing strategy, and 7 token-saving optimizations for rapid MVP development.",
5
5
  "main": "index.js",
6
6
  "bin": {