winter-super-cli 2026.5.27 → 2026.5.29

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();
@@ -241,6 +348,7 @@ export class WinterREPL {
241
348
  // Show banner only if not already shown
242
349
  if (!process.env.WINTER_BANNER_SHOWN) {
243
350
  console.log(welcomeBanner(this.version, info));
351
+ this.showCommandMenu();
244
352
  process.env.WINTER_BANNER_SHOWN = '1';
245
353
  } else {
246
354
  this.showStatus();
@@ -285,21 +393,23 @@ export class WinterREPL {
285
393
  });
286
394
 
287
395
  // Hiển thị prompt lần đầu tiên ngay khi khởi động xong
288
- this.rl.prompt();
396
+ this.showInputPrompt();
289
397
 
290
398
  this.rl.on('line', (line) => {
291
399
  this.inputQueue = this.inputQueue
292
400
  .then(async () => {
401
+ this.closeInputBox();
293
402
  const input = line.trim();
294
403
  if (input) {
295
404
  await this.handleInput(input);
296
405
  } else {
297
- if (this.running && !this.readlineClosed) this.rl.prompt();
406
+ if (this.running && !this.readlineClosed) this.showInputPrompt();
298
407
  }
299
408
  })
300
409
  .catch((error) => {
410
+ this.closeInputBox();
301
411
  console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
302
- if (this.running && !this.readlineClosed) this.rl.prompt();
412
+ if (this.running && !this.readlineClosed) this.showInputPrompt();
303
413
  });
304
414
  });
305
415
 
@@ -311,10 +421,33 @@ export class WinterREPL {
311
421
  });
312
422
  }
313
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
+
314
446
  showStatus() {
315
447
  console.log(`${colors.dim}Project: ${this.projectPath}${colors.reset}`);
316
448
  console.log(`${colors.dim}Provider: ${this.ai.getActiveProvider()}${colors.reset}`);
317
449
  console.log(`${colors.dim}Session: ${this.session.getSessionId().substring(0, 8)}${colors.reset}`);
450
+ console.log(`${colors.dim}Type ${colors.cyan}/help${colors.dim} for commands or ${colors.cyan}/${colors.dim} for menu${colors.reset}`);
318
451
  console.log('');
319
452
  }
320
453
 
@@ -498,7 +631,7 @@ export class WinterREPL {
498
631
  const nextTask = this.taskQueue.shift();
499
632
  setTimeout(() => this.processInputTask(nextTask), 0);
500
633
  } else {
501
- if (!this.readlineClosed) this.rl.prompt(true);
634
+ if (!this.readlineClosed) this.showInputPrompt();
502
635
  }
503
636
  }
504
637
  }
@@ -944,10 +1077,20 @@ ${colors.reset}
944
1077
  case 'research':
945
1078
  return byName(['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch']);
946
1079
  default:
947
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList']);
1080
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep']);
948
1081
  }
949
1082
  }
950
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
+
951
1094
  selectExecutionProfile(messages = [], options = {}) {
952
1095
  if (typeof this.ai?.selectExecutionProfile === 'function') {
953
1096
  const profile = this.ai.selectExecutionProfile(messages, options);
@@ -999,9 +1142,10 @@ ${colors.reset}
999
1142
  let finalContent = '';
1000
1143
  let reachedToolLimit = true;
1001
1144
  let usedTools = false;
1145
+ let verified = false;
1002
1146
  const toolSummaries = [];
1003
1147
  const totalUsage = {};
1004
- let lastToolSignature = null;
1148
+ const toolSignatureHistory = [];
1005
1149
  const executionProfile = this.selectExecutionProfile(messages, { enableTools: true });
1006
1150
  try {
1007
1151
  for (let i = 0; i < 8; i++) {
@@ -1040,13 +1184,21 @@ ${colors.reset}
1040
1184
  if (this.spinner) this.spinner.stop();
1041
1185
 
1042
1186
  const currentToolSignature = this.buildToolCallSignature(toolCalls);
1043
- if (currentToolSignature && currentToolSignature === lastToolSignature) {
1044
- console.log(`
1045
- ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng vòng lặp để tránh spam.${colors.reset}`);
1046
- reachedToolLimit = false;
1047
- break;
1187
+ if (currentToolSignature) {
1188
+ toolSignatureHistory.push(currentToolSignature);
1189
+ if (toolSignatureHistory.length > 3) {
1190
+ toolSignatureHistory.shift();
1191
+ }
1192
+ // Only break if 3+ consecutive identical signatures — 2 repeats is normal iteration
1193
+ if (toolSignatureHistory.length === 3 &&
1194
+ toolSignatureHistory[0] === currentToolSignature &&
1195
+ toolSignatureHistory[1] === currentToolSignature) {
1196
+ console.log(`
1197
+ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls). Breaking out.${colors.reset}`);
1198
+ reachedToolLimit = false;
1199
+ break;
1200
+ }
1048
1201
  }
1049
- lastToolSignature = currentToolSignature;
1050
1202
 
1051
1203
  const BOX_WIDTH = terminalWidth(76, 116, 92);
1052
1204
  messages.push({
@@ -1059,12 +1211,17 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1059
1211
  const { toolName, toolArgs } = tc;
1060
1212
  const canonicalToolName = this.tools.normalizeToolName(toolName);
1061
1213
  const argParseError = toolArgs?.__toolArgParseError;
1062
- const enrichedArgs = argParseError ? {} : this.enrichToolArgs(canonicalToolName, toolArgs, messages);
1214
+ const recoveredArgs = argParseError ? this.recoverToolArgs(canonicalToolName, toolArgs.__rawToolArgs) : null;
1215
+ const canUseRecoveredArgs = recoveredArgs && Object.keys(recoveredArgs).length > 0;
1216
+ const normalizedArgs = argParseError && !canUseRecoveredArgs
1217
+ ? {}
1218
+ : this.tools.normalizeToolInput?.(canonicalToolName, canUseRecoveredArgs ? recoveredArgs : toolArgs) ?? toolArgs;
1219
+ const enrichedArgs = argParseError && !canUseRecoveredArgs ? {} : this.enrichToolArgs(canonicalToolName, normalizedArgs, messages);
1063
1220
 
1064
1221
  const icon = canonicalToolName === 'Bash' ? '⚙' : canonicalToolName === 'Read' ? '📖' : canonicalToolName === 'Write' ? '✏️' : canonicalToolName === 'Edit' ? '🔧' : canonicalToolName === 'Grep' ? '🔍' : canonicalToolName === 'Glob' ? '📂' : '⚡';
1065
1222
 
1066
1223
  let proceed = true;
1067
- if (await this.shouldPromptForToolPermission(canonicalToolName) && !argParseError) {
1224
+ if (await this.shouldPromptForToolPermission(canonicalToolName) && (!argParseError || canUseRecoveredArgs)) {
1068
1225
  const cmd = enrichedArgs.command || enrichedArgs.cmd || 'unknown';
1069
1226
  if (this.sessionPermissionGrants.has(canonicalToolName)) {
1070
1227
  proceed = true;
@@ -1086,11 +1243,12 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1086
1243
  }
1087
1244
 
1088
1245
  let result;
1089
- if (argParseError) {
1246
+ if (argParseError && !canUseRecoveredArgs) {
1090
1247
  result = {
1091
1248
  success: false,
1092
1249
  error: `Invalid ${canonicalToolName} tool arguments JSON: ${toolArgs.__toolArgParseError}`,
1093
1250
  rawArgs: toolArgs.__rawToolArgs,
1251
+ recovery: 'Use valid JSON object arguments, for example {"file_path":"README.md"} for Read or {"command":"npm test"} for Bash.',
1094
1252
  };
1095
1253
  } else if (!proceed) {
1096
1254
  result = { success: false, error: 'User denied permission to execute this command.' };
@@ -1140,7 +1298,7 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1140
1298
  console.log(`\n${colors.yellow}${finalContent}${colors.reset}\n`);
1141
1299
  }
1142
1300
 
1143
- return finalContent;
1301
+ return { finalContent, usedTools };
1144
1302
  }
1145
1303
 
1146
1304
  async requestAssistantTurn(messages, options, startedAt, totalUsage) {
@@ -1428,7 +1586,7 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1428
1586
  }
1429
1587
 
1430
1588
  enrichToolArgs(toolName, toolArgs = {}, messages = []) {
1431
- const args = toolArgs && typeof toolArgs === 'object' ? { ...toolArgs } : {};
1589
+ const args = this.tools.normalizeToolInput?.(toolName, toolArgs) || (toolArgs && typeof toolArgs === 'object' ? { ...toolArgs } : {});
1432
1590
  const fallbackPath = this.extractPathFromMessages(messages);
1433
1591
 
1434
1592
  if (fallbackPath) {
@@ -1458,6 +1616,21 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1458
1616
  return args;
1459
1617
  }
1460
1618
 
1619
+ recoverToolArgs(toolName, rawArgs) {
1620
+ const raw = String(rawArgs || '').trim();
1621
+ if (!raw) return null;
1622
+ if (/^[{[]/.test(raw)) return null;
1623
+
1624
+ const cleaned = raw
1625
+ .replace(/^```(?:json|tool|tool_call)?\s*/i, '')
1626
+ .replace(/```$/i, '')
1627
+ .trim()
1628
+ .replace(/^['"]|['"]$/g, '');
1629
+ if (!cleaned) return null;
1630
+
1631
+ return this.tools.normalizeToolInput?.(toolName, cleaned) || null;
1632
+ }
1633
+
1461
1634
  extractPathFromMessages(messages = []) {
1462
1635
  for (let i = messages.length - 1; i >= 0; i--) {
1463
1636
  const content = messages[i]?.content;
@@ -1791,7 +1964,7 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1791
1964
  async chat(message, imageAttachments = []) {
1792
1965
  try {
1793
1966
  const needsTools = true;
1794
- const context = await this.getProjectContext();
1967
+ const context = await this.getProjectContext(message);
1795
1968
  const messages = [
1796
1969
  { role: 'system', content: this.getSystemPrompt(context) }
1797
1970
  ];
@@ -1800,7 +1973,7 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1800
1973
  const promptHistory = this.getCompressedPromptHistory({
1801
1974
  limit: 20,
1802
1975
  keepRecent: 14,
1803
- maxTotalChars: 12000,
1976
+ maxTotalChars: this.shouldUseCompactPrompt() ? 5000 : 12000,
1804
1977
  });
1805
1978
  if (promptHistory.summary) {
1806
1979
  messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
@@ -1825,16 +1998,100 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1825
1998
  }
1826
1999
 
1827
2000
  const tools = this.getAgentTools('general');
1828
- const finalContent = await this.runConversation(messages, 'Thinking', tools);
2001
+ const { finalContent, usedTools } = await this.runConversation(messages, 'Thinking', tools);
1829
2002
 
1830
2003
  await this.session.addToHistory({ role: 'user', content: message });
1831
2004
  await this.session.addToHistory({ role: 'assistant', content: finalContent });
1832
2005
 
2006
+ // Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build
2007
+ if (usedTools && finalContent) {
2008
+ await this.verifyAndHeal(messages, tools, 5);
2009
+ }
2010
+
1833
2011
  } catch (error) {
1834
2012
  console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
1835
2013
  }
1836
2014
  }
1837
2015
 
2016
+ /**
2017
+ * Chạy verification commands (test, build) và trả về kết quả
2018
+ */
2019
+ async runVerification(commands = ['npm test']) {
2020
+ const { execSync } = await import('child_process');
2021
+ const results = [];
2022
+
2023
+ for (const cmd of commands) {
2024
+ try {
2025
+ const output = execSync(cmd, {
2026
+ timeout: 120000,
2027
+ encoding: 'utf8',
2028
+ stdio: ['pipe', 'pipe', 'pipe'],
2029
+ });
2030
+ results.push({ cmd, passed: true, output: output.trim() });
2031
+ } catch (error) {
2032
+ const stderr = error.stderr || '';
2033
+ const stdout = error.stdout || '';
2034
+ results.push({ cmd, passed: false, output: (stdout + '\n' + stderr).trim() });
2035
+ }
2036
+ }
2037
+
2038
+ return {
2039
+ passed: results.every(r => r.passed),
2040
+ details: results,
2041
+ };
2042
+ }
2043
+
2044
+ /**
2045
+ * Vòng lặp tự động verify + sửa lỗi:
2046
+ * - Chạy test/build
2047
+ * - Nếu fail, gửi lỗi cho AI fix
2048
+ * - Lặp đến khi pass hết hoặc hết số lần thử
2049
+ */
2050
+ async verifyAndHeal(messages, tools, maxAttempts = 5) {
2051
+ const verifCommands = ['npm test'];
2052
+
2053
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
2054
+ console.log(`\n${colors.cyan}=== Verification Attempt ${attempt}/${maxAttempts} ===${colors.reset}`);
2055
+
2056
+ const result = await this.runVerification(verifCommands);
2057
+
2058
+ if (result.passed) {
2059
+ console.log(`\n${colors.green}✅ All verifications passed!${colors.reset}\n`);
2060
+ return;
2061
+ }
2062
+
2063
+ // Collect error details
2064
+ const errorDetails = result.details
2065
+ .filter(r => !r.passed)
2066
+ .map(r => `Command: ${r.cmd}\n${r.output}`)
2067
+ .join('\n\n---\n\n');
2068
+
2069
+ console.log(`\n${colors.yellow}⚠ Verification failed. Sending errors back to AI for fix...${colors.reset}\n`);
2070
+
2071
+ // Push error output as user message for AI to fix
2072
+ const fixPrompt = `VERIFICATION FAILED (attempt ${attempt}/${maxAttempts}):
2073
+
2074
+ The following commands produced errors:
2075
+
2076
+ ${errorDetails}
2077
+
2078
+ The system will re-run verification automatically after you fix. Please FIX ALL ERRORS above.
2079
+ Do NOT stop until all errors are resolved.`;
2080
+
2081
+ messages.push({ role: 'user', content: fixPrompt });
2082
+
2083
+ // Let the AI fix the issues
2084
+ const { usedTools: fixUsedTools } = await this.runConversation(messages, 'Fixing', tools);
2085
+
2086
+ if (!fixUsedTools) {
2087
+ console.log(`\n${colors.red}⚠ AI did not attempt to fix the errors. Stopping.${colors.reset}\n`);
2088
+ break;
2089
+ }
2090
+ }
2091
+
2092
+ console.log(`\n${colors.red}⚠ Max verification attempts (${maxAttempts}) reached. Some issues may remain.${colors.reset}\n`);
2093
+ }
2094
+
1838
2095
  shouldUseTools(message = '', imageAttachments = []) {
1839
2096
  return true;
1840
2097
  }
@@ -1861,12 +2118,16 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1861
2118
  }
1862
2119
 
1863
2120
  async runAgent(role, task) {
1864
- const context = await this.getProjectContext();
2121
+ const context = await this.getProjectContext(task);
1865
2122
  const messages = [
1866
2123
  { role: 'system', content: this.getAgentSystemPrompt(role, context) }
1867
2124
  ];
1868
2125
 
1869
- const promptHistory = this.getCompressedPromptHistory({ limit: 30, keepRecent: 14, maxTotalChars: 12000 });
2126
+ const promptHistory = this.getCompressedPromptHistory({
2127
+ limit: this.shouldUseCompactPrompt() ? 14 : 30,
2128
+ keepRecent: this.shouldUseCompactPrompt() ? 8 : 14,
2129
+ maxTotalChars: this.shouldUseCompactPrompt() ? 5000 : 12000,
2130
+ });
1870
2131
  if (promptHistory.summary) {
1871
2132
  messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
1872
2133
  }
@@ -1876,19 +2137,29 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1876
2137
 
1877
2138
  messages.push({ role: 'user', content: `Task: ${task}` });
1878
2139
 
1879
- const finalContent = await this.runConversation(messages, `Subagent [${role}]`, this.getAgentTools(role));
2140
+ const agentTools = this.getAgentTools(role);
2141
+ const { finalContent, usedTools } = await this.runConversation(messages, `Subagent [${role}]`, agentTools);
1880
2142
 
1881
2143
  await this.session.addToHistory({ role: 'user', content: `[subagent:${role}] ${task}` });
1882
2144
  await this.session.addToHistory({ role: 'assistant', content: finalContent });
2145
+
2146
+ if (usedTools && finalContent) {
2147
+ await this.verifyAndHeal(messages, agentTools, 3);
2148
+ }
1883
2149
  }
1884
2150
 
1885
- async getProjectContext() {
2151
+ async getProjectContext(task = '') {
1886
2152
  const context = [];
2153
+ const requiredLocalResources = await this.getRequiredLocalResourceSummary();
2154
+ if (requiredLocalResources) {
2155
+ context.push(requiredLocalResources);
2156
+ }
2157
+
1887
2158
  const projectInstructionFiles = await this.readProjectInstructionFiles();
1888
2159
 
1889
2160
  for (const file of projectInstructionFiles) {
1890
2161
  try {
1891
- const preview = this.compactText(file.content, 900, 'project instruction');
2162
+ const preview = this.compactText(file.content, this.shouldUseCompactPrompt() ? 450 : 900, 'project instruction');
1892
2163
  context.push(`[${file.relativePath}]\n${preview}`);
1893
2164
  } catch { }
1894
2165
  }
@@ -1898,11 +2169,12 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1898
2169
  const stat = await fs.stat(packageJsonPath);
1899
2170
  if (stat.isFile()) {
1900
2171
  const content = await fs.readFile(packageJsonPath, 'utf-8');
1901
- context.push(`[package.json]\n${this.compactText(content, 1200, 'package.json')}`);
2172
+ context.push(`[package.json]\n${this.compactText(content, this.shouldUseCompactPrompt() ? 650 : 1200, 'package.json')}`);
1902
2173
  }
1903
2174
  } catch { }
1904
2175
 
1905
- const localResources = await this.getLocalResourceContext();
2176
+ const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
2177
+ const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
1906
2178
  if (localResources) {
1907
2179
  context.push(localResources);
1908
2180
  }
@@ -1916,26 +2188,30 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1916
2188
 
1917
2189
  const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
1918
2190
  if (gitSummary) {
1919
- context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
2191
+ context.push(`[Git Summary]\n${this.compactText(gitSummary, this.shouldUseCompactPrompt() ? 650 : 1200, 'git summary')}`);
1920
2192
  }
1921
2193
 
1922
2194
  // Get brief git diff for context
1923
2195
  const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
1924
2196
  if (gitDiff) {
1925
- context.push(`[Git Diff]\n${this.compactText(gitDiff, 1800, 'git diff')}`);
2197
+ context.push(`[Git Diff]\n${this.compactText(gitDiff, this.shouldUseCompactPrompt() ? 900 : 1800, 'git diff')}`);
1926
2198
  }
1927
2199
  }
1928
2200
  } catch (e) {
1929
2201
  // Not a git repo or git not installed
1930
2202
  }
1931
2203
 
1932
- return this.compactText(context.join('\n\n') || 'No project context found.', 9000, 'project context');
2204
+ return this.compactText(context.join('\n\n') || 'No project context found.', this.shouldUseCompactPrompt() ? 4200 : 9000, 'project context');
1933
2205
  }
1934
2206
 
1935
2207
  async getLocalResourceContext() {
1936
2208
  return this.contextLoader.getLocalResourceContext();
1937
2209
  }
1938
2210
 
2211
+ async getRequiredLocalResourceSummary() {
2212
+ return this.contextLoader.getRequiredLocalResourceSummary();
2213
+ }
2214
+
1939
2215
  async bootstrapProjectCapabilities() {
1940
2216
  const sessionContext = this.session.getContext() || {};
1941
2217
 
@@ -1945,10 +2221,10 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1945
2221
  'Inspect rules, resources, and likely skills before doing any task work.'
1946
2222
  );
1947
2223
  await this.session.addPlanStep(plan.id, {
1948
- description: 'Read project rules, local resources, and attached skill libraries.',
2224
+ description: 'Read required local resources, project rules, and attached skill libraries.',
1949
2225
  });
1950
2226
  await this.session.addPlanStep(plan.id, {
1951
- description: 'Choose the smallest relevant skill set before making changes.',
2227
+ description: 'Ground every model in required resource rules before making changes.',
1952
2228
  });
1953
2229
  await this.session.updateContext('bootstrapPlan', {
1954
2230
  id: plan.id,
@@ -1957,6 +2233,12 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
1957
2233
  });
1958
2234
  }
1959
2235
 
2236
+ const requiredLocalResources = await this.getRequiredLocalResourceSummary();
2237
+ if (requiredLocalResources) {
2238
+ await this.session.updateContext('requiredLocalResources', requiredLocalResources);
2239
+ await this.session.replaceMemory('[Required local resources]', requiredLocalResources, 'resource');
2240
+ }
2241
+
1960
2242
  const skillSnapshot = await this.inferStartupSkills();
1961
2243
  await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
1962
2244
  await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
@@ -2016,119 +2298,18 @@ ${colors.yellow}ℹ AI đang lặp lại cùng một chuỗi tool call. Dừng v
2016
2298
  }
2017
2299
  getSystemPrompt(context = '') {
2018
2300
  this.hydrateSessionToolPermissions();
2019
- const memories = this.session.getMemory();
2020
- const plans = this.session.getPlans();
2021
- const sessionContext = this.session.getContext() || {};
2022
- const environmentSummary = this.tools?.getRuntimeEnvironmentSummary?.() || [
2023
- `Host OS: ${process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : process.platform === 'linux' ? 'Linux' : process.platform}`,
2024
- `Node platform: ${process.platform}`,
2025
- `Current shell hint: ${process.platform === 'win32' ? 'powershell-capable or cmd/unknown' : (process.env.SHELL || 'bash/sh')}`,
2026
- process.platform === 'win32'
2027
- ? 'Shell rule: Use shell:"powershell" for PowerShell cmdlets, shell:"cmd" for cmd.exe syntax, and shell:"auto" when unsure.'
2028
- : 'Shell rule: Use the native POSIX shell on non-Windows hosts and leave shell unspecified unless a specific shell is required.',
2029
- ].join('\n');
2030
-
2031
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2032
- limit: 8,
2033
- maxEntryChars: 220,
2034
- maxTotalChars: 1600,
2035
- mapper: memory => memory.text,
2036
- })}` : '';
2037
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2038
- limit: 6,
2039
- maxEntryChars: 260,
2040
- maxTotalChars: 1600,
2041
- mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2042
- })}` : '';
2043
- let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2044
- ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2045
- : '';
2046
- let startupPlanStr = sessionContext.bootstrapPlan?.title
2047
- ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2048
- : '';
2049
- const sessionSignalsStr = `\n${this.buildSessionSignalsPrompt()}`;
2050
-
2051
- return `You are Winter, an expert AI coding assistant.
2052
-
2053
- ## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2054
- 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.
2055
- 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.
2056
- 3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2057
- 4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
2058
- 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.
2059
-
2060
- ## AGENT OPERATING MODE
2061
- - Treat the repository, its memories, skills, rules, and bundled local resources as first-class context.
2062
- - 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.
2063
- - Prefer using the full tool set when needed: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2064
- - If a question is ambiguous, inspect the project context first instead of guessing.
2065
- - 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.
2066
- - Always begin with a short plan before coding or tool execution, then refine the plan if new facts appear.
2067
-
2068
- ## Runtime Environment
2069
- ${environmentSummary}
2070
- ${sessionSignalsStr}
2071
-
2072
- ## CRITICAL LANGUAGE RULE
2073
- **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.
2074
-
2075
- ## Core Principles
2076
- 1. **Think Before Coding** - State assumptions, ask when unclear
2077
- 2. **Simplicity First** - Minimum code that solves the problem
2078
- 3. **Surgical Changes** - Touch only what you must
2079
- 4. **Goal-Driven Execution** - Define success criteria, verify results
2080
-
2081
- ## Tools Available
2082
- - Read, Write, Edit - File operations
2083
- - Write - Create/overwrite files directly. Use this instead of Bash echo/cat/heredoc for writing code.
2084
- - Edit - Replace exact text in existing files.
2085
- - 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}.
2086
- - Glob - Find files
2087
- - Grep - Search content
2088
- - TaskCreate, TaskUpdate, TaskList - Task management
2089
- - WebFetch, WebSearch - Research
2090
- - Vision - Analyze images/screenshots for debugging
2091
-
2092
- ## Guidelines
2093
- - Call tools when they help - be proactive
2094
- - You DO have file write tools. Never say "there is no write tool"; use Write or Edit.
2095
- - If a tool name fails, call the canonical tool name next: Write, Edit, Read, Bash, Glob, or Grep.
2096
- - On Windows, Bash accepts both PowerShell and cmd.exe commands. Prefer Write with full content for file writes.
2097
- - After using tools, always provide a direct final answer to the user.
2098
- - Never claim that you changed files unless a Write, Edit, Bash, or equivalent tool result shows the change succeeded in this turn.
2099
- - Never emit XML or provider-specific pseudo tool syntax like <minimax:tool_call>. Use the actual tool-calling API only.
2100
- - If a file path is unknown, search with Glob/Grep first instead of inventing names like Nav.tsx or Footer.tsx.
2101
- - Answer normal questions directly without unnecessary legal or policy disclaimers.
2102
- - If a request is illegal, unsafe, or harmful, refuse briefly and offer a safe alternative.
2103
- - Read files before modifying
2104
- - Make surgical changes
2105
- - Verify your work
2106
- - Follow project conventions (check CLAUDE.md)
2107
- - When user attaches an image, analyze it carefully for UI bugs, errors, layout issues
2108
-
2109
- ## Project
2110
- Working directory: ${this.projectPath}
2111
- Current session: ${this.session.getSessionId().substring(0, 8)}
2112
- ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2113
- ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}
2114
-
2115
- Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2301
+ this.promptBuilder.session = this.session;
2302
+ this.promptBuilder.ai = this.ai;
2303
+ this.promptBuilder.tools = this.tools;
2304
+ this.promptBuilder.sessionPermissionGrants = this.sessionPermissionGrants;
2305
+ return this.promptBuilder.buildSystemPrompt(context, {
2306
+ projectContextBudget: this.shouldUseCompactPrompt() ? 2200 : 3200,
2307
+ });
2116
2308
  }
2117
2309
 
2118
2310
  getFastSystemPrompt() {
2119
- const memories = this.session.getMemory();
2120
- const memoryStr = memories.length > 0
2121
- ? `\nContext nhớ ngắn:\n${this.summarizePromptList(memories.slice(-8), {
2122
- limit: 8,
2123
- maxEntryChars: 160,
2124
- maxTotalChars: 1200,
2125
- mapper: memory => memory.text,
2126
- })}`
2127
- : '';
2128
-
2129
- return `Bạn là Winter, trợ lý AI trả lời ngắn gọn bằng tiếng Việt.
2130
- Ưu tiên dùng tool và context khi cần; không bịa thông tin.
2131
- 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}`;
2311
+ this.promptBuilder.session = this.session;
2312
+ return this.promptBuilder.buildFastSystemPrompt();
2132
2313
  }
2133
2314
 
2134
2315
  // Tab completion
@@ -2148,73 +2329,10 @@ Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2148
2329
  }
2149
2330
 
2150
2331
  getAgentSystemPrompt(role, context = '') {
2151
- const memories = this.session.getMemory();
2152
- const plans = this.session.getPlans();
2153
- const sessionContext = this.session.getContext() || {};
2154
-
2155
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2156
- limit: 8,
2157
- maxEntryChars: 220,
2158
- maxTotalChars: 1600,
2159
- mapper: memory => memory.text,
2160
- })}` : '';
2161
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2162
- limit: 6,
2163
- maxEntryChars: 260,
2164
- maxTotalChars: 1600,
2165
- mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2166
- })}` : '';
2167
- let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2168
- ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2169
- : '';
2170
- let startupPlanStr = sessionContext.bootstrapPlan?.title
2171
- ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2172
- : '';
2173
-
2174
- let rolePrompt = '';
2175
- switch (role) {
2176
- case 'plan':
2177
- rolePrompt = `You are a Winter planning subagent. Break the request into a concise step-by-step plan, note dependencies, and keep the response short.`;
2178
- break;
2179
- case 'review':
2180
- rolePrompt = `You are a Winter review subagent. Critique the request or implementation with specific issues, edge cases, and concrete improvements.`;
2181
- break;
2182
- case 'debug':
2183
- rolePrompt = `You are a Winter debugging subagent. Focus on root cause, reproduction, and the smallest fix.`;
2184
- break;
2185
- case 'research':
2186
- rolePrompt = `You are a Winter research subagent. Gather the important facts, compare options, and summarize only what matters.`;
2187
- break;
2188
- case 'browser':
2189
- 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.`;
2190
- break;
2191
- default:
2192
- rolePrompt = `You are a Winter coding subagent. Solve the task directly, use tools when needed, and return a concise result.`;
2193
- break;
2194
- }
2195
-
2196
- return `## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2197
- 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.
2198
- 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.
2199
- 3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2200
- 4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
2201
- 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.
2202
-
2203
- ${rolePrompt}
2204
-
2205
- ## Tool Rules
2206
- - Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2207
- - Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.
2208
- - 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}.
2209
- - 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.
2210
- - If a tool call fails because of an unknown alias, call the canonical tool name next.
2211
- - Always start with a brief plan, then refine it when new facts appear.
2212
-
2213
- ## Project
2214
- Working directory: ${this.projectPath}
2215
- Current session: ${this.session.getSessionId().substring(0, 8)}
2216
- ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2217
- ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}`;
2332
+ this.promptBuilder.session = this.session;
2333
+ this.promptBuilder.ai = this.ai;
2334
+ this.promptBuilder.tools = this.tools;
2335
+ return this.promptBuilder.buildAgentSystemPrompt(role, context);
2218
2336
  }
2219
2337
 
2220
2338
  async handleMcpCommand(args) {