winter-super-cli 2026.5.24 → 2026.5.26

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.
Files changed (63) hide show
  1. package/README.md +1 -1
  2. package/WINTER.md +6 -0
  3. package/bin/winter.js +77 -220
  4. package/package.json +1 -1
  5. package/resources/local/manifest.json +60 -57
  6. package/src/ai/providers.js +64 -13
  7. package/src/ai/providers.test.js +35 -0
  8. package/src/cli/commands.js +61 -3
  9. package/src/cli/commands.test.js +179 -0
  10. package/src/cli/config.js +12 -0
  11. package/src/cli/repl.js +475 -150
  12. package/src/cli/repl.test.js +234 -2
  13. package/src/cli/snowflake-logo.js +15 -7
  14. package/src/cli/terminal-ui.js +125 -0
  15. package/src/cli/terminal-ui.test.js +33 -0
  16. package/src/plugins/manager.js +3 -1
  17. package/src/session/manager.js +44 -0
  18. package/src/session/manager.test.js +72 -0
  19. package/src/tools/executor.js +1 -1
  20. package/src/tools/executor.test.js +110 -0
  21. package/resources/local/claude/settings.json +0 -33
  22. package/resources/local/claude/todos/022bdc3c-e2c0-4a20-a74f-b348ed022c75-agent-022bdc3c-e2c0-4a20-a74f-b348ed022c75.json +0 -1
  23. package/resources/local/claude/todos/316f0e7d-5512-49fa-8c7f-edc75b777612-agent-316f0e7d-5512-49fa-8c7f-edc75b777612.json +0 -1
  24. package/resources/local/claude/todos/3676dc17-fca1-4692-934b-ce35e1965af6-agent-3676dc17-fca1-4692-934b-ce35e1965af6.json +0 -1
  25. package/resources/local/claude/todos/464493de-7f2a-45cf-93e8-ad73214afa10-agent-464493de-7f2a-45cf-93e8-ad73214afa10.json +0 -1
  26. package/resources/local/claude/todos/51f2e7a7-3f31-4692-a9b2-d3f3906aafea-agent-51f2e7a7-3f31-4692-a9b2-d3f3906aafea.json +0 -1
  27. package/resources/local/claude/todos/64a67dce-3d62-4a98-a548-b9c91a8e87e8-agent-64a67dce-3d62-4a98-a548-b9c91a8e87e8.json +0 -1
  28. package/resources/local/claude/todos/727a06e6-0ac2-41ca-8b81-2c14e4d40182-agent-727a06e6-0ac2-41ca-8b81-2c14e4d40182.json +0 -1
  29. package/resources/local/claude/todos/7d34d296-9b5a-4525-9b68-600d2ae20b59-agent-7d34d296-9b5a-4525-9b68-600d2ae20b59.json +0 -1
  30. package/resources/local/claude/todos/8c0606f1-5bcc-4176-8125-c5174fd69002-agent-8c0606f1-5bcc-4176-8125-c5174fd69002.json +0 -1
  31. package/resources/local/claude/todos/905aab16-5225-43f6-8ae4-c94491fd3a6f-agent-905aab16-5225-43f6-8ae4-c94491fd3a6f.json +0 -1
  32. package/resources/local/claude/todos/9dbe93f0-d62c-4c12-b4eb-0eecc437d625-agent-9dbe93f0-d62c-4c12-b4eb-0eecc437d625.json +0 -1
  33. package/resources/local/claude/todos/ad48500f-02a5-4f18-970b-82fb595d171f-agent-ad48500f-02a5-4f18-970b-82fb595d171f.json +0 -1
  34. package/resources/local/claude/todos/af86ea71-9907-4066-907c-68055e6c0081-agent-af86ea71-9907-4066-907c-68055e6c0081.json +0 -1
  35. package/resources/local/claude/todos/dbb0dc16-5d71-4f1d-a56c-db0741b3d485-agent-dbb0dc16-5d71-4f1d-a56c-db0741b3d485.json +0 -1
  36. package/resources/local/claude/todos/ff1ac487-eb0f-4c63-9360-fbb0a81bb5ae-agent-ff1ac487-eb0f-4c63-9360-fbb0a81bb5ae.json +0 -1
  37. package/resources/local/codex/config.toml +0 -84
  38. package/resources/local/codex/memories/MEMORY.md +0 -972
  39. package/resources/local/codex/memories/extensions/ad_hoc/instructions.md +0 -13
  40. package/resources/local/codex/memories/memory_summary.md +0 -188
  41. package/resources/local/codex/memories/raw_memories.md +0 -1488
  42. package/resources/local/codex/memories/rollout_summaries/2026-03-27T04-05-14-Iirb-nsis_full_installer_build_cpp_ocr_translator.md +0 -46
  43. package/resources/local/codex/memories/rollout_summaries/2026-03-28T06-18-17-Si3U-my_translator_overlay_lockfix_portable_nsis.md +0 -112
  44. package/resources/local/codex/memories/rollout_summaries/2026-04-15T06-42-11-2JMi-qelasy_timeout_and_watch_control_stability.md +0 -90
  45. package/resources/local/codex/memories/rollout_summaries/2026-04-16T03-12-59-z6Wi-request_all_row_click_detail_navigation.md +0 -42
  46. package/resources/local/codex/memories/rollout_summaries/2026-04-17T05-49-03-tNBk-my_translator_project_readability_audio_latency_clear_button.md +0 -75
  47. package/resources/local/codex/memories/rollout_summaries/2026-04-21T04-05-04-EXnh-nsis_packaging_harfbuzz_dll_qml_runtime_debug.md +0 -108
  48. package/resources/local/codex/memories/rollout_summaries/2026-04-22T03-48-40-VnNG-openclaw_opencode_sync_and_runtime_repair.md +0 -86
  49. package/resources/local/codex/memories/rollout_summaries/2026-04-22T06-49-49-R8yZ-web_book_user_portal_and_lint_fixes.md +0 -82
  50. package/resources/local/codex/memories/rollout_summaries/2026-04-22T06-50-35-ZaS1-smoke_admin_rbac_refund_connection_refused.md +0 -35
  51. package/resources/local/codex/memories/rollout_summaries/2026-04-22T11-05-04-aotT-nextjs_build_fix_statswidget_leaflet_ssr.md +0 -78
  52. package/resources/local/codex/memories/rollout_summaries/2026-04-23T03-22-24-a5q4-ui_still_looks_cloudflare_only.md +0 -41
  53. package/resources/local/codex/memories/rollout_summaries/2026-04-23T04-35-47-amlb-bayre247_hero_slide_above_search_form.md +0 -49
  54. package/resources/local/codex/memories/rollout_summaries/2026-04-23T04-59-21-lZWv-ocr_backend_parity_easyocr_tesseract_paddle_fallback.md +0 -92
  55. package/resources/local/codex/memories/rollout_summaries/2026-04-23T07-36-22-tPuo-request_workflow_editor_drag_edge_smaller_arrows_roadmap.md +0 -72
  56. package/resources/local/codex/memories/rollout_summaries/2026-04-24T08-01-05-Gb3B-checkin_shifts_workdays_assignments_and_checkout_overhaul.md +0 -90
  57. package/resources/local/codex/memories/rollout_summaries/2026-04-25T03-39-02-mbDr-web_book_refund_admin_popup_pagination_responsiveness.md +0 -151
  58. package/resources/local/codex/memories/rollout_summaries/2026-04-25T09-20-30-4usS-tool_scv_9router_custom_provider_and_paddle_ocr.md +0 -130
  59. package/resources/local/codex/memories/rollout_summaries/2026-05-06T10-19-38-mt2X-find_db_config_in_web_book_app_env.md +0 -40
  60. package/resources/local/codex/memories/rollout_summaries/2026-05-06T11-10-23-TkwP-goirong_backend_title_crash_and_client_audio_tcp_tunnel_debu.md +0 -85
  61. package/resources/local/codex/memories/rollout_summaries/2026-05-09T07-52-18-On1F-chakra_git_cleanup_readme_bilingual_publish_config.md +0 -88
  62. package/resources/local/codex/memories/rollout_summaries/2026-05-11T08-05-34-oMEl-check_crack_gui_logo_onefile_build.md +0 -68
  63. package/resources/local/codex/memories/skills/windows-packaged-app-smoke-check/SKILL.md +0 -72
package/src/cli/repl.js CHANGED
@@ -7,6 +7,7 @@ import readline from 'readline';
7
7
  import { promises as fs } from 'fs';
8
8
  import { homedir } from 'os';
9
9
  import { welcomeBanner, colors } from './snowflake-logo.js';
10
+ import { renderBox, terminalWidth, stripAnsi, visibleWidth, wrapText, padVisible } from './terminal-ui.js';
10
11
  import { ToolExecutor } from '../tools/executor.js';
11
12
  import { SessionManager } from '../session/manager.js';
12
13
  import { AIProviderManager } from '../ai/providers.js';
@@ -84,12 +85,16 @@ function formatMarkdown(text) {
84
85
  // Fallback
85
86
  }
86
87
 
87
- const columns = process.stdout.columns || 80;
88
- const W = Math.max(60, Math.min(Math.floor(columns * 0.95), 100));
89
- const headerLine = '─'.repeat(Math.max(0, W - label.length - 4));
90
- const bottomLine = '─'.repeat(W);
88
+ const boxWidth = Math.max(60, Math.min(terminalWidth(60, 100, 80), 100));
89
+ const body = wrapText(coloredCode, boxWidth - 4);
91
90
 
92
- return `\n${colors.dim}┌─ ${label} ${headerLine}${colors.reset}\n${coloredCode}\n${colors.dim}└${bottomLine}${colors.reset}\n${colors.white}`;
91
+ return `\n${renderBox({
92
+ title: label,
93
+ width: boxWidth,
94
+ borderColor: colors.dim,
95
+ titleColor: colors.dim,
96
+ body,
97
+ })}\n${colors.white}`;
93
98
  });
94
99
 
95
100
  // Bold
@@ -164,51 +169,106 @@ export class WinterREPL {
164
169
  this.isCancelled = false;
165
170
  }
166
171
 
172
+ getProjectInstructionFiles() {
173
+ return ['winter.md', 'WINTER.md', 'CLAUDE.md', '.claude/CLAUDE.md'];
174
+ }
175
+
176
+ async readProjectInstructionFiles() {
177
+ const fsPromises = await import('fs/promises');
178
+ const files = [];
179
+ const seen = new Set();
180
+
181
+ for (const relativePath of this.getProjectInstructionFiles()) {
182
+ const filePath = path.join(this.projectPath, relativePath);
183
+ const normalizedPath = path.normalize(filePath).toLowerCase();
184
+
185
+ if (seen.has(normalizedPath)) continue;
186
+ seen.add(normalizedPath);
187
+
188
+ try {
189
+ const stat = await fsPromises.stat(filePath).catch(() => null);
190
+ if (!stat || !stat.isFile()) continue;
191
+
192
+ const content = await fsPromises.readFile(filePath, 'utf8');
193
+ files.push({ relativePath, filePath, content });
194
+ } catch {
195
+ // Ignore unreadable instruction files.
196
+ }
197
+ }
198
+
199
+ return files;
200
+ }
201
+
167
202
  async start() {
168
203
  await this.session.init({ project: this.projectPath, sessionId: this.sessionId });
169
204
  await this.ai.init();
170
205
 
171
- // Tự động đọc và ghi nhớ 3 file hệ thống theo yêu cầu của user
172
- const filesToAutoLoad = [
173
- 'e:\\\\dev\\\\app\\\\winter\\\\resources\\\\local\\\\agents.md',
174
- 'e:\\\\dev\\\\app\\\\winter\\\\resources\\\\local\\\\awesome-design-md',
175
- 'e:\\\\dev\\\\app\\\\winter\\\\resources\\\\local\\\\karpathy-tools'
176
- ];
206
+ await this.session.updateContext('projectAnchor', {
207
+ path: this.projectPath,
208
+ name: path.basename(this.projectPath),
209
+ openedAt: new Date().toISOString(),
210
+ });
211
+ await this.session.replaceMemory('[Project Anchor]', `Current project is ${this.projectPath}. Treat this path as the canonical working directory for the session.`, 'info');
177
212
 
213
+ // Tự động đọc và ghi nhớ một số tài nguyên cục bộ (an toàn): chỉ nạp file hoặc README trong thư mục
178
214
  const fsPromises = await import('fs/promises');
179
- const path = await import('path');
215
+ const resourcePaths = this.getResourcePaths();
216
+ const autoLoadTargets = [resourcePaths.agents, resourcePaths.designs, resourcePaths.karpathy];
180
217
 
181
- for (const filePath of filesToAutoLoad) {
218
+ for (const targetPath of autoLoadTargets) {
182
219
  try {
183
- const content = await fsPromises.readFile(filePath, 'utf8');
184
- const fileName = path.basename(filePath);
185
- const memoryKey = `[Tự động ghi nhớ file ${fileName}]`;
220
+ const stat = await fsPromises.stat(targetPath).catch(() => null);
221
+ if (!stat) continue;
186
222
 
187
- // Xóa memory cũ về file này để cập nhật nội dung mới nhất (tránh trùng lặp)
188
- const currentMemories = this.session.getMemory() || [];
189
- const filteredMemories = currentMemories.filter(m => !m.startsWith(memoryKey));
223
+ if (stat.isFile()) {
224
+ const content = await fsPromises.readFile(targetPath, 'utf8');
225
+ const fileName = path.basename(targetPath);
226
+ const memoryKey = `[Tự động ghi nhớ file ${fileName}]`;
227
+ await this.session.replaceMemory(memoryKey, content);
228
+ console.log(`${colors.dim}ℹ Đã tự động nạp và ghi nhớ file ${fileName}${colors.reset}`);
229
+ continue;
230
+ }
190
231
 
191
- // Cập nhật lại mảng memory trong session
192
- this.session.memory = filteredMemories;
232
+ if (stat.isDirectory()) {
233
+ // Try README.md, index.md, or manifest.json inside the directory
234
+ const candidates = ['README.md', 'README.MD', 'index.md', 'manifest.json'];
235
+ let loaded = false;
236
+ for (const c of candidates) {
237
+ const p = path.join(targetPath, c);
238
+ try {
239
+ const cstat = await fsPromises.stat(p).catch(() => null);
240
+ if (cstat && cstat.isFile()) {
241
+ const content = await fsPromises.readFile(p, 'utf8');
242
+ const memoryKey = `[Tự động ghi nhớ file ${path.basename(targetPath)}/${c}]`;
243
+ await this.session.replaceMemory(memoryKey, content);
244
+ console.log(`${colors.dim}ℹ Đã tự động nạp và ghi nhớ ${path.basename(targetPath)}/${c}${colors.reset}`);
245
+ loaded = true;
246
+ break;
247
+ }
248
+ } catch (e) {
249
+ // continue
250
+ }
251
+ }
193
252
 
194
- // Thêm nội dung mới vào bộ nhớ
195
- this.session.addMemory(`${memoryKey}:\n${content}`);
196
- console.log(`${colors.dim}ℹ Đã tự động nạp và ghi nhớ file ${fileName}${colors.reset}`);
253
+ if (!loaded) {
254
+ // nothing to load
255
+ }
256
+ }
197
257
  } catch (e) {
198
- // Bỏ qua nếu không đọc được file
258
+ // Ignore read errors for resources
199
259
  }
200
260
  }
201
261
 
202
- // Kiểm tra và nạp file winter.md của dự án
203
- const projectWinterMd = path.join(this.projectPath, 'winter.md');
262
+ // Nạp các file quy tắc dự án theo thứ tự ưu tiên.
263
+ const projectInstructionFiles = await this.readProjectInstructionFiles();
204
264
  try {
205
- await fsPromises.access(projectWinterMd);
206
- const content = await fsPromises.readFile(projectWinterMd, 'utf8');
207
-
208
- const memoryKey = `[Quy tắc dự án từ winter.md]`;
209
- this.session.memory = (this.session.getMemory() || []).filter(m => !m.startsWith(memoryKey));
210
- this.session.addMemory(`${memoryKey}:\n${content}`);
211
- console.log(`${colors.dim}ℹ Đã nạp quy tắc dự án từ winter.md${colors.reset}`);
265
+ if (projectInstructionFiles.length > 0) {
266
+ for (const file of projectInstructionFiles) {
267
+ const memoryKey = `[Quy tắc dự án từ ${file.relativePath}]`;
268
+ await this.session.replaceMemory(memoryKey, file.content);
269
+ console.log(`${colors.dim}ℹ Đã nạp quy tắc dự án từ ${file.relativePath}${colors.reset}`);
270
+ }
271
+ }
212
272
  } catch (e) {
213
273
  // Nếu không có, tự động tạo file mẫu!
214
274
  const template = `# Winter Project Rules
@@ -243,17 +303,20 @@ export class WinterREPL {
243
303
  - Luôn đảm bảo file không bị lỗi cú pháp sau khi sửa.
244
304
  `;
245
305
  try {
306
+ const projectWinterMd = path.join(this.projectPath, 'winter.md');
246
307
  await fsPromises.writeFile(projectWinterMd, template, 'utf8');
247
308
  console.log(`\n${colors.green}✓ Đã tự động tạo file winter.md mẫu cho dự án mới!${colors.reset}`);
248
309
  console.log(`${colors.dim}Bạn có thể chỉnh sửa file này để dạy AI các quy tắc riêng của dự án.${colors.reset}\n`);
249
310
 
250
311
  // Nạp luôn vào memory
251
- this.session.addMemory(`[Quy tắc dự án từ winter.md]:\n${template}`);
312
+ await this.session.replaceMemory(`[Quy tắc dự án từ winter.md]`, template);
252
313
  } catch (err) {
253
314
  // Bỏ qua nếu không ghi được file
254
315
  }
255
316
  }
256
317
 
318
+ await this.bootstrapProjectCapabilities();
319
+
257
320
  const activeProvider = this.ai.getActiveProvider();
258
321
  const info = {
259
322
  project: this.projectPath,
@@ -1031,12 +1094,18 @@ export class WinterREPL {
1031
1094
  // Provider commands
1032
1095
  case '/provider':
1033
1096
  if (args[0]) {
1034
- const providerName = args[0];
1035
- if (this.ai.setProvider(providerName)) {
1036
- await this.config.setDefaultProvider(providerName);
1037
- console.log(`${colors.green}✓ Provider: ${providerName}${colors.reset}`);
1097
+ const providerName = args[0].trim().toLowerCase();
1098
+ const switched = typeof this.ai.switchProvider === 'function'
1099
+ ? await this.ai.switchProvider(providerName)
1100
+ : (this.ai.setProvider(providerName) ? providerName : null);
1101
+
1102
+ if (switched) {
1103
+ await this.config.setDefaultProvider(switched);
1104
+ console.log(`${colors.green}✓ Provider: ${switched}${colors.reset}`);
1038
1105
  } else {
1106
+ const available = this.ai.listProviders().map(p => p.name).join(', ') || 'none';
1039
1107
  console.log(`${colors.red}Unknown provider: ${providerName}${colors.reset}`);
1108
+ console.log(`${colors.dim}Available providers: ${available}${colors.reset}`);
1040
1109
  }
1041
1110
  } else {
1042
1111
  console.log(`${colors.cyan}Provider: ${this.ai.getActiveProvider()}${colors.reset}`);
@@ -1109,48 +1178,50 @@ export class WinterREPL {
1109
1178
 
1110
1179
  showCommandMenu() {
1111
1180
  const c = colors;
1112
- const W = 78;
1113
- const line = '─'.repeat(W - 2);
1114
- const dline = '═'.repeat(W - 2);
1115
- const row = (l, r) => {
1116
- const ll = l.replace(/\x1b\[[0-9;]*m/g, '');
1117
- const rl = r.replace(/\x1b\[[0-9;]*m/g, '');
1118
- const pad = W - 4 - ll.length - rl.length;
1119
- return `${c.magenta}│${c.reset} ${l}${' '.repeat(Math.max(1, pad))}${r} ${c.magenta}│${c.reset}`;
1120
- };
1121
- const header = (text) => {
1122
- const tl = text.replace(/\x1b\[[0-9;]*m/g, '').length;
1123
- const pad = W - 4 - tl;
1124
- return `${c.magenta}│${c.reset} ${text}${' '.repeat(Math.max(0, pad))} ${c.magenta}│${c.reset}`;
1181
+ const width = terminalWidth(72, 112, 92);
1182
+ const innerWidth = width - 4;
1183
+ const split = Math.floor(innerWidth * 0.54);
1184
+ const rightWidth = innerWidth - split - 1;
1185
+ const row = (left, right = '') => {
1186
+ if (!right) return left;
1187
+ return `${padVisible(left, split)} ${padVisible(right, rightWidth)}`;
1125
1188
  };
1126
- const sep = `${c.magenta}├${line}┤${c.reset}`;
1189
+
1190
+ const body = [
1191
+ `${c.bright}${c.cyan}❄ WINTER COMMANDS${c.reset}`,
1192
+ '',
1193
+ `${c.bright}Dự án & Phiên làm việc${c.reset}`,
1194
+ row(`${c.yellow}/pwd${c.reset} Thư mục hiện tại`, `${c.yellow}/session${c.reset} Phiên làm việc`),
1195
+ row(`${c.yellow}/cd${c.reset} Đổi thư mục`, `${c.yellow}/clear${c.reset} Xóa màn hình`),
1196
+ row(`${c.yellow}/config${c.reset} Xem cấu hình`, `${c.yellow}/exit${c.reset} Thoát`),
1197
+ '',
1198
+ `${c.bright}AI & Công cụ${c.reset}`,
1199
+ row(`${c.yellow}/auto${c.reset} TDD tự sửa lỗi`, `${c.yellow}/agent${c.reset} Chạy sub-agent`),
1200
+ row(`${c.yellow}/read${c.reset} Đọc file`, `${c.yellow}/write${c.reset} Ghi file`),
1201
+ row(`${c.yellow}/bash${c.reset} Chạy lệnh terminal`, `${c.yellow}/grep${c.reset} Tìm trong file`),
1202
+ row(`${c.yellow}/glob${c.reset} Tìm file theo pattern`, `${c.yellow}/image${c.reset} Phân tích UI`),
1203
+ row(`${c.yellow}/paste${c.reset} Dán từ clipboard`, `${c.yellow}/plan${c.reset} Lập kế hoạch`),
1204
+ '',
1205
+ `${c.bright}Git Auto-Pilot${c.reset}`,
1206
+ row(`${c.yellow}/commit${c.reset} AI tự viết commit`, `${c.yellow}/review${c.reset} AI review code thay đổi`),
1207
+ '',
1208
+ `${c.bright}Cấu hình Model${c.reset}`,
1209
+ row(`${c.yellow}/provider${c.reset} Đổi provider AI`, `${c.yellow}/model${c.reset} Đổi model`),
1210
+ row(`${c.yellow}/providers${c.reset} Danh sách provider`, `${c.yellow}/models${c.reset} Danh sách model`),
1211
+ '',
1212
+ `${c.bright}Bộ nhớ & Kỹ năng${c.reset}`,
1213
+ row(`${c.yellow}/remember${c.reset} Lưu vào bộ nhớ`, `${c.yellow}/memories${c.reset} Xem bộ nhớ`),
1214
+ row(`${c.yellow}/skills${c.reset} Danh sách kỹ năng`, `${c.yellow}/designs${c.reset} Hệ thống thiết kế`),
1215
+ ];
1216
+
1127
1217
  console.log(`
1128
- ${c.magenta}╭${line}╮${c.reset}
1129
- ${header(`${c.bright}${c.cyan}WINTER COMMANDS${c.reset}`)}
1130
- ${sep}
1131
- ${header(`${c.bright}Dự án & Phiên làm việc${c.reset}`)}
1132
- ${row(`${c.yellow}/pwd${c.reset} Thư mục hiện tại`, `${c.yellow}/session${c.reset} Phiên làm việc`)}
1133
- ${row(`${c.yellow}/cd${c.reset} Đổi thư mục`, `${c.yellow}/clear${c.reset} Xóa màn hình`)}
1134
- ${row(`${c.yellow}/config${c.reset} Xem cấu hình`, `${c.yellow}/exit${c.reset} Thoát`)}
1135
- ${sep}
1136
- ${header(`${c.bright}AI & Công cụ${c.reset}`)}
1137
- ${row(`${c.yellow}/auto${c.reset} TDD tự sửa lỗi`, `${c.yellow}/agent${c.reset} Chạy sub-agent`)}
1138
- ${row(`${c.yellow}/read${c.reset} Đọc file`, `${c.yellow}/write${c.reset} Ghi file`)}
1139
- ${row(`${c.yellow}/bash${c.reset} Chạy lệnh terminal`, `${c.yellow}/grep${c.reset} Tìm trong file`)}
1140
- ${row(`${c.yellow}/glob${c.reset} Tìm file theo pattern`, `${c.yellow}/image${c.reset} Phân tích UI`)}
1141
- ${row(`${c.yellow}/paste${c.reset} Dán từ clipboard`, `${c.yellow}/plan${c.reset} Lập kế hoạch`)}
1142
- ${sep}
1143
- ${header(`${c.bright}Git Auto-Pilot${c.reset}`)}
1144
- ${row(`${c.yellow}/commit${c.reset} AI tự viết commit`, `${c.yellow}/review${c.reset} AI review code thay đổi`)}
1145
- ${sep}
1146
- ${header(`${c.bright}Cấu hình Model${c.reset}`)}
1147
- ${row(`${c.yellow}/provider${c.reset} Đổi provider AI`, `${c.yellow}/model${c.reset} Đổi model`)}
1148
- ${row(`${c.yellow}/providers${c.reset} Danh sách provider`, `${c.yellow}/models${c.reset} Danh sách model`)}
1149
- ${sep}
1150
- ${header(`${c.bright}Bộ nhớ & Kỹ năng${c.reset}`)}
1151
- ${row(`${c.yellow}/remember${c.reset} Lưu vào bộ nhớ`, `${c.yellow}/memories${c.reset} Xem bộ nhớ`)}
1152
- ${row(`${c.yellow}/skills${c.reset} Danh sách kỹ năng`, `${c.yellow}/designs${c.reset} Hệ thống thiết kế`)}
1153
- ${c.magenta}╰${line}╯${c.reset}
1218
+ ${renderBox({
1219
+ title: `${c.bright}${c.cyan}WINTER COMMANDS${c.reset}`,
1220
+ width,
1221
+ borderColor: c.magenta,
1222
+ titleColor: c.cyan,
1223
+ body,
1224
+ })}
1154
1225
  ${c.dim}Gõ tin nhắn trực tiếp để chat · ESC để hủy · Prompt tự xếp hàng chờ${c.reset}
1155
1226
  `);
1156
1227
  }
@@ -1358,13 +1429,15 @@ ${colors.reset}
1358
1429
  usedTools = true;
1359
1430
  if (this.spinner) this.spinner.stop();
1360
1431
 
1361
- const BOX_WIDTH = 80;
1362
- const topLabel = ' AGENT TOOLS EXECUTION ';
1363
- const topLeft = '╭─';
1364
- const topRight = '─╮';
1365
- const topPadLen = BOX_WIDTH - topLeft.length - topRight.length - topLabel.length;
1366
- const topLine = `${topLeft}${colors.bright}${topLabel}${colors.reset}${colors.magenta}${'─'.repeat(Math.max(0, topPadLen))}${topRight}`;
1367
- console.log(`\n${colors.magenta}${topLine}${colors.reset}`);
1432
+ const BOX_WIDTH = terminalWidth(76, 116, 92);
1433
+ const innerWidth = BOX_WIDTH - 2;
1434
+ const title = ' AGENT TOOLS EXECUTION ';
1435
+ const titlePad = Math.max(0, innerWidth - visibleWidth(title));
1436
+ const leftPad = Math.floor(titlePad / 2);
1437
+ const rightPad = titlePad - leftPad;
1438
+ console.log(`\n${colors.magenta}╭${'─'.repeat(innerWidth)}╮${colors.reset}`);
1439
+ console.log(`${colors.magenta}│${colors.reset}${' '.repeat(leftPad)}${colors.bright}${title}${colors.reset}${' '.repeat(rightPad)}${colors.magenta}│${colors.reset}`);
1440
+ console.log(`${colors.magenta}├${'─'.repeat(innerWidth)}┤${colors.reset}`);
1368
1441
  messages.push({
1369
1442
  role: 'assistant',
1370
1443
  content: assistantMsg.content || '',
@@ -1420,34 +1493,16 @@ ${colors.reset}
1420
1493
  const statusIcon = result.success === false ? `${colors.red}✖${colors.reset}` : `${colors.green}✓${colors.reset}`;
1421
1494
 
1422
1495
  const maxLen = BOX_WIDTH - 8;
1423
- const lines = summary.split('\n');
1424
- for (const line of lines) {
1425
- // Loại bỏ màu ANSI để tính toán độ dài và cắt dòng chuẩn xác
1426
- const cleanLine = line.replace(/\x1b\[[0-9;]*m/g, '');
1427
-
1428
- if (cleanLine.length <= maxLen) {
1429
- const padLen = BOX_WIDTH - 7 - cleanLine.length;
1430
- const padding = ' '.repeat(Math.max(0, padLen));
1431
- console.log(`${colors.magenta}│${colors.reset} ${statusIcon} ${colors.dim}${cleanLine}${colors.reset}${padding}${colors.magenta}│${colors.reset}`);
1432
- } else {
1433
- // Word wrap
1434
- let remaining = cleanLine;
1435
- let first = true;
1436
- while (remaining.length > 0) {
1437
- const chunk = remaining.substring(0, maxLen);
1438
- remaining = remaining.substring(maxLen);
1439
- const prefix = first ? statusIcon : ' ';
1440
-
1441
- const padLen = BOX_WIDTH - 7 - chunk.length;
1442
- const padding = ' '.repeat(Math.max(0, padLen));
1443
- console.log(`${colors.magenta}│${colors.reset} ${prefix} ${colors.dim}${chunk}${colors.reset}${padding}${colors.magenta}│${colors.reset}`);
1444
- first = false;
1445
- }
1446
- }
1496
+ const wrappedLines = summary.split('\n').flatMap(line => wrapText(line, maxLen));
1497
+ for (const [index, line] of wrappedLines.entries()) {
1498
+ const prefix = index === 0 ? statusIcon : ' ';
1499
+ const cleanLine = stripAnsi(line);
1500
+ const padding = ' '.repeat(Math.max(0, maxLen - visibleWidth(cleanLine)));
1501
+ console.log(`${colors.magenta}│${colors.reset} ${prefix} ${colors.dim}${cleanLine}${colors.reset}${padding}${colors.magenta}│${colors.reset}`);
1447
1502
  }
1448
1503
  }
1449
1504
  }
1450
- console.log(`${colors.magenta}╰${'─'.repeat(BOX_WIDTH - 1)}╯${colors.reset}\n`);
1505
+ console.log(`${colors.magenta}╰${'─'.repeat(innerWidth)}╯${colors.reset}\n`);
1451
1506
  }
1452
1507
 
1453
1508
  if (usedTools && !finalContent) {
@@ -2079,16 +2134,16 @@ ${colors.reset}
2079
2134
 
2080
2135
  async chat(message, imageAttachments = []) {
2081
2136
  try {
2082
- const needsTools = this.shouldUseTools(message, imageAttachments);
2083
- const context = needsTools ? await this.getProjectContext() : '';
2137
+ const needsTools = true;
2138
+ const context = await this.getProjectContext();
2084
2139
  const messages = [
2085
- { role: 'system', content: needsTools ? this.getSystemPrompt(context) : this.getFastSystemPrompt() }
2140
+ { role: 'system', content: this.getSystemPrompt(context) }
2086
2141
  ];
2087
2142
 
2088
2143
  const history = this.getPromptHistory({
2089
- limit: needsTools ? 20 : 4,
2090
- maxEntryChars: needsTools ? 2000 : 350,
2091
- maxTotalChars: needsTools ? 12000 : 900,
2144
+ limit: 20,
2145
+ maxEntryChars: 2000,
2146
+ maxTotalChars: 12000,
2092
2147
  });
2093
2148
  for (const entry of history) {
2094
2149
  messages.push({ role: entry.role, content: entry.content });
@@ -2109,7 +2164,7 @@ ${colors.reset}
2109
2164
  messages.push({ role: 'user', content: message });
2110
2165
  }
2111
2166
 
2112
- const tools = needsTools ? this.getAgentTools('general') : [];
2167
+ const tools = this.getAgentTools('general');
2113
2168
  const finalContent = await this.runConversation(messages, 'Thinking', tools);
2114
2169
 
2115
2170
  await this.session.addToHistory({ role: 'user', content: message });
@@ -2121,12 +2176,7 @@ ${colors.reset}
2121
2176
  }
2122
2177
 
2123
2178
  shouldUseTools(message = '', imageAttachments = []) {
2124
- if (imageAttachments.length > 0) return false;
2125
- const text = String(message || '').toLowerCase();
2126
- if (/[a-z]:[\\/]|\.([cm]?[jt]sx?|json|md|css|html|py|java|go|rs|php|rb|toml|ya?ml)\b/i.test(text)) {
2127
- return true;
2128
- }
2129
- return /\b(read|write|edit|file|folder|repo|project|code|bug|fix|debug|test|build|run|git|commit|push|pull|npm|node|install|create|delete|copy|move|refactor|grep|glob|bash|terminal|powershell|deploy)\b|sửa|lỗi|đọc|thư mục|dự án|mã|kiểm tra|chạy|tạo|xóa|giao diện|ảnh|màn hình|đẩy|cài|build|test/i.test(text);
2179
+ return true;
2130
2180
  }
2131
2181
 
2132
2182
  getPromptHistory({ limit = 20, maxEntryChars = 2000, maxTotalChars = 12000 } = {}) {
@@ -2171,19 +2221,29 @@ ${colors.reset}
2171
2221
 
2172
2222
  async getProjectContext() {
2173
2223
  const context = [];
2174
- const projectFiles = ['CLAUDE.md', 'WINTER.md', '.claude/CLAUDE.md', 'package.json'];
2224
+ const projectInstructionFiles = await this.readProjectInstructionFiles();
2175
2225
 
2176
- for (const file of projectFiles) {
2226
+ for (const file of projectInstructionFiles) {
2177
2227
  try {
2178
- const filePath = path.join(this.projectPath, file);
2179
- const stat = await fs.stat(filePath);
2180
- if (stat.isFile()) {
2181
- const content = await fs.readFile(filePath, 'utf-8');
2182
- context.push(`[${file}]\n${content.substring(0, 300)}...`);
2183
- }
2228
+ const preview = this.compactText(file.content, 900, 'project instruction');
2229
+ context.push(`[${file.relativePath}]\n${preview}`);
2184
2230
  } catch { }
2185
2231
  }
2186
2232
 
2233
+ try {
2234
+ const packageJsonPath = path.join(this.projectPath, 'package.json');
2235
+ const stat = await fs.stat(packageJsonPath);
2236
+ if (stat.isFile()) {
2237
+ const content = await fs.readFile(packageJsonPath, 'utf-8');
2238
+ context.push(`[package.json]\n${this.compactText(content, 1200, 'package.json')}`);
2239
+ }
2240
+ } catch { }
2241
+
2242
+ const localResources = await this.getLocalResourceContext();
2243
+ if (localResources) {
2244
+ context.push(localResources);
2245
+ }
2246
+
2187
2247
  // Git Context
2188
2248
  try {
2189
2249
  const { execSync } = await import('child_process');
@@ -2191,35 +2251,240 @@ ${colors.reset}
2191
2251
  if (gitStatus) {
2192
2252
  context.push(`[Git Status]\n${gitStatus}`);
2193
2253
 
2254
+ const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
2255
+ if (gitSummary) {
2256
+ context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
2257
+ }
2258
+
2194
2259
  // Get brief git diff for context
2195
- const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 50).join('\n');
2260
+ const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
2196
2261
  if (gitDiff) {
2197
- context.push(`[Git Diff]\n${gitDiff}\n...`);
2262
+ context.push(`[Git Diff]\n${this.compactText(gitDiff, 1800, 'git diff')}`);
2198
2263
  }
2199
2264
  }
2200
2265
  } catch (e) {
2201
2266
  // Not a git repo or git not installed
2202
2267
  }
2203
2268
 
2204
- return context.join('\n\n') || 'No project context found.';
2269
+ return this.compactText(context.join('\n\n') || 'No project context found.', 9000, 'project context');
2270
+ }
2271
+
2272
+ async getLocalResourceContext() {
2273
+ try {
2274
+ const manifestPath = this.getResourcePaths().manifest;
2275
+ const raw = await fs.readFile(manifestPath, 'utf8');
2276
+ const manifest = JSON.parse(raw.replace(/^\uFEFF/, ''));
2277
+ const paths = this.getResourcePaths();
2278
+ const [claudeSkills, codexSkills, claudePlugins, codexMemories] = await Promise.all([
2279
+ this.listPathEntries(paths.claude.skills, 20),
2280
+ this.listPathEntries(paths.codex.skills, 20),
2281
+ this.listPathEntries(paths.claude.plugins, 20),
2282
+ this.listPathEntries(paths.codex.memories, 20),
2283
+ ]);
2284
+
2285
+ const lines = [];
2286
+ lines.push('[Local Resources]');
2287
+ lines.push(`- Root: ${manifest.root || this.getResourcePaths().localRoot}`);
2288
+
2289
+ for (const resource of manifest.localResources || []) {
2290
+ lines.push(`- ${resource.name}: ${resource.files} files, ${(resource.bytes / 1024 / 1024).toFixed(2)} MB`);
2291
+ }
2292
+
2293
+ if (manifest.redacted?.length) {
2294
+ lines.push(`- Redacted: ${manifest.redacted.join('; ')}`);
2295
+ }
2296
+
2297
+ lines.push('- Use Read/Grep/Glob to inspect any local resource when it matters for the task.');
2298
+ lines.push('- Local resource families: agents.md, awesome-design-md, claude, codex, karpathy-tools.');
2299
+
2300
+ if (claudeSkills.length > 0) {
2301
+ lines.push(`- Claude skills: ${claudeSkills.slice(0, 10).map(item => item.name).join(', ')}${claudeSkills.length > 10 ? ', ...' : ''}`);
2302
+ }
2303
+
2304
+ if (claudePlugins.length > 0) {
2305
+ lines.push(`- Claude plugin roots: ${claudePlugins.slice(0, 10).map(item => item.name).join(', ')}${claudePlugins.length > 10 ? ', ...' : ''}`);
2306
+ }
2307
+
2308
+ if (codexSkills.length > 0) {
2309
+ lines.push(`- Codex skills: ${codexSkills.slice(0, 10).map(item => item.name).join(', ')}${codexSkills.length > 10 ? ', ...' : ''}`);
2310
+ }
2311
+
2312
+ if (codexMemories.length > 0) {
2313
+ lines.push(`- Codex memories: ${codexMemories.slice(0, 10).map(item => item.name).join(', ')}${codexMemories.length > 10 ? ', ...' : ''}`);
2314
+ }
2315
+
2316
+ return lines.join('\n');
2317
+ } catch {
2318
+ return '';
2319
+ }
2320
+ }
2321
+
2322
+ async bootstrapProjectCapabilities() {
2323
+ const sessionContext = this.session.getContext() || {};
2324
+
2325
+ if (!sessionContext.bootstrapPlan?.id && this.session.getPlans().length === 0) {
2326
+ const plan = await this.session.createPlan(
2327
+ 'Bootstrap project context',
2328
+ 'Inspect rules, resources, and likely skills before doing any task work.'
2329
+ );
2330
+ await this.session.addPlanStep(plan.id, {
2331
+ description: 'Read project rules, local resources, and attached skill libraries.',
2332
+ });
2333
+ await this.session.addPlanStep(plan.id, {
2334
+ description: 'Choose the smallest relevant skill set before making changes.',
2335
+ });
2336
+ await this.session.updateContext('bootstrapPlan', {
2337
+ id: plan.id,
2338
+ title: plan.title,
2339
+ description: plan.description,
2340
+ });
2341
+ }
2342
+
2343
+ const skillSnapshot = await this.inferStartupSkills();
2344
+ await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
2345
+ await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
2346
+
2347
+ const appliedText = skillSnapshot.activeSkills.length > 0
2348
+ ? `Auto-applied skills: ${skillSnapshot.activeSkills.join(', ')}`
2349
+ : 'Auto-applied skills: none';
2350
+ await this.session.replaceMemory('[Auto-applied skills]', appliedText, 'skill');
2351
+ }
2352
+
2353
+ async inferStartupSkills() {
2354
+ const catalog = await this.getStartupSkillCatalog();
2355
+ const signals = await this.getProjectSignals();
2356
+ const normalizedSignals = new Set(signals.map(value => value.toLowerCase()));
2357
+
2358
+ const hasAny = (...items) => items.some(item => normalizedSignals.has(item));
2359
+ const activeSkills = new Set([
2360
+ 'coding',
2361
+ 'debug',
2362
+ 'refactor',
2363
+ 'test',
2364
+ ]);
2365
+
2366
+ if (hasAny('react', 'next', 'nextjs', 'tsx', 'jsx', 'vue', 'svelte', 'vite')) {
2367
+ ['vercel-react-best-practices', 'web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
2368
+ }
2369
+
2370
+ if (hasAny('design', 'ui', 'ux', 'css', 'tailwind', 'styled-components', 'scss', 'style', 'component')) {
2371
+ ['web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
2372
+ }
2373
+
2374
+ if (hasAny('claude', 'agent', 'mcp', 'plugin', 'skill', 'automation', 'workflow')) {
2375
+ ['skill-creator', 'claude-automation-recommender', 'claude-md-improver', 'agent-development', 'hook-development', 'command-development', 'plugin-dev'].forEach(skill => activeSkills.add(skill));
2376
+ }
2377
+
2378
+ if (hasAny('docs', 'markdown', 'md', 'readme', 'documentation')) {
2379
+ ['claude-md-improver', 'docs', 'writing-rules'].forEach(skill => activeSkills.add(skill));
2380
+ }
2381
+
2382
+ if (hasAny('figma', 'design-md', 'brand', 'brand-guidelines', 'style-guide')) {
2383
+ ['vibefigma', 'web-design-guidelines'].forEach(skill => activeSkills.add(skill));
2384
+ }
2385
+
2386
+ const filtered = [...activeSkills].filter(skill => catalog.has(skill));
2387
+ return {
2388
+ availableSkills: [...catalog],
2389
+ activeSkills: filtered,
2390
+ };
2391
+ }
2392
+
2393
+ async getStartupSkillCatalog() {
2394
+ const catalog = new Set(['coding', 'design', 'debug', 'refactor', 'test', 'security', 'performance']);
2395
+ const resourcePaths = this.getResourcePaths();
2396
+ const folders = [resourcePaths.claude.skills, resourcePaths.codex.skills];
2397
+
2398
+ for (const folder of folders) {
2399
+ const entries = await this.listPathEntries(folder, 200);
2400
+ for (const entry of entries) {
2401
+ catalog.add(entry.name);
2402
+ }
2403
+ }
2404
+
2405
+ return catalog;
2406
+ }
2407
+
2408
+ async getProjectSignals() {
2409
+ const signals = [];
2410
+
2411
+ try {
2412
+ const packageJsonPath = path.join(this.projectPath, 'package.json');
2413
+ const raw = await fs.readFile(packageJsonPath, 'utf8');
2414
+ const pkg = JSON.parse(raw);
2415
+
2416
+ signals.push(String(pkg.name || '').toLowerCase());
2417
+ signals.push(String(pkg.description || '').toLowerCase());
2418
+
2419
+ for (const key of ['dependencies', 'devDependencies', 'peerDependencies']) {
2420
+ const deps = pkg[key] || {};
2421
+ for (const depName of Object.keys(deps)) {
2422
+ signals.push(depName.toLowerCase());
2423
+ }
2424
+ }
2425
+
2426
+ for (const script of Object.values(pkg.scripts || {})) {
2427
+ signals.push(String(script).toLowerCase());
2428
+ }
2429
+ } catch {
2430
+ // Ignore package.json parsing issues.
2431
+ }
2432
+
2433
+ try {
2434
+ const entries = await fs.readdir(this.projectPath, { withFileTypes: true });
2435
+ for (const entry of entries) {
2436
+ if (!entry.isFile()) continue;
2437
+ signals.push(path.extname(entry.name).toLowerCase().slice(1));
2438
+ signals.push(entry.name.toLowerCase());
2439
+ }
2440
+ } catch {
2441
+ // Ignore directory scan issues.
2442
+ }
2443
+
2444
+ return signals.filter(Boolean);
2205
2445
  }
2206
2446
 
2207
2447
  getSystemPrompt(context = '') {
2208
2448
  const memories = this.session.getMemory();
2209
2449
  const plans = this.session.getPlans();
2210
-
2211
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${memories.map(m => `- ${m.text}`).join('\n')}` : '';
2212
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${plans.map(p => `- [${p.status}] ${p.title}: ${p.description}`).join('\n')}` : '';
2450
+ const sessionContext = this.session.getContext() || {};
2451
+
2452
+ let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2453
+ limit: 8,
2454
+ maxEntryChars: 220,
2455
+ maxTotalChars: 1600,
2456
+ mapper: memory => memory.text,
2457
+ })}` : '';
2458
+ let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2459
+ limit: 6,
2460
+ maxEntryChars: 260,
2461
+ maxTotalChars: 1600,
2462
+ mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2463
+ })}` : '';
2464
+ let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2465
+ ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2466
+ : '';
2467
+ let startupPlanStr = sessionContext.bootstrapPlan?.title
2468
+ ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2469
+ : '';
2213
2470
 
2214
2471
  return `You are Winter, an expert AI coding assistant.
2215
2472
 
2216
2473
  ## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2217
- 1. [THINKING BEFORE CODING]: Always output your thought process briefly before generating code. Think about edge cases, design structure, and syntax correctness.
2474
+ 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.
2218
2475
  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.
2219
2476
  3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2220
2477
  4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
2221
2478
  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.
2222
2479
 
2480
+ ## AGENT OPERATING MODE
2481
+ - Treat the repository, its memories, skills, rules, and bundled local resources as first-class context.
2482
+ - 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.
2483
+ - Prefer using the full tool set when needed: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2484
+ - If a question is ambiguous, inspect the project context first instead of guessing.
2485
+ - 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.
2486
+ - Always begin with a short plan before coding or tool execution, then refine the plan if new facts appear.
2487
+
2223
2488
  ## CRITICAL LANGUAGE RULE
2224
2489
  **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.
2225
2490
 
@@ -2260,8 +2525,8 @@ ${colors.reset}
2260
2525
  ## Project
2261
2526
  Working directory: ${this.projectPath}
2262
2527
  Current session: ${this.session.getSessionId().substring(0, 8)}
2263
- ${memoryStr}${plansStr}
2264
- ${context ? `\n## Project Context\n${context}` : ''}
2528
+ ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2529
+ ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}
2265
2530
 
2266
2531
  Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2267
2532
  }
@@ -2269,12 +2534,17 @@ Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
2269
2534
  getFastSystemPrompt() {
2270
2535
  const memories = this.session.getMemory();
2271
2536
  const memoryStr = memories.length > 0
2272
- ? `\nContext nhớ ngắn:\n${memories.slice(-8).map(m => `- ${m.text}`).join('\n')}`
2537
+ ? `\nContext nhớ ngắn:\n${this.summarizePromptList(memories.slice(-8), {
2538
+ limit: 8,
2539
+ maxEntryChars: 160,
2540
+ maxTotalChars: 1200,
2541
+ mapper: memory => memory.text,
2542
+ })}`
2273
2543
  : '';
2274
2544
 
2275
2545
  return `Bạn là Winter, trợ lý AI trả lời ngắn gọn bằng tiếng Việt.
2276
- Trả lời trực tiếp, không gọi tool, không tự bịa thông tin.
2277
- Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì nói ngắn rằng cần dùng chế độ tool.${memoryStr}`;
2546
+ Ưu tiên dùng tool context khi cần; không bịa thông tin.
2547
+ 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 chỉ nói chung chung.${memoryStr}`;
2278
2548
  }
2279
2549
 
2280
2550
  // Tab completion
@@ -2296,9 +2566,26 @@ Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì n
2296
2566
  getAgentSystemPrompt(role, context = '') {
2297
2567
  const memories = this.session.getMemory();
2298
2568
  const plans = this.session.getPlans();
2299
-
2300
- let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${memories.map(m => `- ${m.text}`).join('\n')}` : '';
2301
- let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${plans.map(p => `- [${p.status}] ${p.title}: ${p.description}`).join('\n')}` : '';
2569
+ const sessionContext = this.session.getContext() || {};
2570
+
2571
+ let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
2572
+ limit: 8,
2573
+ maxEntryChars: 220,
2574
+ maxTotalChars: 1600,
2575
+ mapper: memory => memory.text,
2576
+ })}` : '';
2577
+ let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
2578
+ limit: 6,
2579
+ maxEntryChars: 260,
2580
+ maxTotalChars: 1600,
2581
+ mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
2582
+ })}` : '';
2583
+ let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
2584
+ ? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
2585
+ : '';
2586
+ let startupPlanStr = sessionContext.bootstrapPlan?.title
2587
+ ? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
2588
+ : '';
2302
2589
 
2303
2590
  let rolePrompt = '';
2304
2591
  switch (role) {
@@ -2323,7 +2610,7 @@ Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì n
2323
2610
  }
2324
2611
 
2325
2612
  return `## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
2326
- 1. [THINKING BEFORE CODING]: Always output your thought process briefly before generating code. Think about edge cases, design structure, and syntax correctness.
2613
+ 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.
2327
2614
  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.
2328
2615
  3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
2329
2616
  4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
@@ -2333,15 +2620,53 @@ ${rolePrompt}
2333
2620
 
2334
2621
  ## Tool Rules
2335
2622
  - Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
2623
+ - Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.
2336
2624
  - 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}.
2337
2625
  - 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.
2338
2626
  - If a tool call fails because of an unknown alias, call the canonical tool name next.
2627
+ - Always start with a brief plan, then refine it when new facts appear.
2339
2628
 
2340
2629
  ## Project
2341
2630
  Working directory: ${this.projectPath}
2342
2631
  Current session: ${this.session.getSessionId().substring(0, 8)}
2343
- ${memoryStr}${plansStr}
2344
- ${context ? `\n## Project Context\n${context}` : ''}`;
2632
+ ${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
2633
+ ${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}`;
2634
+ }
2635
+
2636
+ compactText(text, maxChars = 1200, label = 'text') {
2637
+ const value = String(text ?? '');
2638
+ if (value.length <= maxChars) return value;
2639
+
2640
+ const headChars = Math.max(0, Math.floor(maxChars * 0.7));
2641
+ const tailChars = Math.max(0, Math.floor(maxChars * 0.2));
2642
+ const head = value.slice(0, headChars);
2643
+ const tail = tailChars > 0 ? value.slice(-tailChars) : '';
2644
+ const omitted = Math.max(0, value.length - head.length - tail.length);
2645
+
2646
+ return [
2647
+ head,
2648
+ `[${label} truncated: ${omitted} chars omitted]`,
2649
+ tail,
2650
+ ].filter(Boolean).join('\n');
2651
+ }
2652
+
2653
+ summarizePromptList(items, { limit = 8, maxEntryChars = 220, maxTotalChars = 1600, mapper = value => value?.text ?? String(value ?? '') } = {}) {
2654
+ const selected = [];
2655
+ let total = 0;
2656
+
2657
+ for (const item of items.slice(-limit)) {
2658
+ const raw = this.compactText(mapper(item), maxEntryChars, 'entry').trim();
2659
+ if (!raw) continue;
2660
+ if (total + raw.length > maxTotalChars && selected.length > 0) break;
2661
+ selected.push(`- ${raw}`);
2662
+ total += raw.length;
2663
+ }
2664
+
2665
+ if (items.length > selected.length) {
2666
+ selected.push(`- ... (${items.length - selected.length} mục đã được lược bớt)`);
2667
+ }
2668
+
2669
+ return selected.join('\n');
2345
2670
  }
2346
2671
 
2347
2672
  getAgentTools(role) {