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.
- package/bin/imhcode.js +240 -48
- 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
|
-
|
|
1666
|
-
const
|
|
1667
|
-
const
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
const
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
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.
|
|
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": {
|