imhcode 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/imhcode.js +395 -150
- package/dist/orchestrator/executor.d.ts +6 -1
- package/dist/orchestrator/executor.d.ts.map +1 -1
- package/dist/orchestrator/executor.js +73 -16
- package/dist/orchestrator/executor.js.map +1 -1
- package/dist/orchestrator/index.d.ts +9 -1
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/index.js +59 -12
- package/dist/orchestrator/index.js.map +1 -1
- package/dist/orchestrator/types.d.ts +1 -1
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/orchestrator/executor.ts +80 -15
- package/src/orchestrator/index.ts +62 -13
- package/src/orchestrator/types.ts +1 -1
package/bin/imhcode.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* imhcode agent list → List all discovered agents
|
|
13
13
|
* imhcode agent inspect <id> → Show full manifest + loaded skills
|
|
14
14
|
* imhcode agent run <id> "<task>" → Run agent (dry-run by default)
|
|
15
|
-
* --engine <cli> → Override engine (claude|opencode|codex|agy|qwen|mimo)
|
|
15
|
+
* --engine <cli> → Override engine (claude|opencode|codex|codex-fugu|agy|qwen|mimo)
|
|
16
16
|
* -m, --model <model> → Override model
|
|
17
17
|
* --live → Call real LLM
|
|
18
18
|
* --output <path> → Custom session output directory
|
|
@@ -40,6 +40,88 @@ const BRAINSTORM_MD = path.join(DOCS_DIR, 'brainstorming.md');
|
|
|
40
40
|
const BRIEF_MD = path.join(DOCS_DIR, 'PROJECT_BRIEF.md');
|
|
41
41
|
const CONTEXT_MD = path.join(LOCAL_DIR_NAME, 'context.md');
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Ranked priority lists per category.
|
|
45
|
+
* The algorithm scores each available model against these lists
|
|
46
|
+
* and selects the highest-ranked match found in the installed engines.
|
|
47
|
+
*/
|
|
48
|
+
const MODEL_PRIORITY_RANKS = {
|
|
49
|
+
frontend: [
|
|
50
|
+
// Engine Model substring to match (lowercase, no hyphens/dots)
|
|
51
|
+
['mimo', 'mimov25pro'],
|
|
52
|
+
['mimo', 'mimov25'],
|
|
53
|
+
['opencode', 'mimov25pro'],
|
|
54
|
+
['opencode', 'mimov25'],
|
|
55
|
+
['codex', 'gpt55'],
|
|
56
|
+
['codex', 'gpt5'],
|
|
57
|
+
['agy', 'claudeopus46'],
|
|
58
|
+
['agy', 'claudeopus'],
|
|
59
|
+
['opencode', 'gemini35flash'],
|
|
60
|
+
['opencode', 'gemini25pro'],
|
|
61
|
+
['claude', 'claudeopus46'],
|
|
62
|
+
['claude', 'opus'],
|
|
63
|
+
],
|
|
64
|
+
backend: [
|
|
65
|
+
['opencode', 'deepseekv4pro'],
|
|
66
|
+
['opencode', 'deepseekv4'],
|
|
67
|
+
['opencode', 'kimik27code'],
|
|
68
|
+
['opencode', 'kimik2'],
|
|
69
|
+
['opencode', 'qwen3coder480b'],
|
|
70
|
+
['opencode', 'qwen3coder'],
|
|
71
|
+
['codex', 'gpt55'],
|
|
72
|
+
['codex', 'gpt5'],
|
|
73
|
+
['opencode', 'gptoss120b'],
|
|
74
|
+
['qwen', 'qwen3codermax'],
|
|
75
|
+
['qwen', 'qwen3coder'],
|
|
76
|
+
['claude', 'claudesonnet46'],
|
|
77
|
+
['claude', 'sonnet'],
|
|
78
|
+
],
|
|
79
|
+
planning: [
|
|
80
|
+
['agy', 'claudeopus46'],
|
|
81
|
+
['agy', 'claudeopus'],
|
|
82
|
+
['claude', 'claudeopus46'],
|
|
83
|
+
['claude', 'opus'],
|
|
84
|
+
['codex', 'gpt55'],
|
|
85
|
+
['codex', 'gpt5'],
|
|
86
|
+
['opencode', 'gemini31propreview'],
|
|
87
|
+
['opencode', 'gemini31pro'],
|
|
88
|
+
['opencode', 'deepseekv4pro'],
|
|
89
|
+
],
|
|
90
|
+
testing: [
|
|
91
|
+
['codex-fugu', 'fuguultra'],
|
|
92
|
+
['codex-fugu', 'fugu'],
|
|
93
|
+
['codex', 'gpt55'],
|
|
94
|
+
['codex', 'gpt5'],
|
|
95
|
+
['agy', 'gptoss120bmedium'],
|
|
96
|
+
['agy', 'gptoss120b'],
|
|
97
|
+
['agy', 'claudeopus46'],
|
|
98
|
+
['agy', 'claudeopus'],
|
|
99
|
+
['opencode', 'deepseekv4pro'],
|
|
100
|
+
['claude', 'claudeopus46'],
|
|
101
|
+
],
|
|
102
|
+
review: [
|
|
103
|
+
['codex-fugu', 'fuguultra'],
|
|
104
|
+
['codex-fugu', 'fugu'],
|
|
105
|
+
['codex', 'gpt55'],
|
|
106
|
+
['codex', 'gpt5'],
|
|
107
|
+
['agy', 'claudesonnet46'],
|
|
108
|
+
['agy', 'claudesonnet'],
|
|
109
|
+
['opencode', 'gptoss120b'],
|
|
110
|
+
['claude', 'claudesonnet46'],
|
|
111
|
+
['opencode', 'deepseekv4pro'],
|
|
112
|
+
],
|
|
113
|
+
fast: [
|
|
114
|
+
['opencode', 'deepseekv4flash'],
|
|
115
|
+
['opencode', 'deepseekv4flash'],
|
|
116
|
+
['opencode', 'gemini35flash'],
|
|
117
|
+
['codex', 'gpt54mini'],
|
|
118
|
+
['codex', 'gptmini'],
|
|
119
|
+
['qwen', 'qwen3coderflash'],
|
|
120
|
+
['claude', 'claudehaiku35'],
|
|
121
|
+
['claude', 'haiku'],
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
|
|
43
125
|
// ─── Entry Point ───────────────────────────────────────────────────────────────
|
|
44
126
|
|
|
45
127
|
const args = process.argv.slice(2);
|
|
@@ -155,7 +237,7 @@ function printGeneralHelp() {
|
|
|
155
237
|
${CLI_CMD} agent inspect <id> → Show agent manifest + loaded skills
|
|
156
238
|
${CLI_CMD} agent run <id> "<task>" → Run agent (dry-run by default)
|
|
157
239
|
--live → Call real LLM
|
|
158
|
-
--engine <cli> → Override engine (claude|opencode|codex|agy|qwen|mimo)
|
|
240
|
+
--engine <cli> → Override engine (claude|opencode|codex|codex-fugu|agy|qwen|mimo)
|
|
159
241
|
-m, --model <model> → Override model
|
|
160
242
|
--output <path> → Save session to custom path
|
|
161
243
|
|
|
@@ -1017,6 +1099,132 @@ async function runInit() {
|
|
|
1017
1099
|
console.log(` Created docs/start.md (write your project here)`);
|
|
1018
1100
|
}
|
|
1019
1101
|
|
|
1102
|
+
const globalConfigPath = path.join(GLOBAL_DIR, 'global.config.json');
|
|
1103
|
+
let useGlobal = false;
|
|
1104
|
+
let globalConfig = null;
|
|
1105
|
+
|
|
1106
|
+
if (fs.existsSync(globalConfigPath)) {
|
|
1107
|
+
try {
|
|
1108
|
+
globalConfig = JSON.parse(fs.readFileSync(globalConfigPath, 'utf8'));
|
|
1109
|
+
} catch {}
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const isInteractive = process.stdout.isTTY;
|
|
1113
|
+
|
|
1114
|
+
if (globalConfig && isInteractive) {
|
|
1115
|
+
console.log(`\n🕌 Found saved global default configuration:`);
|
|
1116
|
+
console.log(` Primary Engine: ${globalConfig.primary_engine} (${globalConfig.default_model || 'auto'})`);
|
|
1117
|
+
console.log(` Testing Route: ${globalConfig.model_routing?.testing?.model || 'auto'} via ${globalConfig.model_routing?.testing?.engine || 'auto'}`);
|
|
1118
|
+
console.log(` Review Route: ${globalConfig.model_routing?.review?.model || 'auto'} via ${globalConfig.model_routing?.review?.engine || 'auto'}`);
|
|
1119
|
+
|
|
1120
|
+
const ans = await askQuestion('\nUse default global engine and model routing? [Y/n] ');
|
|
1121
|
+
if (ans.toLowerCase() !== 'n' && ans.toLowerCase() !== 'no') {
|
|
1122
|
+
useGlobal = true;
|
|
1123
|
+
}
|
|
1124
|
+
} else if (globalConfig) {
|
|
1125
|
+
useGlobal = true;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
if (useGlobal && globalConfig) {
|
|
1129
|
+
console.log('\n✅ Applying global default configuration...');
|
|
1130
|
+
|
|
1131
|
+
const dirsToCreate = [
|
|
1132
|
+
DOCS_DIR,
|
|
1133
|
+
LOCAL_DIR_NAME,
|
|
1134
|
+
path.join(LOCAL_DIR_NAME, 'commands'),
|
|
1135
|
+
path.join(LOCAL_DIR_NAME, 'sessions'),
|
|
1136
|
+
];
|
|
1137
|
+
for (const dir of dirsToCreate) {
|
|
1138
|
+
const dirPath = path.join(cwd, dir);
|
|
1139
|
+
if (!fs.existsSync(dirPath)) {
|
|
1140
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
1141
|
+
console.log(` Created local directory: ${dir}`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const startMdPath = path.join(cwd, START_MD);
|
|
1146
|
+
if (!fs.existsSync(startMdPath)) {
|
|
1147
|
+
const startMdContent = [
|
|
1148
|
+
'# 🕌 IMH-Code — Project Start',
|
|
1149
|
+
'',
|
|
1150
|
+
'> **Imam Hussain Coding Harness Platform**',
|
|
1151
|
+
'> Answer the scope questions below, write your description, then run `imhcode plan`.',
|
|
1152
|
+
'',
|
|
1153
|
+
'---',
|
|
1154
|
+
'',
|
|
1155
|
+
'## ⚡ Quick Scope Check',
|
|
1156
|
+
'',
|
|
1157
|
+
'**Do you need a backend API / server-side logic?**',
|
|
1158
|
+
'> **Answer:** yes *(change to: yes / no / unsure)*',
|
|
1159
|
+
'',
|
|
1160
|
+
'**Do you need a mobile app (iOS/Android)?**',
|
|
1161
|
+
'> **Answer:** no *(change to: yes / no)*',
|
|
1162
|
+
'',
|
|
1163
|
+
'---',
|
|
1164
|
+
'',
|
|
1165
|
+
'## 📝 Your Project Description',
|
|
1166
|
+
'',
|
|
1167
|
+
'Write your complete project idea below. Be as detailed as possible.',
|
|
1168
|
+
'Include: what you\'re building, who it\'s for, key features, preferred stack,',
|
|
1169
|
+
'design preferences, integrations needed, business constraints.',
|
|
1170
|
+
'',
|
|
1171
|
+
'<!-- WRITE_PROMPT_HERE -->',
|
|
1172
|
+
'I want to build a SaaS dashboard for managing hotel room bookings with real-time availability,',
|
|
1173
|
+
'a Next.js frontend, Laravel API backend, and PostgreSQL database. Target users are hotel managers.',
|
|
1174
|
+
'Key features: room calendar, booking CRUD, guest management, reporting, email notifications.',
|
|
1175
|
+
'<!-- END_PROMPT -->',
|
|
1176
|
+
'',
|
|
1177
|
+
'---',
|
|
1178
|
+
'',
|
|
1179
|
+
'## 🚀 Next Step',
|
|
1180
|
+
'',
|
|
1181
|
+
'After filling in the scope and your description, run:',
|
|
1182
|
+
'',
|
|
1183
|
+
'```bash',
|
|
1184
|
+
'imhcode plan',
|
|
1185
|
+
'```',
|
|
1186
|
+
'',
|
|
1187
|
+
'IMH-Code will invoke your configured **planning AI model** (e.g. Claude Opus, GPT-5.5)',
|
|
1188
|
+
'to generate `docs/brainstorming.md` with smart, project-specific questions and answers.',
|
|
1189
|
+
].join('\n');
|
|
1190
|
+
fs.writeFileSync(startMdPath, startMdContent, 'utf8');
|
|
1191
|
+
console.log(` Created docs/start.md (write your project here)`);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
const localGitignore = path.join(cwd, '.gitignore');
|
|
1195
|
+
if (!fs.existsSync(localGitignore)) {
|
|
1196
|
+
const gitignoreTemplate = path.join(packageRoot, '.gitignore.template');
|
|
1197
|
+
if (fs.existsSync(gitignoreTemplate)) {
|
|
1198
|
+
fs.copyFileSync(gitignoreTemplate, localGitignore);
|
|
1199
|
+
console.log(` Created local .gitignore`);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
registerCliGlobally(imhcodeScriptPath);
|
|
1204
|
+
|
|
1205
|
+
const configPath = path.join(cwd, CONFIG_FILE);
|
|
1206
|
+
fs.writeFileSync(configPath, JSON.stringify(globalConfig, null, 2), 'utf8');
|
|
1207
|
+
console.log(`\n💾 Configuration saved: ${configPath}`);
|
|
1208
|
+
|
|
1209
|
+
console.log(`\n✅ ${PLATFORM_NAME} initialized successfully!`);
|
|
1210
|
+
console.log(`─`.repeat(60));
|
|
1211
|
+
console.log(`\n🕌 HOW TO BUILD WITH IMH-CODE:\n`);
|
|
1212
|
+
console.log(` 1. Open docs/start.md → Answer scope questions + write your description`);
|
|
1213
|
+
console.log(` 2. Run: imhcode plan`);
|
|
1214
|
+
console.log(` → Your planning AI generates brainstorming.md`);
|
|
1215
|
+
console.log(` 3. Open docs/brainstorming.md → Review/edit AI-recommended answers`);
|
|
1216
|
+
console.log(` 4. Run: imhcode plan`);
|
|
1217
|
+
console.log(` → Your planning AI generates sprint plans with correct agent routing`);
|
|
1218
|
+
console.log(` 5. Run: imhcode execute 1 → Sprint 1 (frontend tasks)`);
|
|
1219
|
+
console.log(` 6. Run: imhcode execute 2 → Sprint 2 (backend tasks)`);
|
|
1220
|
+
console.log(` 7. Run: imhcode test → Final testing + security + SEO`);
|
|
1221
|
+
console.log(` 8. Run: imhcode report → Generate PROJECT_REPORT.md`);
|
|
1222
|
+
console.log(`\n Run "imhcode --help" for all commands.`);
|
|
1223
|
+
console.log(`─`.repeat(60));
|
|
1224
|
+
console.log('');
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1020
1228
|
// Create .gitignore
|
|
1021
1229
|
const localGitignore = path.join(cwd, '.gitignore');
|
|
1022
1230
|
if (!fs.existsSync(localGitignore)) {
|
|
@@ -1028,7 +1236,7 @@ async function runInit() {
|
|
|
1028
1236
|
}
|
|
1029
1237
|
|
|
1030
1238
|
registerCliGlobally(imhcodeScriptPath);
|
|
1031
|
-
ensureCavemanAndGraphify();
|
|
1239
|
+
ensureCavemanAndGraphify(globalConfig?.skills_configured);
|
|
1032
1240
|
|
|
1033
1241
|
// ── Interactive Engine & Model Setup ────────────────────────────────────────
|
|
1034
1242
|
console.log('\n🔍 Scanning for local coding assistant CLIs...');
|
|
@@ -1037,12 +1245,11 @@ async function runInit() {
|
|
|
1037
1245
|
|
|
1038
1246
|
if (foundEngines.length === 0) {
|
|
1039
1247
|
console.warn('\n⚠️ No local coding assistant CLIs detected.');
|
|
1040
|
-
console.warn(' Supported: claude, opencode, codex, agy (Antigravity), qwen (QwenCode), mimo (MimoCode)');
|
|
1248
|
+
console.warn(' Supported: claude, opencode, codex, codex-fugu, agy (Antigravity), qwen (QwenCode), mimo (MimoCode)');
|
|
1041
1249
|
console.warn(' Install one and re-run "imhcode" to configure model routing.\n');
|
|
1042
1250
|
}
|
|
1043
1251
|
|
|
1044
1252
|
let primaryEngine = 'claude';
|
|
1045
|
-
const isInteractive = process.stdout.isTTY;
|
|
1046
1253
|
|
|
1047
1254
|
if (isInteractive && foundEngines.length > 0) {
|
|
1048
1255
|
console.log('\nFound the following local coding assistant CLIs:');
|
|
@@ -1114,6 +1321,18 @@ async function runInit() {
|
|
|
1114
1321
|
fs.writeFileSync(configPath, JSON.stringify(configData, null, 2), 'utf8');
|
|
1115
1322
|
console.log(`\n💾 Configuration saved: ${configPath}`);
|
|
1116
1323
|
|
|
1324
|
+
// Save to global config defaults as well
|
|
1325
|
+
const globalConfigData = {
|
|
1326
|
+
...configData,
|
|
1327
|
+
skills_configured: true,
|
|
1328
|
+
};
|
|
1329
|
+
try {
|
|
1330
|
+
fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfigData, null, 2), 'utf8');
|
|
1331
|
+
console.log(`💾 Global defaults saved: ${globalConfigPath}`);
|
|
1332
|
+
} catch (e) {
|
|
1333
|
+
console.warn(`⚠️ Could not save global default configuration: ${e.message}`);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1117
1336
|
// Print final guide
|
|
1118
1337
|
console.log(`\n✅ ${PLATFORM_NAME} initialized successfully!`);
|
|
1119
1338
|
console.log(`─`.repeat(60));
|
|
@@ -1132,87 +1351,8 @@ async function runInit() {
|
|
|
1132
1351
|
console.log(`─`.repeat(60));
|
|
1133
1352
|
console.log('');
|
|
1134
1353
|
}
|
|
1135
|
-
|
|
1136
1354
|
// ─── Fix 4: Model Routing Setup Wizard (Ranked Scoring Algorithm) ─────────────
|
|
1137
1355
|
|
|
1138
|
-
/**
|
|
1139
|
-
* Ranked priority lists per category.
|
|
1140
|
-
* The algorithm scores each available model against these lists
|
|
1141
|
-
* and selects the highest-ranked match found in the installed engines.
|
|
1142
|
-
*/
|
|
1143
|
-
const MODEL_PRIORITY_RANKS = {
|
|
1144
|
-
frontend: [
|
|
1145
|
-
// Engine Model substring to match (lowercase, no hyphens/dots)
|
|
1146
|
-
['mimo', 'mimov25pro'],
|
|
1147
|
-
['mimo', 'mimov25'],
|
|
1148
|
-
['opencode', 'mimov25pro'],
|
|
1149
|
-
['opencode', 'mimov25'],
|
|
1150
|
-
['codex', 'gpt55'],
|
|
1151
|
-
['codex', 'gpt5'],
|
|
1152
|
-
['agy', 'claudeopus46'],
|
|
1153
|
-
['agy', 'claudeopus'],
|
|
1154
|
-
['opencode', 'gemini35flash'],
|
|
1155
|
-
['opencode', 'gemini25pro'],
|
|
1156
|
-
['claude', 'claudeopus46'],
|
|
1157
|
-
['claude', 'opus'],
|
|
1158
|
-
],
|
|
1159
|
-
backend: [
|
|
1160
|
-
['opencode', 'deepseekv4pro'],
|
|
1161
|
-
['opencode', 'deepseekv4'],
|
|
1162
|
-
['opencode', 'kimik27code'],
|
|
1163
|
-
['opencode', 'kimik2'],
|
|
1164
|
-
['opencode', 'qwen3coder480b'],
|
|
1165
|
-
['opencode', 'qwen3coder'],
|
|
1166
|
-
['codex', 'gpt55'],
|
|
1167
|
-
['codex', 'gpt5'],
|
|
1168
|
-
['opencode', 'gptoss120b'],
|
|
1169
|
-
['qwen', 'qwen3codermax'],
|
|
1170
|
-
['qwen', 'qwen3coder'],
|
|
1171
|
-
['claude', 'claudesonnet46'],
|
|
1172
|
-
['claude', 'sonnet'],
|
|
1173
|
-
],
|
|
1174
|
-
planning: [
|
|
1175
|
-
['agy', 'claudeopus46'],
|
|
1176
|
-
['agy', 'claudeopus'],
|
|
1177
|
-
['claude', 'claudeopus46'],
|
|
1178
|
-
['claude', 'opus'],
|
|
1179
|
-
['codex', 'gpt55'],
|
|
1180
|
-
['codex', 'gpt5'],
|
|
1181
|
-
['opencode', 'gemini31propreview'],
|
|
1182
|
-
['opencode', 'gemini31pro'],
|
|
1183
|
-
['opencode', 'deepseekv4pro'],
|
|
1184
|
-
],
|
|
1185
|
-
testing: [
|
|
1186
|
-
['codex', 'gpt55'],
|
|
1187
|
-
['codex', 'gpt5'],
|
|
1188
|
-
['agy', 'gptoss120bmedium'],
|
|
1189
|
-
['agy', 'gptoss120b'],
|
|
1190
|
-
['agy', 'claudeopus46'],
|
|
1191
|
-
['agy', 'claudeopus'],
|
|
1192
|
-
['opencode', 'deepseekv4pro'],
|
|
1193
|
-
['claude', 'claudeopus46'],
|
|
1194
|
-
],
|
|
1195
|
-
review: [
|
|
1196
|
-
['codex', 'gpt55'],
|
|
1197
|
-
['codex', 'gpt5'],
|
|
1198
|
-
['agy', 'claudesonnet46'],
|
|
1199
|
-
['agy', 'claudesonnet'],
|
|
1200
|
-
['opencode', 'gptoss120b'],
|
|
1201
|
-
['claude', 'claudesonnet46'],
|
|
1202
|
-
['opencode', 'deepseekv4pro'],
|
|
1203
|
-
],
|
|
1204
|
-
fast: [
|
|
1205
|
-
['opencode', 'deepseekv4flash'],
|
|
1206
|
-
['opencode', 'deepseekv4flash'],
|
|
1207
|
-
['opencode', 'gemini35flash'],
|
|
1208
|
-
['codex', 'gpt54mini'],
|
|
1209
|
-
['codex', 'gptmini'],
|
|
1210
|
-
['qwen', 'qwen3coderflash'],
|
|
1211
|
-
['claude', 'claudehaiku35'],
|
|
1212
|
-
['claude', 'haiku'],
|
|
1213
|
-
],
|
|
1214
|
-
};
|
|
1215
|
-
|
|
1216
1356
|
/**
|
|
1217
1357
|
* Normalize a model/engine string for fuzzy matching:
|
|
1218
1358
|
* lowercase, remove hyphens, dots, underscores, spaces.
|
|
@@ -2377,15 +2517,37 @@ function detectSprintDocs(cwd) {
|
|
|
2377
2517
|
return fs.readdirSync(docsDir).some(f => /^sprint-\d+$/i.test(f));
|
|
2378
2518
|
}
|
|
2379
2519
|
|
|
2520
|
+
function isSprintComplete(cwd, sprintNum) {
|
|
2521
|
+
const progressPath = path.join(cwd, DOCS_DIR, `sprint-${sprintNum}`, 'progress.md');
|
|
2522
|
+
if (!fs.existsSync(progressPath)) return false;
|
|
2523
|
+
try {
|
|
2524
|
+
const content = fs.readFileSync(progressPath, 'utf8');
|
|
2525
|
+
return !/-\s*\[\s*\]/i.test(content);
|
|
2526
|
+
} catch {
|
|
2527
|
+
return false;
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2380
2531
|
function detectCurrentSprint(cwd) {
|
|
2381
2532
|
const docsDir = path.join(cwd, DOCS_DIR);
|
|
2382
2533
|
if (!fs.existsSync(docsDir)) return 1;
|
|
2383
|
-
|
|
2534
|
+
|
|
2535
|
+
const sprintNums = [];
|
|
2384
2536
|
for (const f of fs.readdirSync(docsDir)) {
|
|
2385
2537
|
const m = f.match(/^sprint-(\d+)$/i);
|
|
2386
|
-
if (m)
|
|
2538
|
+
if (m) sprintNums.push(parseInt(m[1], 10));
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
if (sprintNums.length === 0) return 1;
|
|
2542
|
+
sprintNums.sort((a, b) => a - b);
|
|
2543
|
+
|
|
2544
|
+
for (const num of sprintNums) {
|
|
2545
|
+
if (!isSprintComplete(cwd, num)) {
|
|
2546
|
+
return num;
|
|
2547
|
+
}
|
|
2387
2548
|
}
|
|
2388
|
-
|
|
2549
|
+
|
|
2550
|
+
return sprintNums[sprintNums.length - 1];
|
|
2389
2551
|
}
|
|
2390
2552
|
|
|
2391
2553
|
function detectLastSprint(cwd) {
|
|
@@ -2611,11 +2773,40 @@ function scanAssistantCLIs() {
|
|
|
2611
2773
|
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 ''` },
|
|
2612
2774
|
opencode: { name: 'OpenCode', path: resolveBinary('opencode', ['~/.opencode/bin/opencode', '/usr/local/bin/opencode']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null` },
|
|
2613
2775
|
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` },
|
|
2776
|
+
'codex-fugu': { name: 'Codex Fugu', path: resolveBinary('codex-fugu', ['~/.local/bin/codex-fugu', '/usr/local/bin/codex-fugu', '/opt/homebrew/bin/codex-fugu']), models: [], modelsCmd: b => `"${b}" debug models 2>/dev/null || echo ''` },
|
|
2614
2777
|
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 ''` },
|
|
2615
2778
|
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 ''` },
|
|
2616
2779
|
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 ''` },
|
|
2617
2780
|
};
|
|
2618
2781
|
|
|
2782
|
+
// Scan for agy2, agy3, agy4, etc.
|
|
2783
|
+
for (let i = 2; i <= 10; i++) {
|
|
2784
|
+
const key = `agy${i}`;
|
|
2785
|
+
const binaryPath = resolveBinary(key, [
|
|
2786
|
+
`~/.local/bin/${key}`,
|
|
2787
|
+
`/usr/local/bin/${key}`,
|
|
2788
|
+
`/opt/homebrew/bin/${key}`,
|
|
2789
|
+
]);
|
|
2790
|
+
if (binaryPath) {
|
|
2791
|
+
engines[key] = {
|
|
2792
|
+
name: `Antigravity CLI (${key})`,
|
|
2793
|
+
path: binaryPath,
|
|
2794
|
+
models: [],
|
|
2795
|
+
modelsCmd: b => `"${b}" models 2>/dev/null || echo ''`
|
|
2796
|
+
};
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
const fallbackModels = {
|
|
2801
|
+
claude: ['claude-3-5-sonnet', 'claude-3-opus', 'claude-3-5-haiku', 'claude-opus-4-6', 'claude-sonnet-4-6', 'claude-haiku-3-5', 'claude-sonnet-5', 'claude-fable-5', 'claude-opus-4-8', 'opus', 'sonnet', 'haiku'],
|
|
2802
|
+
opencode: ['mimo-vl-v2.5-pro', 'deepseek-v4-pro', 'deepseek-v4-flash', 'gemini-3.5-flash', 'kimi-k2.7-code', 'qwen-3-coder-480b', 'gpt-oss-120b'],
|
|
2803
|
+
codex: ['gpt-5.5', 'gpt-5.4-mini', 'gpt-mini'],
|
|
2804
|
+
'codex-fugu': ['fugu', 'fugu-ultra'],
|
|
2805
|
+
agy: ['claude-opus-4-6', 'gemini-3.5-flash'],
|
|
2806
|
+
qwen: ['qwen3-coder-max', 'qwen3-coder-flash'],
|
|
2807
|
+
mimo: ['mimo-vl-v2.5-pro', 'mimo-vl-v2.5']
|
|
2808
|
+
};
|
|
2809
|
+
|
|
2619
2810
|
for (const [key, eng] of Object.entries(engines)) {
|
|
2620
2811
|
if (!eng.path) continue;
|
|
2621
2812
|
try {
|
|
@@ -2630,6 +2821,11 @@ function scanAssistantCLIs() {
|
|
|
2630
2821
|
}
|
|
2631
2822
|
if (models.length > 0) eng.models = models;
|
|
2632
2823
|
} catch { eng.models = []; }
|
|
2824
|
+
|
|
2825
|
+
const baseKey = key.startsWith('agy') ? 'agy' : key;
|
|
2826
|
+
if (eng.models.length === 0 && fallbackModels[baseKey]) {
|
|
2827
|
+
eng.models = fallbackModels[baseKey];
|
|
2828
|
+
}
|
|
2633
2829
|
}
|
|
2634
2830
|
|
|
2635
2831
|
return engines;
|
|
@@ -2703,7 +2899,11 @@ function registerCliGlobally(imhcodeScriptPath) {
|
|
|
2703
2899
|
}
|
|
2704
2900
|
}
|
|
2705
2901
|
|
|
2706
|
-
function ensureCavemanAndGraphify() {
|
|
2902
|
+
function ensureCavemanAndGraphify(skillsAlreadyConfigured) {
|
|
2903
|
+
if (skillsAlreadyConfigured) {
|
|
2904
|
+
console.log('\n🔍 Token-saving skills already configured globally. Skipping installation checks.');
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2707
2907
|
console.log('\n🔍 Checking token-saving skills...');
|
|
2708
2908
|
let skillsInstalled = false;
|
|
2709
2909
|
try { execSync('npx --no-install skills --version', { stdio: 'ignore' }); skillsInstalled = true; } catch { /* not installed */ }
|
|
@@ -3063,9 +3263,9 @@ async function runTuiCommand() {
|
|
|
3063
3263
|
|
|
3064
3264
|
// Step 2: Requirements
|
|
3065
3265
|
if (state.hasConfig && !state.hasStart) {
|
|
3066
|
-
items.push({ id: 'write', name: 'Write Requirements', desc: '⚠️
|
|
3266
|
+
items.push({ id: 'write', name: 'Write Requirements', desc: '⚠️ Input requirements & scope (Required)', highlight: true });
|
|
3067
3267
|
} else if (state.hasConfig) {
|
|
3068
|
-
items.push({ id: 'write', name: 'Write Requirements', desc: '
|
|
3268
|
+
items.push({ id: 'write', name: 'Write Requirements', desc: 'Input requirements & scope answers' });
|
|
3069
3269
|
}
|
|
3070
3270
|
|
|
3071
3271
|
// Step 3: Plan & Brainstorm
|
|
@@ -3107,7 +3307,17 @@ async function runTuiCommand() {
|
|
|
3107
3307
|
|
|
3108
3308
|
function detectProjectState(dir) {
|
|
3109
3309
|
const hasConfig = fs.existsSync(path.join(dir, CONFIG_FILE));
|
|
3110
|
-
|
|
3310
|
+
let hasStart = fs.existsSync(path.join(dir, START_MD));
|
|
3311
|
+
if (hasStart) {
|
|
3312
|
+
try {
|
|
3313
|
+
const content = fs.readFileSync(path.join(dir, START_MD), 'utf8');
|
|
3314
|
+
if (content.includes('hotel room bookings with real-time availability')) {
|
|
3315
|
+
hasStart = false; // default template hasn't been edited/replaced yet
|
|
3316
|
+
}
|
|
3317
|
+
} catch {
|
|
3318
|
+
hasStart = false;
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3111
3321
|
const hasBrainstorm = fs.existsSync(path.join(dir, BRAINSTORM_MD));
|
|
3112
3322
|
const hasSprints = detectSprintDocs(dir);
|
|
3113
3323
|
|
|
@@ -3346,69 +3556,104 @@ async function runTuiCommand() {
|
|
|
3346
3556
|
process.stdin.pause();
|
|
3347
3557
|
process.stdout.write(ANSI.showCursor + '\n');
|
|
3348
3558
|
|
|
3349
|
-
|
|
3350
|
-
console.log('
|
|
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');
|
|
3559
|
+
console.log(ANSI.cyan + '\n🕌 IMH-Code — Write Requirements' + ANSI.reset);
|
|
3560
|
+
console.log('Enter your project description below. When done, type /submit or a single . on a new line to finish:\n');
|
|
3354
3561
|
|
|
3355
|
-
const
|
|
3356
|
-
const
|
|
3357
|
-
rlEdit.close();
|
|
3358
|
-
|
|
3359
|
-
const startMdPath = path.join(cwd, START_MD);
|
|
3562
|
+
const lines = [];
|
|
3563
|
+
const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3360
3564
|
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
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
|
-
}
|
|
3379
|
-
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');
|
|
3565
|
+
await new Promise(resolve => {
|
|
3566
|
+
rlInput.on('line', (line) => {
|
|
3567
|
+
if (line.trim() === '/submit' || line.trim() === '.') {
|
|
3568
|
+
resolve();
|
|
3569
|
+
return;
|
|
3386
3570
|
}
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
}
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3571
|
+
lines.push(line);
|
|
3572
|
+
});
|
|
3573
|
+
rlInput.on('close', () => {
|
|
3574
|
+
resolve();
|
|
3575
|
+
});
|
|
3576
|
+
});
|
|
3577
|
+
|
|
3578
|
+
const desc = lines.join('\n');
|
|
3579
|
+
rlInput.close();
|
|
3580
|
+
|
|
3581
|
+
if (desc.trim() === '') {
|
|
3582
|
+
console.log('⚠️ No requirements entered. Aborting.');
|
|
3583
|
+
inSubMode = false;
|
|
3584
|
+
if (isRaw) process.stdin.setRawMode(true);
|
|
3585
|
+
process.stdin.resume();
|
|
3586
|
+
renderMenu();
|
|
3587
|
+
break;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
const rlQuestions = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3591
|
+
|
|
3592
|
+
// Ask for backend
|
|
3593
|
+
let backend = 'yes';
|
|
3594
|
+
while (true) {
|
|
3595
|
+
const ans = await new Promise(res => rlQuestions.question('\nDo you need a backend API / server-side logic? (yes/no/unsure) [default: yes]: ', res));
|
|
3596
|
+
const cleanAns = ans.trim().toLowerCase();
|
|
3597
|
+
if (cleanAns === '') { backend = 'yes'; break; }
|
|
3598
|
+
if (['yes', 'no', 'unsure'].includes(cleanAns)) { backend = cleanAns; break; }
|
|
3599
|
+
console.log('❌ Invalid input. Please enter yes, no, or unsure.');
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
// Ask for mobile
|
|
3603
|
+
let mobile = 'no';
|
|
3604
|
+
while (true) {
|
|
3605
|
+
const ans = await new Promise(res => rlQuestions.question('Do you need a mobile app (iOS/Android)? (yes/no) [default: no]: ', res));
|
|
3606
|
+
const cleanAns = ans.trim().toLowerCase();
|
|
3607
|
+
if (cleanAns === '') { mobile = 'no'; break; }
|
|
3608
|
+
if (['yes', 'no'].includes(cleanAns)) { mobile = cleanAns; break; }
|
|
3609
|
+
console.log('❌ Invalid input. Please enter yes or no.');
|
|
3610
|
+
}
|
|
3611
|
+
|
|
3612
|
+
rlQuestions.close();
|
|
3613
|
+
|
|
3614
|
+
// Write start.md template
|
|
3615
|
+
const startMdPath = path.join(cwd, START_MD);
|
|
3616
|
+
const startMdContent = [
|
|
3617
|
+
'# 🕌 IMH-Code — Project Start',
|
|
3618
|
+
'',
|
|
3619
|
+
'> **Imam Hussain Coding Harness Platform**',
|
|
3620
|
+
'',
|
|
3621
|
+
'---',
|
|
3622
|
+
'',
|
|
3623
|
+
'## ⚡ Quick Scope Check',
|
|
3624
|
+
'',
|
|
3625
|
+
'**Do you need a backend API / server-side logic?**',
|
|
3626
|
+
`> **Answer:** ${backend}`,
|
|
3627
|
+
'',
|
|
3628
|
+
'**Do you need a mobile app (iOS/Android)?**',
|
|
3629
|
+
`> **Answer:** ${mobile}`,
|
|
3630
|
+
'',
|
|
3631
|
+
'---',
|
|
3632
|
+
'',
|
|
3633
|
+
'## 📝 Your Project Description',
|
|
3634
|
+
'',
|
|
3635
|
+
'<!-- WRITE_PROMPT_HERE -->',
|
|
3636
|
+
desc,
|
|
3637
|
+
'<!-- END_PROMPT -->',
|
|
3638
|
+
'',
|
|
3639
|
+
'---',
|
|
3640
|
+
'',
|
|
3641
|
+
'## 🚀 Next Step',
|
|
3642
|
+
'',
|
|
3643
|
+
'After filling in the scope and your description, run:',
|
|
3644
|
+
'',
|
|
3645
|
+
'```bash',
|
|
3646
|
+
'imhcode plan',
|
|
3647
|
+
'```'
|
|
3648
|
+
].join('\n');
|
|
3649
|
+
|
|
3650
|
+
// Ensure directories exist
|
|
3651
|
+
const docsDir = path.dirname(startMdPath);
|
|
3652
|
+
if (!fs.existsSync(docsDir)) {
|
|
3653
|
+
fs.mkdirSync(docsDir, { recursive: true });
|
|
3411
3654
|
}
|
|
3655
|
+
fs.writeFileSync(startMdPath, startMdContent, 'utf8');
|
|
3656
|
+
console.log('\n✅ Requirements updated successfully in docs/start.md!');
|
|
3412
3657
|
|
|
3413
3658
|
inSubMode = false;
|
|
3414
3659
|
if (isRaw) process.stdin.setRawMode(true);
|