imhcode 1.0.2 → 1.0.4

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 +240 -48
  2. package/package.json +1 -1
package/bin/imhcode.js CHANGED
@@ -296,6 +296,7 @@ async function runExecuteCommand(restArgs) {
296
296
 
297
297
  console.log(`─`.repeat(60));
298
298
  try {
299
+ await markSprintStarted(cwd, sprintNum);
299
300
  execSync(`sh "${masterScript}"`, { stdio: 'inherit', cwd });
300
301
  console.log(`─`.repeat(60));
301
302
  console.log(`\n🏁 Sprint ${sprintNum} execution complete!\n`);
@@ -704,6 +705,11 @@ async function cmdRun(orc, args) {
704
705
  const outputIdx = restArgs.indexOf('--output');
705
706
  const outputDir = outputIdx >= 0 ? restArgs[outputIdx + 1] : undefined;
706
707
 
708
+ const sprintIdx = restArgs.indexOf('--sprint');
709
+ const sprintVal = sprintIdx >= 0 ? parseInt(restArgs[sprintIdx + 1], 10) : undefined;
710
+ const taskIdx = restArgs.indexOf('--task');
711
+ const taskVal = taskIdx >= 0 ? parseInt(restArgs[taskIdx + 1], 10) : undefined;
712
+
707
713
  const agentsDir = resolveAgentsDir();
708
714
  const cwd = process.cwd();
709
715
  const config = loadLocalConfig(cwd);
@@ -736,6 +742,10 @@ async function cmdRun(orc, args) {
736
742
 
737
743
  const agent = orc.getAgent(agents, agentId);
738
744
 
745
+ if (live && sprintVal !== undefined) {
746
+ await markSprintStarted(cwd, sprintVal);
747
+ }
748
+
739
749
  // Pass the routed engine + model to runAgent (Fix 0b Part A)
740
750
  const result = await orc.runAgent(
741
751
  agent, task,
@@ -743,6 +753,12 @@ async function cmdRun(orc, args) {
743
753
  criteria
744
754
  );
745
755
 
756
+ if (!result.dryRun && result.errors.length === 0) {
757
+ if (sprintVal !== undefined && taskVal !== undefined) {
758
+ await markTaskCompleted(cwd, sprintVal, taskVal);
759
+ }
760
+ }
761
+
746
762
  console.log('\n' + '─'.repeat(72));
747
763
  if (result.errors.length > 0 && !result.dryRun) {
748
764
  console.error(`\n❌ [IMH-Code] Execution failed with errors:`);
@@ -1662,28 +1678,40 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1662
1678
  const testingModeRaw = extractAnswer(brainstormContent, 'Q31', 'A').trim().toUpperCase().charAt(0);
1663
1679
  const detectedTesting = testingModeRaw === 'B' ? 'balanced' : testingModeRaw === 'C' ? 'strict' : 'fast';
1664
1680
 
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
+ const ansQ8 = extractAnswer(brainstormContent, 'Q8', 'Dark Premium').trim();
1682
+ const ansQ9 = extractAnswer(brainstormContent, 'Q9', 'Modern SaaS').trim();
1683
+ const ansQ12 = extractAnswer(brainstormContent, 'Q12', 'system toggle').toLowerCase();
1684
+ const ansQ14 = extractAnswer(brainstormContent, 'Q14', 'no').toLowerCase();
1685
+ const ansQ16 = extractAnswer(brainstormContent, 'Q16', 'no').toLowerCase();
1686
+ const ansQ21 = extractAnswer(brainstormContent, 'Q21', 'no').toLowerCase();
1687
+ const ansQ22 = extractAnswer(brainstormContent, 'Q22', 'no').toLowerCase();
1688
+ const ansQ25 = extractAnswer(brainstormContent, 'Q25', 'no').toLowerCase();
1689
+
1690
+ const hasFrontend = true; // Always assume frontend
1691
+
1692
+ // Robust scope detection: only true if user answer is not "no", "none", or standard default fallback placeholder
1693
+ const hasBackend = (() => {
1694
+ if (ansQ16.includes('no') || ansQ16.includes('none') || ansQ16.includes('edit if needed') || ansQ16.trim() === '') {
1695
+ return false;
1696
+ }
1697
+ return true;
1681
1698
  })();
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';
1699
+
1700
+ const hasMobile = (() => {
1701
+ if (ansQ25.includes('no') || ansQ25.includes('none') || ansQ25.includes('edit if needed') || ansQ25.trim() === '') {
1702
+ return false;
1703
+ }
1704
+ return true;
1685
1705
  })();
1686
1706
 
1707
+ const needsWorktrees = ansQ14.includes('yes') || ansQ14.includes('true');
1708
+ const hasPayments = !ansQ22.includes('no') && !ansQ22.includes('none') && !ansQ22.includes('edit if needed') && ansQ22.trim().length > 0;
1709
+ const hasRealtime = !ansQ21.includes('no') && !ansQ21.includes('none') && !ansQ21.includes('edit if needed') && ansQ21.trim().length > 0;
1710
+
1711
+ const designStyle = ansQ9.replace(/\*\(.*?\)\*/g, '').trim() || 'Modern SaaS';
1712
+ const colorStyle = ansQ8.replace(/\*\(.*?\)\*/g, '').trim() || 'Dark Premium';
1713
+ const darkmode = ansQ12.replace(/\*\(.*?\)\*/g, '').trim() || 'system toggle';
1714
+
1687
1715
  // Save testing mode to config
1688
1716
  if (config) {
1689
1717
  config.testing_mode = detectedTesting;
@@ -1691,21 +1719,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1691
1719
  try { fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8'); } catch {}
1692
1720
  }
1693
1721
 
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
- }
1722
+ // Create workspace directories lazily (only worktrees since it's a layout helper, NOT frontend/backend which must remain empty for official CLI scaffolding)
1709
1723
  if (needsWorktrees) {
1710
1724
  const wtDir = path.join(cwd, '.worktrees');
1711
1725
  if (!fs.existsSync(wtDir)) {
@@ -1716,7 +1730,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1716
1730
 
1717
1731
  // Generate PROJECT_BRIEF.md
1718
1732
  const briefContent = generateProjectBrief(userPrompt, brainstormContent, {
1719
- hasFrontend, hasBackend, hasMobile, designStyle, colorStyle, detectedTesting
1733
+ hasFrontend, hasBackend, hasMobile, designStyle, colorStyle, darkmode, detectedTesting
1720
1734
  });
1721
1735
  fs.writeFileSync(path.join(cwd, BRIEF_MD), briefContent, 'utf8');
1722
1736
  console.log(` ✅ Created PROJECT_BRIEF.md`);
@@ -1724,7 +1738,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1724
1738
  // Try LLM-powered sprint generation first (Fix 0)
1725
1739
  const llmSprintResult = await tryLLMSprintGeneration(
1726
1740
  cwd, userPrompt, brainstormContent, config,
1727
- { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting, needsWorktrees }
1741
+ { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, darkmode, detectedTesting, needsWorktrees }
1728
1742
  );
1729
1743
 
1730
1744
  let lastSprintNum;
@@ -1736,7 +1750,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1736
1750
  // Fix 5: Fallback to smart static generation
1737
1751
  lastSprintNum = await generateStaticSprintPlans(
1738
1752
  cwd,
1739
- { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting },
1753
+ { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, darkmode, detectedTesting },
1740
1754
  config
1741
1755
  );
1742
1756
  }
@@ -1750,7 +1764,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1750
1764
 
1751
1765
  // Update compact context
1752
1766
  const contextContent = buildContextContent(userPrompt, {
1753
- hasFrontend, hasBackend, hasMobile, detectedTesting, lastSprintNum, designStyle, colorStyle
1767
+ hasFrontend, hasBackend, hasMobile, detectedTesting, lastSprintNum, designStyle, colorStyle, darkmode
1754
1768
  });
1755
1769
  fs.mkdirSync(path.join(cwd, LOCAL_DIR_NAME), { recursive: true });
1756
1770
  fs.writeFileSync(path.join(cwd, CONTEXT_MD), contextContent, 'utf8');
@@ -1761,7 +1775,7 @@ async function generateSprintPlans(cwd, userPrompt, brainstormContent, config) {
1761
1775
  * Attempt LLM-generated sprint plans. Returns { sprintCount } on success, null on failure.
1762
1776
  */
1763
1777
  async function tryLLMSprintGeneration(cwd, userPrompt, brainstormContent, config, scope) {
1764
- const { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, detectedTesting } = scope;
1778
+ const { hasFrontend, hasBackend, hasMobile, hasPayments, hasRealtime, designStyle, colorStyle, darkmode, detectedTesting } = scope;
1765
1779
 
1766
1780
  const agentList = [
1767
1781
  'planner (planning)', 'designer (frontend)', 'nextjs-executor (frontend)', 'react-executor (frontend)',
@@ -1788,7 +1802,7 @@ ${userPrompt}
1788
1802
  ${brainstormContent}
1789
1803
 
1790
1804
  ## Scope
1791
- - Frontend: ${hasFrontend ? 'YES' : 'NO'} (Design style: ${designStyle}, Colors: ${colorStyle})
1805
+ - Frontend: ${hasFrontend ? 'YES' : 'NO'} (Design style: ${designStyle}, Colors: ${colorStyle}, Dark Mode Strategy: ${darkmode})
1792
1806
  - Backend: ${hasBackend ? 'YES' : 'NO'}
1793
1807
  - Mobile: ${hasMobile ? 'YES' : 'NO'}
1794
1808
  - Payments: ${hasPayments ? 'YES' : 'NO'}
@@ -1838,7 +1852,8 @@ Return ONLY this JSON structure (no markdown, no explanation):
1838
1852
  8. Max 6 tasks per sprint for clean execution
1839
1853
  9. If design style is Glassmorphism/Neumorphism — include a designer task for design tokens in Sprint 1
1840
1854
  10. If payments needed — include payment integration task in a sprint
1841
- 11. If realtime needed — include WebSocket/SSE task in a sprint`;
1855
+ 11. If realtime needed — include WebSocket/SSE task in a sprint
1856
+ 12. Under Frontend, STRICTLY respect the Dark Mode Strategy: if strategy is "light only", do NOT generate any dark mode setups, variables, toggles or dark styles. If strategy is "always dark", design the UI strictly for dark backgrounds.`;
1842
1857
 
1843
1858
  const llmOutput = await invokePlanningLLM(llmPrompt, config, cwd);
1844
1859
  if (!llmOutput) return null;
@@ -2021,7 +2036,16 @@ ${tasks.map((t, i) => `- [ ] Task ${i+1}: ${t.task} [\`${t.agent}\`]`).join('\n'
2021
2036
  # Model: ${routedModel || 'default'} via ${routedEngine || 'default'}
2022
2037
  # Tier: ${t.tier}
2023
2038
  CWD="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
2024
- cd "$CWD/../../../.."
2039
+ cd "$CWD/../../.."
2040
+
2041
+ # Check if task is already completed
2042
+ PROGRESS_FILE="$CWD/../progress.md"
2043
+ if [ -f "$PROGRESS_FILE" ]; then
2044
+ if grep -q -e "- \\[[xX]\\] Task ${taskNum}:" "$PROGRESS_FILE"; then
2045
+ echo "✅ Task ${taskNum} is already completed. Skipping."
2046
+ exit 0
2047
+ fi
2048
+ fi
2025
2049
 
2026
2050
  TASK="${taskDesc.replace(/"/g, '\\"')}"
2027
2051
 
@@ -2031,9 +2055,9 @@ echo " Model: ${routedModel || 'default'} via ${routedEngine || 'default'}"
2031
2055
  echo " Tier: ${t.tier}"
2032
2056
 
2033
2057
  if command -v imhcode >/dev/null 2>&1; then
2034
- imhcode agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag}
2058
+ imhcode agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag} --sprint ${sprintNum} --task ${taskNum}
2035
2059
  else
2036
- node "$(npm root -g)/imhcode/bin/imhcode.js" agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag}
2060
+ node "$(npm root -g)/imhcode/bin/imhcode.js" agent run ${t.agent} "$TASK" --live ${engineFlag} ${modelFlag} --sprint ${sprintNum} --task ${taskNum}
2037
2061
  fi
2038
2062
  `;
2039
2063
  fs.writeFileSync(path.join(tasksDir, `task_${taskNum}.sh`), taskScript, { mode: 0o755 });
@@ -2251,14 +2275,182 @@ async function updateCompactContext(cwd, completedSprint) {
2251
2275
  if (!fs.existsSync(contextPath)) return;
2252
2276
  try {
2253
2277
  let content = fs.readFileSync(contextPath, 'utf8');
2254
- content = content.replace(
2255
- /Current Sprint\nSprint \d+ \(not started\)/,
2256
- `Current Sprint\nSprint ${completedSprint + 1}`
2257
- );
2278
+ const pattern = /(## Current Sprint\r?\nSprint )\d+\s*(\([^)]*\))?/i;
2279
+ if (pattern.test(content)) {
2280
+ content = content.replace(pattern, `$1${completedSprint + 1} (not started)`);
2281
+ } else {
2282
+ content = content.replace(
2283
+ /Current Sprint\r?\nSprint \d+ \(not started\)/,
2284
+ `Current Sprint\nSprint ${completedSprint + 1} (not started)`
2285
+ );
2286
+ }
2258
2287
  fs.writeFileSync(contextPath, content, 'utf8');
2288
+
2289
+ // Update PROJECT_BRIEF.md Current Sprint
2290
+ const briefPath = path.join(cwd, BRIEF_MD);
2291
+ if (fs.existsSync(briefPath)) {
2292
+ try {
2293
+ let briefContent = fs.readFileSync(briefPath, 'utf8');
2294
+ const currentSprintPattern = /(Current Sprint:\s*Sprint\s*)\d+/i;
2295
+ if (currentSprintPattern.test(briefContent)) {
2296
+ briefContent = briefContent.replace(currentSprintPattern, `$1${completedSprint + 1}`);
2297
+ fs.writeFileSync(briefPath, briefContent, 'utf8');
2298
+ }
2299
+ } catch {}
2300
+ }
2259
2301
  } catch { /* non-critical */ }
2260
2302
  }
2261
2303
 
2304
+ async function markSprintStarted(cwd, sprintNum) {
2305
+ const progressPath = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`, 'progress.md');
2306
+ const briefPath = path.join(cwd, BRIEF_MD);
2307
+
2308
+ // 1. Update progress.md
2309
+ if (fs.existsSync(progressPath)) {
2310
+ try {
2311
+ let progressContent = fs.readFileSync(progressPath, 'utf8');
2312
+ if (progressContent.includes('Status: 🟡 Not Started')) {
2313
+ progressContent = progressContent.replace('Status: 🟡 Not Started', 'Status: 🟢 In Progress');
2314
+ const dateStr = new Date().toLocaleString();
2315
+ progressContent = progressContent.replace('Start: —', `Start: ${dateStr}`);
2316
+ fs.writeFileSync(progressPath, progressContent, 'utf8');
2317
+ console.log(` 🔄 Updated sprint progress to: 🟢 In Progress`);
2318
+ }
2319
+ } catch (err) {
2320
+ console.warn(` ⚠️ Could not update sprint progress.md: ${err.message}`);
2321
+ }
2322
+ }
2323
+
2324
+ // 2. Update PROJECT_BRIEF.md status
2325
+ if (fs.existsSync(briefPath)) {
2326
+ try {
2327
+ let briefContent = fs.readFileSync(briefPath, 'utf8');
2328
+ const rowPattern = new RegExp(`^\\|\\s*${sprintNum}\\s*\\|([^|]+)\\|([^|]+)\\|`, 'm');
2329
+ const match = briefContent.match(rowPattern);
2330
+ if (match) {
2331
+ const currentStatus = match[2].trim();
2332
+ if (currentStatus.includes('Not Started') || currentStatus.includes('Pending')) {
2333
+ briefContent = updateSprintLogTable(briefContent, sprintNum, '🟢 In Progress');
2334
+
2335
+ // Also check if Current Sprint needs updating
2336
+ const currentSprintPattern = /(Current Sprint:\s*Sprint\s*)\d+/i;
2337
+ if (currentSprintPattern.test(briefContent)) {
2338
+ briefContent = briefContent.replace(currentSprintPattern, `$1${sprintNum}`);
2339
+ }
2340
+
2341
+ fs.writeFileSync(briefPath, briefContent, 'utf8');
2342
+ console.log(` 🔄 Updated PROJECT_BRIEF.md sprint ${sprintNum} status to: 🟢 In Progress`);
2343
+ }
2344
+ }
2345
+ } catch (err) {
2346
+ console.warn(` ⚠️ Could not update PROJECT_BRIEF.md: ${err.message}`);
2347
+ }
2348
+ }
2349
+
2350
+ // 3. Update context.md status
2351
+ await updateContextSprintStatus(cwd, sprintNum, 'in progress');
2352
+ }
2353
+
2354
+ async function markTaskCompleted(cwd, sprintNum, taskNum) {
2355
+ const progressPath = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`, 'progress.md');
2356
+ const briefPath = path.join(cwd, BRIEF_MD);
2357
+
2358
+ if (!fs.existsSync(progressPath)) return;
2359
+
2360
+ try {
2361
+ let progressContent = fs.readFileSync(progressPath, 'utf8');
2362
+ const lines = progressContent.split('\n');
2363
+ let taskLineIndex = -1;
2364
+ for (let i = 0; i < lines.length; i++) {
2365
+ const taskPattern = new RegExp(`^-\\s*\\[\\s*\\]\\s*Task\\s*${taskNum}\\b`, 'i');
2366
+ if (taskPattern.test(lines[i])) {
2367
+ taskLineIndex = i;
2368
+ lines[i] = lines[i].replace(/-\s*\[\s*\]/, '- [x]');
2369
+ break;
2370
+ }
2371
+ }
2372
+
2373
+ if (taskLineIndex === -1) {
2374
+ console.warn(` ⚠️ Could not find Task ${taskNum} checkbox in progress.md`);
2375
+ return;
2376
+ }
2377
+
2378
+ progressContent = lines.join('\n');
2379
+ console.log(` ✅ Marked Task ${taskNum} as completed in progress.md`);
2380
+
2381
+ const incompleteTasksCount = (progressContent.match(/^-\s*\[\s*\]\s*Task\b/gim) || []).length;
2382
+
2383
+ if (incompleteTasksCount === 0) {
2384
+ progressContent = progressContent.replace(/Status:\s*🟢\s*In\s*Progress/i, 'Status: ✅ Complete');
2385
+ progressContent = progressContent.replace(/Status:\s*🟡\s*Not\s*Started/i, 'Status: ✅ Complete');
2386
+
2387
+ const dateStr = new Date().toLocaleString();
2388
+ progressContent = progressContent.replace('End: —', `End: ${dateStr}`);
2389
+
2390
+ console.log(` 🏁 All tasks in Sprint ${sprintNum} completed! Sprint is now ✅ Complete.`);
2391
+
2392
+ if (fs.existsSync(briefPath)) {
2393
+ try {
2394
+ let briefContent = fs.readFileSync(briefPath, 'utf8');
2395
+ briefContent = updateSprintLogTable(briefContent, sprintNum, '✅ Complete');
2396
+ fs.writeFileSync(briefPath, briefContent, 'utf8');
2397
+ console.log(` 🔄 Updated PROJECT_BRIEF.md sprint ${sprintNum} status to: ✅ Complete`);
2398
+ } catch (err) {
2399
+ console.warn(` ⚠️ Could not update PROJECT_BRIEF.md: ${err.message}`);
2400
+ }
2401
+ }
2402
+ }
2403
+
2404
+ fs.writeFileSync(progressPath, progressContent, 'utf8');
2405
+ } catch (err) {
2406
+ console.warn(` ⚠️ Could not update task progress: ${err.message}`);
2407
+ }
2408
+ }
2409
+
2410
+ function updateSprintLogTable(content, sprintNum, newStatus) {
2411
+ const lines = content.split('\n');
2412
+ let inSprintLog = false;
2413
+ for (let i = 0; i < lines.length; i++) {
2414
+ const line = lines[i];
2415
+ if (line.startsWith('## Sprint Log')) {
2416
+ inSprintLog = true;
2417
+ continue;
2418
+ }
2419
+ if (inSprintLog && line.startsWith('#')) {
2420
+ inSprintLog = false;
2421
+ }
2422
+ if (inSprintLog) {
2423
+ const match = line.match(new RegExp(`^\\|\\s*${sprintNum}\\s*\\|([^|]+)\\|([^|]+)\\|`));
2424
+ if (match) {
2425
+ const title = match[1].trim();
2426
+ lines[i] = `| ${sprintNum} | ${title} | ${newStatus} |`;
2427
+ break;
2428
+ }
2429
+ }
2430
+ }
2431
+ return lines.join('\n');
2432
+ }
2433
+
2434
+ async function updateContextSprintStatus(cwd, sprintNum, status) {
2435
+ const contextPath = path.join(cwd, CONTEXT_MD);
2436
+ if (!fs.existsSync(contextPath)) return;
2437
+ try {
2438
+ let content = fs.readFileSync(contextPath, 'utf8');
2439
+ const pattern = /(## Current Sprint\r?\nSprint \d+)\s*\([^)]*\)/i;
2440
+ if (pattern.test(content)) {
2441
+ content = content.replace(pattern, `$1 (${status})`);
2442
+ } else {
2443
+ const simplePattern = /(## Current Sprint\r?\nSprint \d+)/i;
2444
+ if (simplePattern.test(content)) {
2445
+ content = content.replace(simplePattern, `$1 (${status})`);
2446
+ }
2447
+ }
2448
+ fs.writeFileSync(contextPath, content, 'utf8');
2449
+ } catch (err) {
2450
+ // non-critical
2451
+ }
2452
+ }
2453
+
2262
2454
  // ─── Utilities ────────────────────────────────────────────────────────────────
2263
2455
 
2264
2456
  function askQuestion(query) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imhcode",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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": {