winter-super-cli 2026.5.28 → 2026.5.30

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/src/cli/repl.js CHANGED
@@ -21,6 +21,7 @@ import { formatMarkdown } from './markdown-format.js';
21
21
  import { Spinner } from './spinner.js';
22
22
  import { ContextLoader } from './context-loader.js';
23
23
  import { PromptBuilder } from './prompt-builder.js';
24
+ import { classifyModelTier, isSmallModel } from '../ai/model-capabilities.js';
24
25
  import {
25
26
  addUsage as mergeUsage,
26
27
  buildToolCallSignature as buildToolCallSignatureText,
@@ -65,6 +66,7 @@ export class WinterREPL {
65
66
  this.contextLoader = new ContextLoader({ projectPath: this.projectPath, session: this.session, tools: this.tools });
66
67
  this.promptBuilder = new PromptBuilder({
67
68
  session: this.session,
69
+ ai: this.ai,
68
70
  tools: this.tools,
69
71
  projectPath: this.projectPath,
70
72
  sessionPermissionGrants: this.sessionPermissionGrants,
@@ -228,6 +230,111 @@ export class WinterREPL {
228
230
  }
229
231
  }
230
232
 
233
+ // ── Tự động tạo design.md, skill.md, rule.md nếu chưa có ──────────────
234
+ const autoCreateDocs = [
235
+ {
236
+ filename: 'design.md',
237
+ generate: async () => {
238
+ const designDir = this.getResourcePaths().designs;
239
+ let brands = [];
240
+ try {
241
+ const entries = await fsPromises.readdir(designDir, { withFileTypes: true });
242
+ brands = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
243
+ } catch {}
244
+ return `# Design Resources
245
+
246
+ Danh sách các design system có sẵn trong local resources:
247
+
248
+ ## Available Brands (${brands.length})
249
+
250
+ ${brands.length > 0 ? brands.map(b => `- ${b}`).join('\n') : '- Không tìm thấy design system nào.'}
251
+
252
+ ---
253
+ *File này được tự động tạo bởi Winter CLI.*`;
254
+ },
255
+ },
256
+ {
257
+ filename: 'skill.md',
258
+ generate: async () => {
259
+ const catalog = await this.contextLoader.getStartupSkillCatalog();
260
+ const skills = [...catalog].sort();
261
+ return `# Available Skills
262
+
263
+ Danh sách các skill có sẵn trong hệ thống:
264
+
265
+ ## Core Skills
266
+ - **coding**: Code analysis, generation, review
267
+ - **design**: Design system integration
268
+ - **debug**: Debugging assistance
269
+ - **refactor**: Code refactoring
270
+ - **test**: Test generation
271
+ - **security**: Security review
272
+ - **performance**: Performance optimization
273
+
274
+ ## All Available Skills (${skills.length})
275
+
276
+ ${skills.map(s => `- ${s}`).join('\n')}
277
+
278
+ ---
279
+ *File này được tự động tạo bởi Winter CLI.*`;
280
+ },
281
+ },
282
+ {
283
+ filename: 'rule.md',
284
+ generate: async () => {
285
+ const parts = ['# Project Rules', '', '## Quy tắc dự án', ''];
286
+ // Load từ các instruction files đã có
287
+ const files = await this.readProjectInstructionFiles();
288
+ for (const file of files) {
289
+ if (file.relativePath === 'rule.md') continue; // skip self
290
+ parts.push(`- [${file.relativePath}](./${file.relativePath})`);
291
+ }
292
+ // Liệt kê các thư mục rules
293
+ const rulesDirs = [
294
+ this.getResourcePaths().codex.rules,
295
+ this.getUserResourcePaths()?.codexRules,
296
+ ].filter(Boolean);
297
+ for (const dir of rulesDirs) {
298
+ try {
299
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
300
+ const ruleFiles = entries.filter(e => e.isFile() && e.name.endsWith('.md')).map(e => e.name);
301
+ if (ruleFiles.length > 0) {
302
+ parts.push('', `## Rules from ${path.basename(path.dirname(dir))}/${path.basename(dir)}`, '');
303
+ for (const f of ruleFiles) {
304
+ parts.push(`- ${f}`);
305
+ }
306
+ }
307
+ } catch {}
308
+ }
309
+ // Liệt kê local resource rules
310
+ parts.push('', '## Local Resource Guidelines', '');
311
+ parts.push('- Karpathy tools guidelines available in local resources');
312
+ parts.push('- Agents.md project guidelines available in local resources');
313
+ parts.push('', '---', '*File này được tự động tạo bởi Winter CLI.*');
314
+ return parts.join('\n');
315
+ },
316
+ },
317
+ ];
318
+
319
+ for (const doc of autoCreateDocs) {
320
+ const filePath = path.join(this.projectPath, doc.filename);
321
+ try {
322
+ await fsPromises.stat(filePath);
323
+ // File đã tồn tại, bỏ qua
324
+ } catch {
325
+ // File chưa tồn tại, tự động tạo
326
+ try {
327
+ const content = await doc.generate();
328
+ await fsPromises.writeFile(filePath, content, 'utf8');
329
+ console.log(`${colors.green}✓ Đã tự động tạo file ${doc.filename} từ local resources!${colors.reset}`);
330
+ const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
331
+ await this.session.replaceMemory(memoryKey, content);
332
+ } catch (err) {
333
+ // Bỏ qua nếu không tạo được
334
+ }
335
+ }
336
+ }
337
+
231
338
  await this.bootstrapProjectCapabilities();
232
339
 
233
340
  const activeProvider = this.ai.getActiveProvider();
@@ -286,21 +393,23 @@ export class WinterREPL {
286
393
  });
287
394
 
288
395
  // Hiển thị prompt lần đầu tiên ngay khi khởi động xong
289
- this.rl.prompt();
396
+ this.showInputPrompt();
290
397
 
291
398
  this.rl.on('line', (line) => {
292
399
  this.inputQueue = this.inputQueue
293
400
  .then(async () => {
401
+ this.closeInputBox();
294
402
  const input = line.trim();
295
403
  if (input) {
296
404
  await this.handleInput(input);
297
405
  } else {
298
- if (this.running && !this.readlineClosed) this.rl.prompt();
406
+ if (this.running && !this.readlineClosed) this.showInputPrompt();
299
407
  }
300
408
  })
301
409
  .catch((error) => {
410
+ this.closeInputBox();
302
411
  console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
303
- if (this.running && !this.readlineClosed) this.rl.prompt();
412
+ if (this.running && !this.readlineClosed) this.showInputPrompt();
304
413
  });
305
414
  });
306
415
 
@@ -312,6 +421,28 @@ export class WinterREPL {
312
421
  });
313
422
  }
314
423
 
424
+ showInputPrompt() {
425
+ if (!this.running || this.readlineClosed) return;
426
+ const w = Math.max(20, terminalWidth() - 2);
427
+ process.stdout.write(`
428
+ ${colors.magenta}╭${'─'.repeat(w)}╮${colors.reset}
429
+ `);
430
+ process.stdout.write(`${colors.magenta}│${colors.reset} `);
431
+ this.rl.setPrompt(`${colors.bright}${colors.cyan}winter❄️: ${colors.reset}`);
432
+ this.rl.prompt();
433
+ }
434
+
435
+ closeInputBox() {
436
+ const w = Math.max(20, terminalWidth() - 2);
437
+ readline.moveCursor(process.stdout, 0, -1);
438
+ readline.cursorTo(process.stdout, terminalWidth() - 1);
439
+ process.stdout.write(`${colors.magenta}│${colors.reset}`);
440
+ process.stdout.write(`
441
+ `);
442
+ process.stdout.write(`${colors.magenta}╰${'─'.repeat(w)}╯${colors.reset}
443
+ `);
444
+ }
445
+
315
446
  showStatus() {
316
447
  console.log(`${colors.dim}Project: ${this.projectPath}${colors.reset}`);
317
448
  console.log(`${colors.dim}Provider: ${this.ai.getActiveProvider()}${colors.reset}`);
@@ -500,7 +631,7 @@ export class WinterREPL {
500
631
  const nextTask = this.taskQueue.shift();
501
632
  setTimeout(() => this.processInputTask(nextTask), 0);
502
633
  } else {
503
- if (!this.readlineClosed) this.rl.prompt(true);
634
+ if (!this.readlineClosed) this.showInputPrompt();
504
635
  }
505
636
  }
506
637
  }
@@ -946,10 +1077,20 @@ ${colors.reset}
946
1077
  case 'research':
947
1078
  return byName(['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch']);
948
1079
  default:
949
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList']);
1080
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep']);
950
1081
  }
951
1082
  }
952
1083
 
1084
+ getActiveModelTier() {
1085
+ const providerName = this.ai?.getActiveProvider?.();
1086
+ const model = this.ai?.providers?.[providerName]?.model || '';
1087
+ return classifyModelTier(model, providerName);
1088
+ }
1089
+
1090
+ shouldUseCompactPrompt() {
1091
+ return isSmallModel(this.getActiveModelTier());
1092
+ }
1093
+
953
1094
  selectExecutionProfile(messages = [], options = {}) {
954
1095
  if (typeof this.ai?.selectExecutionProfile === 'function') {
955
1096
  const profile = this.ai.selectExecutionProfile(messages, options);
@@ -971,14 +1112,17 @@ ${colors.reset}
971
1112
  const activeProvider = this.ai?.getActiveProvider?.() || Object.keys(providers)[0] || null;
972
1113
  const hasProvider = name => !!providers[name]?.model || !!providers[name]?.ready;
973
1114
 
974
- let provider = activeProvider;
975
- if (/\b(review|refactor|debug|fix|bug|error|stack trace|test|tool|patch|code)\b/.test(text) && hasProvider('claude')) {
1115
+ const activeProviderIsValid = activeProvider && hasProvider(activeProvider);
1116
+ const allowAutoRoute = options.autoRouteProvider === true && !activeProviderIsValid;
1117
+
1118
+ let provider = activeProviderIsValid ? activeProvider : Object.keys(providers).find(hasProvider) || activeProvider;
1119
+ if (allowAutoRoute && /\b(review|refactor|debug|fix|bug|error|stack trace|test|tool|patch|code)\b/.test(text) && hasProvider('claude')) {
976
1120
  provider = 'claude';
977
- } else if (/\b(summary|summarize|commit message|changelog|docs|explain|rewrite)\b/.test(text) && hasProvider('openai')) {
1121
+ } else if (allowAutoRoute && /\b(summary|summarize|commit message|changelog|docs|explain|rewrite)\b/.test(text) && hasProvider('openai')) {
978
1122
  provider = 'openai';
979
- } else if (/\b(local|offline|privacy|private|on-device)\b/.test(text) && hasProvider('ollama')) {
1123
+ } else if (allowAutoRoute && /\b(local|offline|privacy|private|on-device)\b/.test(text) && hasProvider('ollama')) {
980
1124
  provider = 'ollama';
981
- } else if (/\b(quick|brief|short|fast)\b/.test(text) && hasProvider('groq')) {
1125
+ } else if (allowAutoRoute && /\b(quick|brief|short|fast)\b/.test(text) && hasProvider('groq')) {
982
1126
  provider = 'groq';
983
1127
  }
984
1128
 
@@ -1001,9 +1145,10 @@ ${colors.reset}
1001
1145
  let finalContent = '';
1002
1146
  let reachedToolLimit = true;
1003
1147
  let usedTools = false;
1148
+ let verified = false;
1004
1149
  const toolSummaries = [];
1005
1150
  const totalUsage = {};
1006
- let lastToolSignature = null;
1151
+ const toolSignatureHistory = [];
1007
1152
  const executionProfile = this.selectExecutionProfile(messages, { enableTools: true });
1008
1153
  try {
1009
1154
  for (let i = 0; i < 8; i++) {
@@ -1042,13 +1187,21 @@ ${colors.reset}
1042
1187
  if (this.spinner) this.spinner.stop();
1043
1188
 
1044
1189
  const currentToolSignature = this.buildToolCallSignature(toolCalls);
1045
- if (currentToolSignature && currentToolSignature === lastToolSignature) {
1046
- console.log(`
1047
- ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1048
- reachedToolLimit = false;
1049
- break;
1190
+ if (currentToolSignature) {
1191
+ toolSignatureHistory.push(currentToolSignature);
1192
+ if (toolSignatureHistory.length > 3) {
1193
+ toolSignatureHistory.shift();
1194
+ }
1195
+ // Only break if 3+ consecutive identical signatures — 2 repeats is normal iteration
1196
+ if (toolSignatureHistory.length === 3 &&
1197
+ toolSignatureHistory[0] === currentToolSignature &&
1198
+ toolSignatureHistory[1] === currentToolSignature) {
1199
+ console.log(`
1200
+ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls). Breaking out.${colors.reset}`);
1201
+ reachedToolLimit = false;
1202
+ break;
1203
+ }
1050
1204
  }
1051
- lastToolSignature = currentToolSignature;
1052
1205
 
1053
1206
  const BOX_WIDTH = terminalWidth(76, 116, 92);
1054
1207
  messages.push({
@@ -1061,12 +1214,17 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1061
1214
  const { toolName, toolArgs } = tc;
1062
1215
  const canonicalToolName = this.tools.normalizeToolName(toolName);
1063
1216
  const argParseError = toolArgs?.__toolArgParseError;
1064
- const enrichedArgs = argParseError ? {} : this.enrichToolArgs(canonicalToolName, toolArgs, messages);
1217
+ const recoveredArgs = argParseError ? this.recoverToolArgs(canonicalToolName, toolArgs.__rawToolArgs) : null;
1218
+ const canUseRecoveredArgs = recoveredArgs && Object.keys(recoveredArgs).length > 0;
1219
+ const normalizedArgs = argParseError && !canUseRecoveredArgs
1220
+ ? {}
1221
+ : this.tools.normalizeToolInput?.(canonicalToolName, canUseRecoveredArgs ? recoveredArgs : toolArgs) ?? toolArgs;
1222
+ const enrichedArgs = argParseError && !canUseRecoveredArgs ? {} : this.enrichToolArgs(canonicalToolName, normalizedArgs, messages);
1065
1223
 
1066
1224
  const icon = canonicalToolName === 'Bash' ? '⚙' : canonicalToolName === 'Read' ? '📖' : canonicalToolName === 'Write' ? '✏️' : canonicalToolName === 'Edit' ? '🔧' : canonicalToolName === 'Grep' ? '🔍' : canonicalToolName === 'Glob' ? '📂' : '⚡';
1067
1225
 
1068
1226
  let proceed = true;
1069
- if (await this.shouldPromptForToolPermission(canonicalToolName) && !argParseError) {
1227
+ if (await this.shouldPromptForToolPermission(canonicalToolName) && (!argParseError || canUseRecoveredArgs)) {
1070
1228
  const cmd = enrichedArgs.command || enrichedArgs.cmd || 'unknown';
1071
1229
  if (this.sessionPermissionGrants.has(canonicalToolName)) {
1072
1230
  proceed = true;
@@ -1088,11 +1246,12 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1088
1246
  }
1089
1247
 
1090
1248
  let result;
1091
- if (argParseError) {
1249
+ if (argParseError && !canUseRecoveredArgs) {
1092
1250
  result = {
1093
1251
  success: false,
1094
1252
  error: `Invalid ${canonicalToolName} tool arguments JSON: ${toolArgs.__toolArgParseError}`,
1095
1253
  rawArgs: toolArgs.__rawToolArgs,
1254
+ recovery: 'Use valid JSON object arguments, for example {"file_path":"README.md"} for Read or {"command":"npm test"} for Bash.',
1096
1255
  };
1097
1256
  } else if (!proceed) {
1098
1257
  result = { success: false, error: 'User denied permission to execute this command.' };
@@ -1142,7 +1301,7 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1142
1301
  console.log(`\n${colors.yellow}${finalContent}${colors.reset}\n`);
1143
1302
  }
1144
1303
 
1145
- return finalContent;
1304
+ return { finalContent, usedTools };
1146
1305
  }
1147
1306
 
1148
1307
  async requestAssistantTurn(messages, options, startedAt, totalUsage) {
@@ -1430,7 +1589,7 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1430
1589
  }
1431
1590
 
1432
1591
  enrichToolArgs(toolName, toolArgs = {}, messages = []) {
1433
- const args = toolArgs && typeof toolArgs === 'object' ? { ...toolArgs } : {};
1592
+ const args = this.tools.normalizeToolInput?.(toolName, toolArgs) || (toolArgs && typeof toolArgs === 'object' ? { ...toolArgs } : {});
1434
1593
  const fallbackPath = this.extractPathFromMessages(messages);
1435
1594
 
1436
1595
  if (fallbackPath) {
@@ -1460,6 +1619,21 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1460
1619
  return args;
1461
1620
  }
1462
1621
 
1622
+ recoverToolArgs(toolName, rawArgs) {
1623
+ const raw = String(rawArgs || '').trim();
1624
+ if (!raw) return null;
1625
+ if (/^[{[]/.test(raw)) return null;
1626
+
1627
+ const cleaned = raw
1628
+ .replace(/^```(?:json|tool|tool_call)?\s*/i, '')
1629
+ .replace(/```$/i, '')
1630
+ .trim()
1631
+ .replace(/^['"]|['"]$/g, '');
1632
+ if (!cleaned) return null;
1633
+
1634
+ return this.tools.normalizeToolInput?.(toolName, cleaned) || null;
1635
+ }
1636
+
1463
1637
  extractPathFromMessages(messages = []) {
1464
1638
  for (let i = messages.length - 1; i >= 0; i--) {
1465
1639
  const content = messages[i]?.content;
@@ -1793,7 +1967,7 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1793
1967
  async chat(message, imageAttachments = []) {
1794
1968
  try {
1795
1969
  const needsTools = true;
1796
- const context = await this.getProjectContext();
1970
+ const context = await this.getProjectContext(message);
1797
1971
  const messages = [
1798
1972
  { role: 'system', content: this.getSystemPrompt(context) }
1799
1973
  ];
@@ -1802,7 +1976,7 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1802
1976
  const promptHistory = this.getCompressedPromptHistory({
1803
1977
  limit: 20,
1804
1978
  keepRecent: 14,
1805
- maxTotalChars: 12000,
1979
+ maxTotalChars: this.shouldUseCompactPrompt() ? 5000 : 12000,
1806
1980
  });
1807
1981
  if (promptHistory.summary) {
1808
1982
  messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
@@ -1827,16 +2001,100 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1827
2001
  }
1828
2002
 
1829
2003
  const tools = this.getAgentTools('general');
1830
- const finalContent = await this.runConversation(messages, 'Thinking', tools);
2004
+ const { finalContent, usedTools } = await this.runConversation(messages, 'Thinking', tools);
1831
2005
 
1832
2006
  await this.session.addToHistory({ role: 'user', content: message });
1833
2007
  await this.session.addToHistory({ role: 'assistant', content: finalContent });
1834
2008
 
2009
+ // Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build
2010
+ if (usedTools && finalContent) {
2011
+ await this.verifyAndHeal(messages, tools, 5);
2012
+ }
2013
+
1835
2014
  } catch (error) {
1836
2015
  console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
1837
2016
  }
1838
2017
  }
1839
2018
 
2019
+ /**
2020
+ * Chạy verification commands (test, build) và trả về kết quả
2021
+ */
2022
+ async runVerification(commands = ['npm test']) {
2023
+ const { execSync } = await import('child_process');
2024
+ const results = [];
2025
+
2026
+ for (const cmd of commands) {
2027
+ try {
2028
+ const output = execSync(cmd, {
2029
+ timeout: 120000,
2030
+ encoding: 'utf8',
2031
+ stdio: ['pipe', 'pipe', 'pipe'],
2032
+ });
2033
+ results.push({ cmd, passed: true, output: output.trim() });
2034
+ } catch (error) {
2035
+ const stderr = error.stderr || '';
2036
+ const stdout = error.stdout || '';
2037
+ results.push({ cmd, passed: false, output: (stdout + '\n' + stderr).trim() });
2038
+ }
2039
+ }
2040
+
2041
+ return {
2042
+ passed: results.every(r => r.passed),
2043
+ details: results,
2044
+ };
2045
+ }
2046
+
2047
+ /**
2048
+ * Vòng lặp tự động verify + sửa lỗi:
2049
+ * - Chạy test/build
2050
+ * - Nếu fail, gửi lỗi cho AI fix
2051
+ * - Lặp đến khi pass hết hoặc hết số lần thử
2052
+ */
2053
+ async verifyAndHeal(messages, tools, maxAttempts = 5) {
2054
+ const verifCommands = ['npm test'];
2055
+
2056
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2057
+ console.log(`\n${colors.cyan}=== Verification Attempt ${attempt}/${maxAttempts} ===${colors.reset}`);
2058
+
2059
+ const result = await this.runVerification(verifCommands);
2060
+
2061
+ if (result.passed) {
2062
+ console.log(`\n${colors.green}✅ All verifications passed!${colors.reset}\n`);
2063
+ return;
2064
+ }
2065
+
2066
+ // Collect error details
2067
+ const errorDetails = result.details
2068
+ .filter(r => !r.passed)
2069
+ .map(r => `Command: ${r.cmd}\n${r.output}`)
2070
+ .join('\n\n---\n\n');
2071
+
2072
+ console.log(`\n${colors.yellow}⚠ Verification failed. Sending errors back to AI for fix...${colors.reset}\n`);
2073
+
2074
+ // Push error output as user message for AI to fix
2075
+ const fixPrompt = `VERIFICATION FAILED (attempt ${attempt}/${maxAttempts}):
2076
+
2077
+ The following commands produced errors:
2078
+
2079
+ ${errorDetails}
2080
+
2081
+ The system will re-run verification automatically after you fix. Please FIX ALL ERRORS above.
2082
+ Do NOT stop until all errors are resolved.`;
2083
+
2084
+ messages.push({ role: 'user', content: fixPrompt });
2085
+
2086
+ // Let the AI fix the issues
2087
+ const { usedTools: fixUsedTools } = await this.runConversation(messages, 'Fixing', tools);
2088
+
2089
+ if (!fixUsedTools) {
2090
+ console.log(`\n${colors.red}⚠ AI did not attempt to fix the errors. Stopping.${colors.reset}\n`);
2091
+ break;
2092
+ }
2093
+ }
2094
+
2095
+ console.log(`\n${colors.red}⚠ Max verification attempts (${maxAttempts}) reached. Some issues may remain.${colors.reset}\n`);
2096
+ }
2097
+
1840
2098
  shouldUseTools(message = '', imageAttachments = []) {
1841
2099
  return true;
1842
2100
  }
@@ -1863,12 +2121,16 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1863
2121
  }
1864
2122
 
1865
2123
  async runAgent(role, task) {
1866
- const context = await this.getProjectContext();
2124
+ const context = await this.getProjectContext(task);
1867
2125
  const messages = [
1868
2126
  { role: 'system', content: this.getAgentSystemPrompt(role, context) }
1869
2127
  ];
1870
2128
 
1871
- const promptHistory = this.getCompressedPromptHistory({ limit: 30, keepRecent: 14, maxTotalChars: 12000 });
2129
+ const promptHistory = this.getCompressedPromptHistory({
2130
+ limit: this.shouldUseCompactPrompt() ? 14 : 30,
2131
+ keepRecent: this.shouldUseCompactPrompt() ? 8 : 14,
2132
+ maxTotalChars: this.shouldUseCompactPrompt() ? 5000 : 12000,
2133
+ });
1872
2134
  if (promptHistory.summary) {
1873
2135
  messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
1874
2136
  }
@@ -1878,19 +2140,29 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1878
2140
 
1879
2141
  messages.push({ role: 'user', content: `Task: ${task}` });
1880
2142
 
1881
- const finalContent = await this.runConversation(messages, `Subagent [${role}]`, this.getAgentTools(role));
2143
+ const agentTools = this.getAgentTools(role);
2144
+ const { finalContent, usedTools } = await this.runConversation(messages, `Subagent [${role}]`, agentTools);
1882
2145
 
1883
2146
  await this.session.addToHistory({ role: 'user', content: `[subagent:${role}] ${task}` });
1884
2147
  await this.session.addToHistory({ role: 'assistant', content: finalContent });
2148
+
2149
+ if (usedTools && finalContent) {
2150
+ await this.verifyAndHeal(messages, agentTools, 3);
2151
+ }
1885
2152
  }
1886
2153
 
1887
- async getProjectContext() {
2154
+ async getProjectContext(task = '') {
1888
2155
  const context = [];
2156
+ const requiredLocalResources = await this.getRequiredLocalResourceSummary();
2157
+ if (requiredLocalResources) {
2158
+ context.push(requiredLocalResources);
2159
+ }
2160
+
1889
2161
  const projectInstructionFiles = await this.readProjectInstructionFiles();
1890
2162
 
1891
2163
  for (const file of projectInstructionFiles) {
1892
2164
  try {
1893
- const preview = this.compactText(file.content, 900, 'project instruction');
2165
+ const preview = this.compactText(file.content, this.shouldUseCompactPrompt() ? 450 : 900, 'project instruction');
1894
2166
  context.push(`[${file.relativePath}]\n${preview}`);
1895
2167
  } catch { }
1896
2168
  }
@@ -1900,11 +2172,12 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1900
2172
  const stat = await fs.stat(packageJsonPath);
1901
2173
  if (stat.isFile()) {
1902
2174
  const content = await fs.readFile(packageJsonPath, 'utf-8');
1903
- context.push(`[package.json]\n${this.compactText(content, 1200, 'package.json')}`);
2175
+ context.push(`[package.json]\n${this.compactText(content, this.shouldUseCompactPrompt() ? 650 : 1200, 'package.json')}`);
1904
2176
  }
1905
2177
  } catch { }
1906
2178
 
1907
- const localResources = await this.getLocalResourceContext();
2179
+ const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
2180
+ const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
1908
2181
  if (localResources) {
1909
2182
  context.push(localResources);
1910
2183
  }
@@ -1918,26 +2191,30 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1918
2191
 
1919
2192
  const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
1920
2193
  if (gitSummary) {
1921
- context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
2194
+ context.push(`[Git Summary]\n${this.compactText(gitSummary, this.shouldUseCompactPrompt() ? 650 : 1200, 'git summary')}`);
1922
2195
  }
1923
2196
 
1924
2197
  // Get brief git diff for context
1925
2198
  const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
1926
2199
  if (gitDiff) {
1927
- context.push(`[Git Diff]\n${this.compactText(gitDiff, 1800, 'git diff')}`);
2200
+ context.push(`[Git Diff]\n${this.compactText(gitDiff, this.shouldUseCompactPrompt() ? 900 : 1800, 'git diff')}`);
1928
2201
  }
1929
2202
  }
1930
2203
  } catch (e) {
1931
2204
  // Not a git repo or git not installed
1932
2205
  }
1933
2206
 
1934
- return this.compactText(context.join('\n\n') || 'No project context found.', 9000, 'project context');
2207
+ return this.compactText(context.join('\n\n') || 'No project context found.', this.shouldUseCompactPrompt() ? 4200 : 9000, 'project context');
1935
2208
  }
1936
2209
 
1937
2210
  async getLocalResourceContext() {
1938
2211
  return this.contextLoader.getLocalResourceContext();
1939
2212
  }
1940
2213
 
2214
+ async getRequiredLocalResourceSummary() {
2215
+ return this.contextLoader.getRequiredLocalResourceSummary();
2216
+ }
2217
+
1941
2218
  async bootstrapProjectCapabilities() {
1942
2219
  const sessionContext = this.session.getContext() || {};
1943
2220
 
@@ -1947,10 +2224,10 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1947
2224
  'Inspect rules, resources, and likely skills before doing any task work.'
1948
2225
  );
1949
2226
  await this.session.addPlanStep(plan.id, {
1950
- description: 'Read project rules, local resources, and attached skill libraries.',
2227
+ description: 'Read required local resources, project rules, and attached skill libraries.',
1951
2228
  });
1952
2229
  await this.session.addPlanStep(plan.id, {
1953
- description: 'Choose the smallest relevant skill set before making changes.',
2230
+ description: 'Ground every model in required resource rules before making changes.',
1954
2231
  });
1955
2232
  await this.session.updateContext('bootstrapPlan', {
1956
2233
  id: plan.id,
@@ -1959,6 +2236,12 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
1959
2236
  });
1960
2237
  }
1961
2238
 
2239
+ const requiredLocalResources = await this.getRequiredLocalResourceSummary();
2240
+ if (requiredLocalResources) {
2241
+ await this.session.updateContext('requiredLocalResources', requiredLocalResources);
2242
+ await this.session.replaceMemory('[Required local resources]', requiredLocalResources, 'resource');
2243
+ }
2244
+
1962
2245
  const skillSnapshot = await this.inferStartupSkills();
1963
2246
  await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
1964
2247
  await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
@@ -2018,119 +2301,18 @@ ${colors.yellow}ℹ AI tool loop detected. Breaking out.${colors.reset}`);
2018
2301
  }
2019
2302
  getSystemPrompt(context = '') {
2020
2303
  this.hydrateSessionToolPermissions();
2021
- const memories = this.session.getMemory();
2022
- const plans = this.session.getPlans();
2023
- const sessionContext = this.session.getContext() || {};
2024
- const environmentSummary = this.tools?.getRuntimeEnvironmentSummary?.() || [
2025
- `Host OS: ${process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : process.platform === 'linux' ? 'Linux' : process.platform}`,
2026
- `Node platform: ${process.platform}`,
2027
- `Current shell hint: ${process.platform === 'win32' ? 'powershell-capable or cmd/unknown' : (process.env.SHELL || 'bash/sh')}`,
2028
- process.platform === 'win32'
2029
- ? 'Shell rule: Use shell:"powershell" for PowerShell cmdlets, shell:"cmd" for cmd.exe syntax, and shell:"auto" when unsure.'
2030
- : 'Shell rule: Use the native POSIX shell on non-Windows hosts and leave shell unspecified unless a specific shell is required.',
2031
- ].join('\n');
2032
-
2033
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2034
- limit: 8,
2035
- maxEntryChars: 220,
2036
- maxTotalChars: 1600,
2037
- mapper: memory => memory.text,
2038
- })}` : '';
2039
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2040
- limit: 6,
2041
- maxEntryChars: 260,
2042
- maxTotalChars: 1600,
2043
- mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2044
- })}` : '';
2045
- let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2046
- ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2047
- : '';
2048
- let startupPlanStr = sessionContext.bootstrapPlan?.title
2049
- ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2050
- : '';
2051
- const sessionSignalsStr = `\n${this.buildSessionSignalsPrompt()}`;
2052
-
2053
- return `You are Winter, an expert AI coding assistant.
2054
-
2055
- ## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2056
- 1. [THINKING BEFORE CODING]: State assumptions, constraints, and a brief plan before making changes. Be thorough enough to be useful, and do not invent facts.
2057
- 2. [DESIGN EXCELLENCE]: Use rich aesthetics. Default to modern UI frameworks if applicable. Never output plain, ugly HTML/CSS. Ensure responsive, premium feel with micro-animations.
2058
- 3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2059
- 4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
2060
- 5. [TOOL EXECUTION FIRST]: NEVER output full code blocks to the chat and tell the user to copy-paste. ALWAYS use the 'Write' or 'Edit' tool to apply changes directly to their files! The user cannot copy-paste code. You MUST do the work using tools.
2061
-
2062
- ## AGENT OPERATING MODE
2063
- - Treat the repository, its memories, skills, rules, and bundled local resources as first-class context.
2064
- - Before answering a task that may depend on project state, rules, skills, memories, local resources, or external facts, proactively call the relevant tool(s) to inspect them.
2065
- - Prefer using the full tool set when needed: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2066
- - If a question is ambiguous, inspect the project context first instead of guessing.
2067
- - Winter's job is to amplify weaker models: decompose problems, pull the right context, and use tools to reach a stronger answer than raw inference alone would produce.
2068
- - Always begin with a short plan before coding or tool execution, then refine the plan if new facts appear.
2069
-
2070
- ## Runtime Environment
2071
- ${environmentSummary}
2072
- ${sessionSignalsStr}
2073
-
2074
- ## CRITICAL LANGUAGE RULE
2075
- **You MUST always respond in Vietnamese (tiếng Việt).** Never respond in Chinese, Japanese, Korean or any other language unless the user explicitly asks you to. This is non-negotiable.
2076
-
2077
- ## Core Principles
2078
- 1. **Think Before Coding** - State assumptions, ask when unclear
2079
- 2. **Simplicity First** - Minimum code that solves the problem
2080
- 3. **Surgical Changes** - Touch only what you must
2081
- 4. **Goal-Driven Execution** - Define success criteria, verify results
2082
-
2083
- ## Tools Available
2084
- - Read, Write, Edit - File operations
2085
- - Write - Create/overwrite files directly. Use this instead of Bash echo/cat/heredoc for writing code.
2086
- - Edit - Replace exact text in existing files.
2087
- - Bash - Execute shell commands. Current OS is ${process.platform === 'win32' ? 'Windows; Bash auto-detects PowerShell and cmd.exe syntax. Use shell="powershell" or shell="cmd" when needed.' : process.platform}.
2088
- - Glob - Find files
2089
- - Grep - Search content
2090
- - TaskCreate, TaskUpdate, TaskList - Task management
2091
- - WebFetch, WebSearch - Research
2092
- - Vision - Analyze images/screenshots for debugging
2093
-
2094
- ## Guidelines
2095
- - Call tools when they help - be proactive
2096
- - You DO have file write tools. Never say "there is no write tool"; use Write or Edit.
2097
- - If a tool name fails, call the canonical tool name next: Write, Edit, Read, Bash, Glob, or Grep.
2098
- - On Windows, Bash accepts both PowerShell and cmd.exe commands. Prefer Write with full content for file writes.
2099
- - After using tools, always provide a direct final answer to the user.
2100
- - Never claim that you changed files unless a Write, Edit, Bash, or equivalent tool result shows the change succeeded in this turn.
2101
- - Never emit XML or provider-specific pseudo tool syntax like <minimax:tool_call>. Use the actual tool-calling API only.
2102
- - If a file path is unknown, search with Glob/Grep first instead of inventing names like Nav.tsx or Footer.tsx.
2103
- - Answer normal questions directly without unnecessary legal or policy disclaimers.
2104
- - If a request is illegal, unsafe, or harmful, refuse briefly and offer a safe alternative.
2105
- - Read files before modifying
2106
- - Make surgical changes
2107
- - Verify your work
2108
- - Follow project conventions (check CLAUDE.md)
2109
- - When user attaches an image, analyze it carefully for UI bugs, errors, layout issues
2110
-
2111
- ## Project
2112
- Working directory: ${this.projectPath}
2113
- Current session: ${this.session.getSessionId().substring(0, 8)}
2114
- ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2115
- ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}
2116
-
2117
- Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2304
+ this.promptBuilder.session = this.session;
2305
+ this.promptBuilder.ai = this.ai;
2306
+ this.promptBuilder.tools = this.tools;
2307
+ this.promptBuilder.sessionPermissionGrants = this.sessionPermissionGrants;
2308
+ return this.promptBuilder.buildSystemPrompt(context, {
2309
+ projectContextBudget: this.shouldUseCompactPrompt() ? 2200 : 3200,
2310
+ });
2118
2311
  }
2119
2312
 
2120
2313
  getFastSystemPrompt() {
2121
- const memories = this.session.getMemory();
2122
- const memoryStr = memories.length > 0
2123
- ? `\nContext nhớ ngắn:\n${this.summarizePromptList(memories.slice(-8), {
2124
- limit: 8,
2125
- maxEntryChars: 160,
2126
- maxTotalChars: 1200,
2127
- mapper: memory => memory.text,
2128
- })}`
2129
- : '';
2130
-
2131
- return `Bạn là Winter, trợ lý AI trả lời ngắn gọn bằng tiếng Việt.
2132
- Ưu tiên dùng tool và context khi cần; không bịa thông tin.
2133
- Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì hãy gọi tool tương ứng thay vì chỉ nói chung chung.${memoryStr}`;
2314
+ this.promptBuilder.session = this.session;
2315
+ return this.promptBuilder.buildFastSystemPrompt();
2134
2316
  }
2135
2317
 
2136
2318
  // Tab completion
@@ -2150,73 +2332,10 @@ Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2150
2332
  }
2151
2333
 
2152
2334
  getAgentSystemPrompt(role, context = '') {
2153
- const memories = this.session.getMemory();
2154
- const plans = this.session.getPlans();
2155
- const sessionContext = this.session.getContext() || {};
2156
-
2157
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2158
- limit: 8,
2159
- maxEntryChars: 220,
2160
- maxTotalChars: 1600,
2161
- mapper: memory => memory.text,
2162
- })}` : '';
2163
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2164
- limit: 6,
2165
- maxEntryChars: 260,
2166
- maxTotalChars: 1600,
2167
- mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2168
- })}` : '';
2169
- let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2170
- ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2171
- : '';
2172
- let startupPlanStr = sessionContext.bootstrapPlan?.title
2173
- ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2174
- : '';
2175
-
2176
- let rolePrompt = '';
2177
- switch (role) {
2178
- case 'plan':
2179
- rolePrompt = `You are a Winter planning subagent. Break the request into a concise step-by-step plan, note dependencies, and keep the response short.`;
2180
- break;
2181
- case 'review':
2182
- rolePrompt = `You are a Winter review subagent. Critique the request or implementation with specific issues, edge cases, and concrete improvements.`;
2183
- break;
2184
- case 'debug':
2185
- rolePrompt = `You are a Winter debugging subagent. Focus on root cause, reproduction, and the smallest fix.`;
2186
- break;
2187
- case 'research':
2188
- rolePrompt = `You are a Winter research subagent. Gather the important facts, compare options, and summarize only what matters.`;
2189
- break;
2190
- case 'browser':
2191
- rolePrompt = `You are a Winter browser subagent. Bạn CÓ QUYỀN sử dụng tool 'BrowserDebug' để tương tác với trình duyệt. Hãy dùng nó để mở URL, chụp ảnh màn hình (nếu cần), hoặc chạy JS để kiểm tra trang web.`;
2192
- break;
2193
- default:
2194
- rolePrompt = `You are a Winter coding subagent. Solve the task directly, use tools when needed, and return a concise result.`;
2195
- break;
2196
- }
2197
-
2198
- return `## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2199
- 1. [THINKING BEFORE CODING]: State assumptions, constraints, and a brief plan before making changes. Be thorough enough to be useful, and do not invent facts.
2200
- 2. [DESIGN EXCELLENCE]: Use rich aesthetics. Default to modern UI frameworks if applicable. Never output plain, ugly HTML/CSS. Ensure responsive, premium feel with micro-animations.
2201
- 3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2202
- 4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
2203
- 5. [TOOL EXECUTION FIRST]: You DO have file tools. Use Write to create/overwrite files and Edit to patch files. Never say there is no write tool.
2204
-
2205
- ${rolePrompt}
2206
-
2207
- ## Tool Rules
2208
- - Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2209
- - Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.
2210
- - Current OS is ${process.platform === 'win32' ? 'Windows; Bash auto-detects PowerShell and cmd.exe syntax. Use shell="powershell" or shell="cmd" when needed.' : process.platform}.
2211
- - Prefer Write/Edit for writing files. Bash accepts both PowerShell and cmd.exe on Windows, but do not use long echo chains for code files.
2212
- - If a tool call fails because of an unknown alias, call the canonical tool name next.
2213
- - Always start with a brief plan, then refine it when new facts appear.
2214
-
2215
- ## Project
2216
- Working directory: ${this.projectPath}
2217
- Current session: ${this.session.getSessionId().substring(0, 8)}
2218
- ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2219
- ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}`;
2335
+ this.promptBuilder.session = this.session;
2336
+ this.promptBuilder.ai = this.ai;
2337
+ this.promptBuilder.tools = this.tools;
2338
+ return this.promptBuilder.buildAgentSystemPrompt(role, context);
2220
2339
  }
2221
2340
 
2222
2341
  async handleMcpCommand(args) {