imhcode 2.0.5 → 3.0.1

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 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:');
@@ -1091,16 +1298,12 @@ async function runInit() {
1091
1298
  console.log(`✅ Default model: ${defaultModel}`);
1092
1299
  }
1093
1300
 
1094
- // Fix 4: Smart Model Routing Setup with ranked scoring
1095
- const modelRouting = await setupModelRouting(engines, foundEngines, isInteractive);
1096
-
1097
1301
  // Write config
1098
1302
  const configPath = path.join(cwd, CONFIG_FILE);
1099
1303
  const configData = {
1100
1304
  primary_engine: primaryEngine,
1101
1305
  default_model: defaultModel || undefined,
1102
1306
  testing_mode: 'fast',
1103
- model_routing: modelRouting,
1104
1307
  available_engines: {},
1105
1308
  };
1106
1309
 
@@ -1114,221 +1317,36 @@ async function runInit() {
1114
1317
  fs.writeFileSync(configPath, JSON.stringify(configData, null, 2), 'utf8');
1115
1318
  console.log(`\n💾 Configuration saved: ${configPath}`);
1116
1319
 
1320
+ // Save to global config defaults as well
1321
+ const globalConfigData = {
1322
+ ...configData,
1323
+ skills_configured: true,
1324
+ };
1325
+ try {
1326
+ fs.writeFileSync(globalConfigPath, JSON.stringify(globalConfigData, null, 2), 'utf8');
1327
+ console.log(`💾 Global defaults saved: ${globalConfigPath}`);
1328
+ } catch (e) {
1329
+ console.warn(`⚠️ Could not save global default configuration: ${e.message}`);
1330
+ }
1331
+
1117
1332
  // Print final guide
1118
1333
  console.log(`\n✅ ${PLATFORM_NAME} initialized successfully!`);
1119
1334
  console.log(`─`.repeat(60));
1120
1335
  console.log(`\n🕌 HOW TO BUILD WITH IMH-CODE:\n`);
1121
1336
  console.log(` 1. Open docs/start.md → Answer scope questions + write your description`);
1122
1337
  console.log(` 2. Run: imhcode plan`);
1123
- console.log(` → Your planning AI (${modelRouting?.planning?.model || 'configured model'}) generates brainstorming.md`);
1338
+ console.log(` → Your planning AI generates brainstorming.md`);
1124
1339
  console.log(` 3. Open docs/brainstorming.md → Review/edit AI-recommended answers`);
1125
1340
  console.log(` 4. Run: imhcode plan`);
1126
1341
  console.log(` → Your planning AI generates sprint plans with correct agent routing`);
1127
- console.log(` 5. Run: imhcode execute 1 → Sprint 1 (frontend tasks → ${modelRouting?.frontend?.model || 'frontend model'})`);
1128
- console.log(` 6. Run: imhcode execute 2 → Sprint 2 (backend tasks → ${modelRouting?.backend?.model || 'backend model'})`);
1342
+ console.log(` 5. Run: imhcode execute 1 → Sprint 1 (frontend tasks)`);
1343
+ console.log(` 6. Run: imhcode execute 2 → Sprint 2 (backend tasks)`);
1129
1344
  console.log(` 7. Run: imhcode test → Final testing + security + SEO`);
1130
1345
  console.log(` 8. Run: imhcode report → Generate PROJECT_REPORT.md`);
1131
1346
  console.log(`\n Run "imhcode --help" for all commands.`);
1132
1347
  console.log(`─`.repeat(60));
1133
1348
  console.log('');
1134
1349
  }
1135
-
1136
- // ─── Fix 4: Model Routing Setup Wizard (Ranked Scoring Algorithm) ─────────────
1137
-
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
- /**
1217
- * Normalize a model/engine string for fuzzy matching:
1218
- * lowercase, remove hyphens, dots, underscores, spaces.
1219
- */
1220
- function normalizeForMatch(s) {
1221
- return (s || '').toLowerCase().replace(/[-._\s]/g, '');
1222
- }
1223
-
1224
- /**
1225
- * Select the best model for a category from available engines using ranked scoring.
1226
- * Returns { engine, model } or null if nothing found.
1227
- */
1228
- function selectBestModel(category, engines) {
1229
- const ranks = MODEL_PRIORITY_RANKS[category] || [];
1230
- for (const [preferredEngine, modelSubstring] of ranks) {
1231
- const engData = engines[preferredEngine];
1232
- if (!engData?.path || !engData.models?.length) continue;
1233
- const match = engData.models.find(m => normalizeForMatch(m).includes(modelSubstring));
1234
- if (match) return { engine: preferredEngine, model: match };
1235
- }
1236
- return null;
1237
- }
1238
-
1239
- async function setupModelRouting(engines, foundEngines, isInteractive) {
1240
- const categories = {
1241
- frontend: { label: 'Frontend (UI/UX, components, animations)', note: 'Mimo v2.5 Pro → GPT-5.5 → Claude Opus' },
1242
- backend: { label: 'Backend (APIs, database, business logic)', note: 'DeepSeek V4 Pro → Kimi K2.7 → Qwen3 Coder → GPT-5.5' },
1243
- planning: { label: 'Planning (brainstorming, sprint planning)', note: 'Claude Opus 4.6 → GPT-5.5 → Gemini 3.1 Pro' },
1244
- testing: { label: 'Testing (QA, security audit, E2E)', note: 'GPT-5.5 → GPT-OSS 120B → Claude Opus' },
1245
- review: { label: 'Review (SEO, debugging, code review)', note: 'GPT-5.5 → Claude Sonnet → GPT-OSS 120B' },
1246
- fast: { label: 'Fast (boilerplate, config, simple tasks)', note: 'DeepSeek V4 Flash → Gemini 3.5 Flash → GPT Mini' },
1247
- };
1248
-
1249
- const recommended = {};
1250
- for (const cat of Object.keys(categories)) {
1251
- const best = selectBestModel(cat, engines);
1252
- if (best) {
1253
- recommended[cat] = best;
1254
- } else if (foundEngines.length > 0) {
1255
- // Fallback: primary engine first model
1256
- const fe = foundEngines[0];
1257
- recommended[cat] = { engine: fe, model: engines[fe].models[0] || 'default' };
1258
- }
1259
- }
1260
-
1261
- // Show recommended routing table
1262
- console.log('\n' + '─'.repeat(70));
1263
- console.log('🧠 Recommended Model Routing (ranked by quality for each category):\n');
1264
- console.log(' Category │ Engine │ Model');
1265
- console.log(' ──────────────┼───────────────┼───────────────────────────────────────');
1266
- for (const [cat, rec] of Object.entries(recommended)) {
1267
- const catLabel = cat.padEnd(12);
1268
- const eng = (rec.engine || '?').padEnd(13);
1269
- const mdl = rec.model || '?';
1270
- const note = categories[cat]?.note || '';
1271
- console.log(` ${catLabel} │ ${eng} │ ${mdl}`);
1272
- }
1273
- console.log(' ' + '─'.repeat(67));
1274
- console.log(`\n Priority: Mimo v2.5 Pro (frontend) | DeepSeek V4 Pro (backend)`);
1275
- console.log(` Claude Opus (planning) | GPT-5.5 (testing/review)`);
1276
-
1277
- const routing = {};
1278
-
1279
- if (isInteractive) {
1280
- const answer = await askQuestion('\nAccept recommended routing? [Y/n] ');
1281
- if (answer.toLowerCase() === 'n' || answer.toLowerCase() === 'no') {
1282
- // Let user customize each category
1283
- for (const [cat, cfg] of Object.entries(categories)) {
1284
- console.log(`\n Configure model for [${cat}] — ${cfg.label}`);
1285
- console.log(` Priority: ${cfg.note}`);
1286
-
1287
- const allModels = [];
1288
- for (const eng of foundEngines) {
1289
- for (const m of engines[eng].models) {
1290
- allModels.push({ engine: eng, model: m });
1291
- }
1292
- }
1293
-
1294
- if (allModels.length === 0) {
1295
- routing[cat] = recommended[cat];
1296
- continue;
1297
- }
1298
-
1299
- allModels.forEach((item, i) => {
1300
- const rec = recommended[cat];
1301
- const isRec = rec && rec.engine === item.engine && rec.model === item.model;
1302
- console.log(` [${i + 1}] ${item.model} (${item.engine})${isRec ? ' ← Recommended ✅' : ''}`);
1303
- });
1304
-
1305
- let selectedIdx = allModels.findIndex(m => {
1306
- const rec = recommended[cat];
1307
- return rec && m.engine === rec.engine && m.model === rec.model;
1308
- });
1309
- if (selectedIdx < 0) selectedIdx = 0;
1310
-
1311
- while (true) {
1312
- const ans = await askQuestion(` Select model for ${cat} [1-${allModels.length}] (default: ${selectedIdx + 1}): `);
1313
- if (ans === '') { routing[cat] = allModels[selectedIdx]; break; }
1314
- const p = parseInt(ans, 10);
1315
- if (p >= 1 && p <= allModels.length) { routing[cat] = allModels[p - 1]; break; }
1316
- console.log(` ❌ Invalid. Enter 1–${allModels.length}.`);
1317
- }
1318
- console.log(` ✅ ${cat}: ${routing[cat].model} (${routing[cat].engine})`);
1319
- }
1320
- } else {
1321
- Object.assign(routing, recommended);
1322
- console.log('\n✅ Recommended routing accepted.');
1323
- }
1324
- } else {
1325
- Object.assign(routing, recommended);
1326
- console.log('\n✅ Recommended routing applied automatically.');
1327
- }
1328
-
1329
- return routing;
1330
- }
1331
-
1332
1350
  // ─── Fix 0: LLM-Powered Planning ─────────────────────────────────────────────
1333
1351
 
1334
1352
  /**
@@ -2633,11 +2651,40 @@ function scanAssistantCLIs() {
2633
2651
  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 ''` },
2634
2652
  opencode: { name: 'OpenCode', path: resolveBinary('opencode', ['~/.opencode/bin/opencode', '/usr/local/bin/opencode']), models: [], modelsCmd: b => `"${b}" models 2>/dev/null` },
2635
2653
  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` },
2654
+ '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 ''` },
2636
2655
  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 ''` },
2637
2656
  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 ''` },
2638
2657
  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 ''` },
2639
2658
  };
2640
2659
 
2660
+ // Scan for agy2, agy3, agy4, etc.
2661
+ for (let i = 2; i <= 10; i++) {
2662
+ const key = `agy${i}`;
2663
+ const binaryPath = resolveBinary(key, [
2664
+ `~/.local/bin/${key}`,
2665
+ `/usr/local/bin/${key}`,
2666
+ `/opt/homebrew/bin/${key}`,
2667
+ ]);
2668
+ if (binaryPath) {
2669
+ engines[key] = {
2670
+ name: `Antigravity CLI (${key})`,
2671
+ path: binaryPath,
2672
+ models: [],
2673
+ modelsCmd: b => `"${b}" models 2>/dev/null || echo ''`
2674
+ };
2675
+ }
2676
+ }
2677
+
2678
+ const fallbackModels = {
2679
+ claude: ['claude-sonnet-5', 'claude-fable-5', 'claude-opus-4-8', 'opus', 'sonnet', 'haiku'],
2680
+ 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'],
2681
+ codex: ['gpt-5.5', 'gpt-5.4-mini', 'gpt-mini'],
2682
+ 'codex-fugu': ['fugu', 'fugu-ultra'],
2683
+ agy: ['claude-opus-4-6', 'gemini-3.5-flash'],
2684
+ qwen: ['qwen3-coder-max', 'qwen3-coder-flash'],
2685
+ mimo: ['mimo-vl-v2.5-pro', 'mimo-vl-v2.5']
2686
+ };
2687
+
2641
2688
  for (const [key, eng] of Object.entries(engines)) {
2642
2689
  if (!eng.path) continue;
2643
2690
  try {
@@ -2652,6 +2699,11 @@ function scanAssistantCLIs() {
2652
2699
  }
2653
2700
  if (models.length > 0) eng.models = models;
2654
2701
  } catch { eng.models = []; }
2702
+
2703
+ const baseKey = key.startsWith('agy') ? 'agy' : key;
2704
+ if (eng.models.length === 0 && fallbackModels[baseKey]) {
2705
+ eng.models = fallbackModels[baseKey];
2706
+ }
2655
2707
  }
2656
2708
 
2657
2709
  return engines;
@@ -2725,7 +2777,11 @@ function registerCliGlobally(imhcodeScriptPath) {
2725
2777
  }
2726
2778
  }
2727
2779
 
2728
- function ensureCavemanAndGraphify() {
2780
+ function ensureCavemanAndGraphify(skillsAlreadyConfigured) {
2781
+ if (skillsAlreadyConfigured) {
2782
+ console.log('\n🔍 Token-saving skills already configured globally. Skipping installation checks.');
2783
+ return;
2784
+ }
2729
2785
  console.log('\n🔍 Checking token-saving skills...');
2730
2786
  let skillsInstalled = false;
2731
2787
  try { execSync('npx --no-install skills --version', { stdio: 'ignore' }); skillsInstalled = true; } catch { /* not installed */ }
@@ -3129,7 +3185,17 @@ async function runTuiCommand() {
3129
3185
 
3130
3186
  function detectProjectState(dir) {
3131
3187
  const hasConfig = fs.existsSync(path.join(dir, CONFIG_FILE));
3132
- const hasStart = fs.existsSync(path.join(dir, START_MD));
3188
+ let hasStart = fs.existsSync(path.join(dir, START_MD));
3189
+ if (hasStart) {
3190
+ try {
3191
+ const content = fs.readFileSync(path.join(dir, START_MD), 'utf8');
3192
+ if (content.includes('hotel room bookings with real-time availability')) {
3193
+ hasStart = false; // default template hasn't been edited/replaced yet
3194
+ }
3195
+ } catch {
3196
+ hasStart = false;
3197
+ }
3198
+ }
3133
3199
  const hasBrainstorm = fs.existsSync(path.join(dir, BRAINSTORM_MD));
3134
3200
  const hasSprints = detectSprintDocs(dir);
3135
3201
 
@@ -3369,21 +3435,25 @@ async function runTuiCommand() {
3369
3435
  process.stdout.write(ANSI.showCursor + '\n');
3370
3436
 
3371
3437
  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');
3438
+ console.log('Enter your project description below. When done, type /submit or a single . on a new line to finish:\n');
3373
3439
 
3374
3440
  const lines = [];
3375
3441
  const rlInput = readline.createInterface({ input: process.stdin, output: process.stdout });
3376
3442
 
3377
3443
  while (true) {
3378
3444
  const line = await new Promise(res => rlInput.question('> ', res));
3379
- if (line.trim() === '') break;
3445
+ const trimmed = line.trim();
3446
+ if (trimmed === '/submit' || trimmed === '.') {
3447
+ break;
3448
+ }
3380
3449
  lines.push(line);
3381
3450
  }
3382
3451
 
3383
3452
  const desc = lines.join('\n');
3453
+ rlInput.close();
3454
+
3384
3455
  if (desc.trim() === '') {
3385
3456
  console.log('⚠️ No requirements entered. Aborting.');
3386
- rlInput.close();
3387
3457
  inSubMode = false;
3388
3458
  if (isRaw) process.stdin.setRawMode(true);
3389
3459
  process.stdin.resume();
@@ -3391,10 +3461,12 @@ async function runTuiCommand() {
3391
3461
  break;
3392
3462
  }
3393
3463
 
3464
+ const rlQuestions = readline.createInterface({ input: process.stdin, output: process.stdout });
3465
+
3394
3466
  // Ask for backend
3395
3467
  let backend = 'yes';
3396
3468
  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));
3469
+ const ans = await new Promise(res => rlQuestions.question('\nDo you need a backend API / server-side logic? (yes/no/unsure) [default: yes]: ', res));
3398
3470
  const cleanAns = ans.trim().toLowerCase();
3399
3471
  if (cleanAns === '') { backend = 'yes'; break; }
3400
3472
  if (['yes', 'no', 'unsure'].includes(cleanAns)) { backend = cleanAns; break; }
@@ -3404,14 +3476,14 @@ async function runTuiCommand() {
3404
3476
  // Ask for mobile
3405
3477
  let mobile = 'no';
3406
3478
  while (true) {
3407
- const ans = await new Promise(res => rlInput.question('Do you need a mobile app (iOS/Android)? (yes/no) [default: no]: ', res));
3479
+ const ans = await new Promise(res => rlQuestions.question('Do you need a mobile app (iOS/Android)? (yes/no) [default: no]: ', res));
3408
3480
  const cleanAns = ans.trim().toLowerCase();
3409
3481
  if (cleanAns === '') { mobile = 'no'; break; }
3410
3482
  if (['yes', 'no'].includes(cleanAns)) { mobile = cleanAns; break; }
3411
3483
  console.log('❌ Invalid input. Please enter yes or no.');
3412
3484
  }
3413
3485
 
3414
- rlInput.close();
3486
+ rlQuestions.close();
3415
3487
 
3416
3488
  // Write start.md template
3417
3489
  const startMdPath = path.join(cwd, START_MD);
@@ -27,8 +27,13 @@ export declare class CodexCLIAdapter implements EngineAdapter {
27
27
  readonly name = "codex";
28
28
  run(prompt: string, model: string): Promise<string>;
29
29
  }
30
+ export declare class CodexFuguCLIAdapter implements EngineAdapter {
31
+ readonly name = "codex-fugu";
32
+ run(prompt: string, model: string): Promise<string>;
33
+ }
30
34
  export declare class AgyCLIAdapter implements EngineAdapter {
31
- readonly name = "agy";
35
+ readonly name: string;
36
+ constructor(name?: string);
32
37
  run(prompt: string, model: string): Promise<string>;
33
38
  }
34
39
  export declare class QwenCodeCLIAdapter implements EngineAdapter {