winter-super-cli 2026.5.24 → 2026.5.25
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/README.md +1 -1
- package/WINTER.md +6 -0
- package/bin/winter.js +77 -220
- package/package.json +1 -1
- package/resources/local/manifest.json +60 -57
- package/src/ai/providers.js +38 -11
- package/src/cli/commands.js +24 -3
- package/src/cli/commands.test.js +116 -0
- package/src/cli/config.js +12 -0
- package/src/cli/repl.js +465 -146
- package/src/cli/repl.test.js +203 -2
- package/src/cli/snowflake-logo.js +15 -7
- package/src/cli/terminal-ui.js +125 -0
- package/src/cli/terminal-ui.test.js +33 -0
- package/src/plugins/manager.js +3 -1
- package/src/session/manager.js +44 -0
- package/src/session/manager.test.js +72 -0
- package/src/tools/executor.js +1 -1
- package/src/tools/executor.test.js +110 -0
- package/resources/local/claude/settings.json +0 -33
- package/resources/local/claude/todos/022bdc3c-e2c0-4a20-a74f-b348ed022c75-agent-022bdc3c-e2c0-4a20-a74f-b348ed022c75.json +0 -1
- package/resources/local/claude/todos/316f0e7d-5512-49fa-8c7f-edc75b777612-agent-316f0e7d-5512-49fa-8c7f-edc75b777612.json +0 -1
- package/resources/local/claude/todos/3676dc17-fca1-4692-934b-ce35e1965af6-agent-3676dc17-fca1-4692-934b-ce35e1965af6.json +0 -1
- package/resources/local/claude/todos/464493de-7f2a-45cf-93e8-ad73214afa10-agent-464493de-7f2a-45cf-93e8-ad73214afa10.json +0 -1
- package/resources/local/claude/todos/51f2e7a7-3f31-4692-a9b2-d3f3906aafea-agent-51f2e7a7-3f31-4692-a9b2-d3f3906aafea.json +0 -1
- package/resources/local/claude/todos/64a67dce-3d62-4a98-a548-b9c91a8e87e8-agent-64a67dce-3d62-4a98-a548-b9c91a8e87e8.json +0 -1
- package/resources/local/claude/todos/727a06e6-0ac2-41ca-8b81-2c14e4d40182-agent-727a06e6-0ac2-41ca-8b81-2c14e4d40182.json +0 -1
- package/resources/local/claude/todos/7d34d296-9b5a-4525-9b68-600d2ae20b59-agent-7d34d296-9b5a-4525-9b68-600d2ae20b59.json +0 -1
- package/resources/local/claude/todos/8c0606f1-5bcc-4176-8125-c5174fd69002-agent-8c0606f1-5bcc-4176-8125-c5174fd69002.json +0 -1
- package/resources/local/claude/todos/905aab16-5225-43f6-8ae4-c94491fd3a6f-agent-905aab16-5225-43f6-8ae4-c94491fd3a6f.json +0 -1
- package/resources/local/claude/todos/9dbe93f0-d62c-4c12-b4eb-0eecc437d625-agent-9dbe93f0-d62c-4c12-b4eb-0eecc437d625.json +0 -1
- package/resources/local/claude/todos/ad48500f-02a5-4f18-970b-82fb595d171f-agent-ad48500f-02a5-4f18-970b-82fb595d171f.json +0 -1
- package/resources/local/claude/todos/af86ea71-9907-4066-907c-68055e6c0081-agent-af86ea71-9907-4066-907c-68055e6c0081.json +0 -1
- package/resources/local/claude/todos/dbb0dc16-5d71-4f1d-a56c-db0741b3d485-agent-dbb0dc16-5d71-4f1d-a56c-db0741b3d485.json +0 -1
- package/resources/local/claude/todos/ff1ac487-eb0f-4c63-9360-fbb0a81bb5ae-agent-ff1ac487-eb0f-4c63-9360-fbb0a81bb5ae.json +0 -1
- package/resources/local/codex/config.toml +0 -84
- package/resources/local/codex/memories/MEMORY.md +0 -972
- package/resources/local/codex/memories/extensions/ad_hoc/instructions.md +0 -13
- package/resources/local/codex/memories/memory_summary.md +0 -188
- package/resources/local/codex/memories/raw_memories.md +0 -1488
- package/resources/local/codex/memories/rollout_summaries/2026-03-27T04-05-14-Iirb-nsis_full_installer_build_cpp_ocr_translator.md +0 -46
- package/resources/local/codex/memories/rollout_summaries/2026-03-28T06-18-17-Si3U-my_translator_overlay_lockfix_portable_nsis.md +0 -112
- package/resources/local/codex/memories/rollout_summaries/2026-04-15T06-42-11-2JMi-qelasy_timeout_and_watch_control_stability.md +0 -90
- package/resources/local/codex/memories/rollout_summaries/2026-04-16T03-12-59-z6Wi-request_all_row_click_detail_navigation.md +0 -42
- package/resources/local/codex/memories/rollout_summaries/2026-04-17T05-49-03-tNBk-my_translator_project_readability_audio_latency_clear_button.md +0 -75
- package/resources/local/codex/memories/rollout_summaries/2026-04-21T04-05-04-EXnh-nsis_packaging_harfbuzz_dll_qml_runtime_debug.md +0 -108
- package/resources/local/codex/memories/rollout_summaries/2026-04-22T03-48-40-VnNG-openclaw_opencode_sync_and_runtime_repair.md +0 -86
- package/resources/local/codex/memories/rollout_summaries/2026-04-22T06-49-49-R8yZ-web_book_user_portal_and_lint_fixes.md +0 -82
- package/resources/local/codex/memories/rollout_summaries/2026-04-22T06-50-35-ZaS1-smoke_admin_rbac_refund_connection_refused.md +0 -35
- package/resources/local/codex/memories/rollout_summaries/2026-04-22T11-05-04-aotT-nextjs_build_fix_statswidget_leaflet_ssr.md +0 -78
- package/resources/local/codex/memories/rollout_summaries/2026-04-23T03-22-24-a5q4-ui_still_looks_cloudflare_only.md +0 -41
- package/resources/local/codex/memories/rollout_summaries/2026-04-23T04-35-47-amlb-bayre247_hero_slide_above_search_form.md +0 -49
- package/resources/local/codex/memories/rollout_summaries/2026-04-23T04-59-21-lZWv-ocr_backend_parity_easyocr_tesseract_paddle_fallback.md +0 -92
- package/resources/local/codex/memories/rollout_summaries/2026-04-23T07-36-22-tPuo-request_workflow_editor_drag_edge_smaller_arrows_roadmap.md +0 -72
- package/resources/local/codex/memories/rollout_summaries/2026-04-24T08-01-05-Gb3B-checkin_shifts_workdays_assignments_and_checkout_overhaul.md +0 -90
- package/resources/local/codex/memories/rollout_summaries/2026-04-25T03-39-02-mbDr-web_book_refund_admin_popup_pagination_responsiveness.md +0 -151
- package/resources/local/codex/memories/rollout_summaries/2026-04-25T09-20-30-4usS-tool_scv_9router_custom_provider_and_paddle_ocr.md +0 -130
- package/resources/local/codex/memories/rollout_summaries/2026-05-06T10-19-38-mt2X-find_db_config_in_web_book_app_env.md +0 -40
- 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
- package/resources/local/codex/memories/rollout_summaries/2026-05-09T07-52-18-On1F-chakra_git_cleanup_readme_bilingual_publish_config.md +0 -88
- package/resources/local/codex/memories/rollout_summaries/2026-05-11T08-05-34-oMEl-check_crack_gui_logo_onefile_build.md +0 -68
- 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
|
|
88
|
-
const
|
|
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${
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
215
|
+
const resourcePaths = this.getResourcePaths();
|
|
216
|
+
const autoLoadTargets = [resourcePaths.agents, resourcePaths.designs, resourcePaths.karpathy];
|
|
180
217
|
|
|
181
|
-
for (const
|
|
218
|
+
for (const targetPath of autoLoadTargets) {
|
|
182
219
|
try {
|
|
183
|
-
const
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
253
|
+
if (!loaded) {
|
|
254
|
+
// nothing to load
|
|
255
|
+
}
|
|
256
|
+
}
|
|
197
257
|
} catch (e) {
|
|
198
|
-
//
|
|
258
|
+
// Ignore read errors for resources
|
|
199
259
|
}
|
|
200
260
|
}
|
|
201
261
|
|
|
202
|
-
//
|
|
203
|
-
const
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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.
|
|
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,
|
|
@@ -1109,48 +1172,50 @@ export class WinterREPL {
|
|
|
1109
1172
|
|
|
1110
1173
|
showCommandMenu() {
|
|
1111
1174
|
const c = colors;
|
|
1112
|
-
const
|
|
1113
|
-
const
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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}`;
|
|
1175
|
+
const width = terminalWidth(72, 112, 92);
|
|
1176
|
+
const innerWidth = width - 4;
|
|
1177
|
+
const split = Math.floor(innerWidth * 0.54);
|
|
1178
|
+
const rightWidth = innerWidth - split - 1;
|
|
1179
|
+
const row = (left, right = '') => {
|
|
1180
|
+
if (!right) return left;
|
|
1181
|
+
return `${padVisible(left, split)} ${padVisible(right, rightWidth)}`;
|
|
1125
1182
|
};
|
|
1126
|
-
|
|
1183
|
+
|
|
1184
|
+
const body = [
|
|
1185
|
+
`${c.bright}${c.cyan}❄ WINTER COMMANDS${c.reset}`,
|
|
1186
|
+
'',
|
|
1187
|
+
`${c.bright}Dự án & Phiên làm việc${c.reset}`,
|
|
1188
|
+
row(`${c.yellow}/pwd${c.reset} Thư mục hiện tại`, `${c.yellow}/session${c.reset} Phiên làm việc`),
|
|
1189
|
+
row(`${c.yellow}/cd${c.reset} Đổi thư mục`, `${c.yellow}/clear${c.reset} Xóa màn hình`),
|
|
1190
|
+
row(`${c.yellow}/config${c.reset} Xem cấu hình`, `${c.yellow}/exit${c.reset} Thoát`),
|
|
1191
|
+
'',
|
|
1192
|
+
`${c.bright}AI & Công cụ${c.reset}`,
|
|
1193
|
+
row(`${c.yellow}/auto${c.reset} TDD tự sửa lỗi`, `${c.yellow}/agent${c.reset} Chạy sub-agent`),
|
|
1194
|
+
row(`${c.yellow}/read${c.reset} Đọc file`, `${c.yellow}/write${c.reset} Ghi file`),
|
|
1195
|
+
row(`${c.yellow}/bash${c.reset} Chạy lệnh terminal`, `${c.yellow}/grep${c.reset} Tìm trong file`),
|
|
1196
|
+
row(`${c.yellow}/glob${c.reset} Tìm file theo pattern`, `${c.yellow}/image${c.reset} Phân tích UI`),
|
|
1197
|
+
row(`${c.yellow}/paste${c.reset} Dán từ clipboard`, `${c.yellow}/plan${c.reset} Lập kế hoạch`),
|
|
1198
|
+
'',
|
|
1199
|
+
`${c.bright}Git Auto-Pilot${c.reset}`,
|
|
1200
|
+
row(`${c.yellow}/commit${c.reset} AI tự viết commit`, `${c.yellow}/review${c.reset} AI review code thay đổi`),
|
|
1201
|
+
'',
|
|
1202
|
+
`${c.bright}Cấu hình Model${c.reset}`,
|
|
1203
|
+
row(`${c.yellow}/provider${c.reset} Đổi provider AI`, `${c.yellow}/model${c.reset} Đổi model`),
|
|
1204
|
+
row(`${c.yellow}/providers${c.reset} Danh sách provider`, `${c.yellow}/models${c.reset} Danh sách model`),
|
|
1205
|
+
'',
|
|
1206
|
+
`${c.bright}Bộ nhớ & Kỹ năng${c.reset}`,
|
|
1207
|
+
row(`${c.yellow}/remember${c.reset} Lưu vào bộ nhớ`, `${c.yellow}/memories${c.reset} Xem bộ nhớ`),
|
|
1208
|
+
row(`${c.yellow}/skills${c.reset} Danh sách kỹ năng`, `${c.yellow}/designs${c.reset} Hệ thống thiết kế`),
|
|
1209
|
+
];
|
|
1210
|
+
|
|
1127
1211
|
console.log(`
|
|
1128
|
-
${
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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}
|
|
1212
|
+
${renderBox({
|
|
1213
|
+
title: `${c.bright}${c.cyan}WINTER COMMANDS${c.reset}`,
|
|
1214
|
+
width,
|
|
1215
|
+
borderColor: c.magenta,
|
|
1216
|
+
titleColor: c.cyan,
|
|
1217
|
+
body,
|
|
1218
|
+
})}
|
|
1154
1219
|
${c.dim}Gõ tin nhắn trực tiếp để chat · ESC để hủy · Prompt tự xếp hàng chờ${c.reset}
|
|
1155
1220
|
`);
|
|
1156
1221
|
}
|
|
@@ -1358,13 +1423,15 @@ ${colors.reset}
|
|
|
1358
1423
|
usedTools = true;
|
|
1359
1424
|
if (this.spinner) this.spinner.stop();
|
|
1360
1425
|
|
|
1361
|
-
const BOX_WIDTH =
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1364
|
-
const
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1367
|
-
console.log(`\n${colors.magenta}
|
|
1426
|
+
const BOX_WIDTH = terminalWidth(76, 116, 92);
|
|
1427
|
+
const innerWidth = BOX_WIDTH - 2;
|
|
1428
|
+
const title = ' AGENT TOOLS EXECUTION ';
|
|
1429
|
+
const titlePad = Math.max(0, innerWidth - visibleWidth(title));
|
|
1430
|
+
const leftPad = Math.floor(titlePad / 2);
|
|
1431
|
+
const rightPad = titlePad - leftPad;
|
|
1432
|
+
console.log(`\n${colors.magenta}╭${'─'.repeat(innerWidth)}╮${colors.reset}`);
|
|
1433
|
+
console.log(`${colors.magenta}│${colors.reset}${' '.repeat(leftPad)}${colors.bright}${title}${colors.reset}${' '.repeat(rightPad)}${colors.magenta}│${colors.reset}`);
|
|
1434
|
+
console.log(`${colors.magenta}├${'─'.repeat(innerWidth)}┤${colors.reset}`);
|
|
1368
1435
|
messages.push({
|
|
1369
1436
|
role: 'assistant',
|
|
1370
1437
|
content: assistantMsg.content || '',
|
|
@@ -1420,34 +1487,16 @@ ${colors.reset}
|
|
|
1420
1487
|
const statusIcon = result.success === false ? `${colors.red}✖${colors.reset}` : `${colors.green}✓${colors.reset}`;
|
|
1421
1488
|
|
|
1422
1489
|
const maxLen = BOX_WIDTH - 8;
|
|
1423
|
-
const
|
|
1424
|
-
for (const line of
|
|
1425
|
-
|
|
1426
|
-
const cleanLine = line
|
|
1427
|
-
|
|
1428
|
-
|
|
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
|
-
}
|
|
1490
|
+
const wrappedLines = summary.split('\n').flatMap(line => wrapText(line, maxLen));
|
|
1491
|
+
for (const [index, line] of wrappedLines.entries()) {
|
|
1492
|
+
const prefix = index === 0 ? statusIcon : ' ';
|
|
1493
|
+
const cleanLine = stripAnsi(line);
|
|
1494
|
+
const padding = ' '.repeat(Math.max(0, maxLen - visibleWidth(cleanLine)));
|
|
1495
|
+
console.log(`${colors.magenta}│${colors.reset} ${prefix} ${colors.dim}${cleanLine}${colors.reset}${padding}${colors.magenta}│${colors.reset}`);
|
|
1447
1496
|
}
|
|
1448
1497
|
}
|
|
1449
1498
|
}
|
|
1450
|
-
console.log(`${colors.magenta}╰${'─'.repeat(
|
|
1499
|
+
console.log(`${colors.magenta}╰${'─'.repeat(innerWidth)}╯${colors.reset}\n`);
|
|
1451
1500
|
}
|
|
1452
1501
|
|
|
1453
1502
|
if (usedTools && !finalContent) {
|
|
@@ -2079,16 +2128,16 @@ ${colors.reset}
|
|
|
2079
2128
|
|
|
2080
2129
|
async chat(message, imageAttachments = []) {
|
|
2081
2130
|
try {
|
|
2082
|
-
const needsTools =
|
|
2083
|
-
const context =
|
|
2131
|
+
const needsTools = true;
|
|
2132
|
+
const context = await this.getProjectContext();
|
|
2084
2133
|
const messages = [
|
|
2085
|
-
{ role: 'system', content:
|
|
2134
|
+
{ role: 'system', content: this.getSystemPrompt(context) }
|
|
2086
2135
|
];
|
|
2087
2136
|
|
|
2088
2137
|
const history = this.getPromptHistory({
|
|
2089
|
-
limit:
|
|
2090
|
-
maxEntryChars:
|
|
2091
|
-
maxTotalChars:
|
|
2138
|
+
limit: 20,
|
|
2139
|
+
maxEntryChars: 2000,
|
|
2140
|
+
maxTotalChars: 12000,
|
|
2092
2141
|
});
|
|
2093
2142
|
for (const entry of history) {
|
|
2094
2143
|
messages.push({ role: entry.role, content: entry.content });
|
|
@@ -2109,7 +2158,7 @@ ${colors.reset}
|
|
|
2109
2158
|
messages.push({ role: 'user', content: message });
|
|
2110
2159
|
}
|
|
2111
2160
|
|
|
2112
|
-
const tools =
|
|
2161
|
+
const tools = this.getAgentTools('general');
|
|
2113
2162
|
const finalContent = await this.runConversation(messages, 'Thinking', tools);
|
|
2114
2163
|
|
|
2115
2164
|
await this.session.addToHistory({ role: 'user', content: message });
|
|
@@ -2121,12 +2170,7 @@ ${colors.reset}
|
|
|
2121
2170
|
}
|
|
2122
2171
|
|
|
2123
2172
|
shouldUseTools(message = '', imageAttachments = []) {
|
|
2124
|
-
|
|
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);
|
|
2173
|
+
return true;
|
|
2130
2174
|
}
|
|
2131
2175
|
|
|
2132
2176
|
getPromptHistory({ limit = 20, maxEntryChars = 2000, maxTotalChars = 12000 } = {}) {
|
|
@@ -2171,19 +2215,29 @@ ${colors.reset}
|
|
|
2171
2215
|
|
|
2172
2216
|
async getProjectContext() {
|
|
2173
2217
|
const context = [];
|
|
2174
|
-
const
|
|
2218
|
+
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
2175
2219
|
|
|
2176
|
-
for (const file of
|
|
2220
|
+
for (const file of projectInstructionFiles) {
|
|
2177
2221
|
try {
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
if (stat.isFile()) {
|
|
2181
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
2182
|
-
context.push(`[${file}]\n${content.substring(0, 300)}...`);
|
|
2183
|
-
}
|
|
2222
|
+
const preview = this.compactText(file.content, 900, 'project instruction');
|
|
2223
|
+
context.push(`[${file.relativePath}]\n${preview}`);
|
|
2184
2224
|
} catch { }
|
|
2185
2225
|
}
|
|
2186
2226
|
|
|
2227
|
+
try {
|
|
2228
|
+
const packageJsonPath = path.join(this.projectPath, 'package.json');
|
|
2229
|
+
const stat = await fs.stat(packageJsonPath);
|
|
2230
|
+
if (stat.isFile()) {
|
|
2231
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
2232
|
+
context.push(`[package.json]\n${this.compactText(content, 1200, 'package.json')}`);
|
|
2233
|
+
}
|
|
2234
|
+
} catch { }
|
|
2235
|
+
|
|
2236
|
+
const localResources = await this.getLocalResourceContext();
|
|
2237
|
+
if (localResources) {
|
|
2238
|
+
context.push(localResources);
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2187
2241
|
// Git Context
|
|
2188
2242
|
try {
|
|
2189
2243
|
const { execSync } = await import('child_process');
|
|
@@ -2191,35 +2245,240 @@ ${colors.reset}
|
|
|
2191
2245
|
if (gitStatus) {
|
|
2192
2246
|
context.push(`[Git Status]\n${gitStatus}`);
|
|
2193
2247
|
|
|
2248
|
+
const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
|
|
2249
|
+
if (gitSummary) {
|
|
2250
|
+
context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2194
2253
|
// 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,
|
|
2254
|
+
const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
|
|
2196
2255
|
if (gitDiff) {
|
|
2197
|
-
context.push(`[Git Diff]\n${gitDiff}
|
|
2256
|
+
context.push(`[Git Diff]\n${this.compactText(gitDiff, 1800, 'git diff')}`);
|
|
2198
2257
|
}
|
|
2199
2258
|
}
|
|
2200
2259
|
} catch (e) {
|
|
2201
2260
|
// Not a git repo or git not installed
|
|
2202
2261
|
}
|
|
2203
2262
|
|
|
2204
|
-
return context.join('\n\n') || 'No project context found.';
|
|
2263
|
+
return this.compactText(context.join('\n\n') || 'No project context found.', 9000, 'project context');
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
async getLocalResourceContext() {
|
|
2267
|
+
try {
|
|
2268
|
+
const manifestPath = this.getResourcePaths().manifest;
|
|
2269
|
+
const raw = await fs.readFile(manifestPath, 'utf8');
|
|
2270
|
+
const manifest = JSON.parse(raw.replace(/^\uFEFF/, ''));
|
|
2271
|
+
const paths = this.getResourcePaths();
|
|
2272
|
+
const [claudeSkills, codexSkills, claudePlugins, codexMemories] = await Promise.all([
|
|
2273
|
+
this.listPathEntries(paths.claude.skills, 20),
|
|
2274
|
+
this.listPathEntries(paths.codex.skills, 20),
|
|
2275
|
+
this.listPathEntries(paths.claude.plugins, 20),
|
|
2276
|
+
this.listPathEntries(paths.codex.memories, 20),
|
|
2277
|
+
]);
|
|
2278
|
+
|
|
2279
|
+
const lines = [];
|
|
2280
|
+
lines.push('[Local Resources]');
|
|
2281
|
+
lines.push(`- Root: ${manifest.root || this.getResourcePaths().localRoot}`);
|
|
2282
|
+
|
|
2283
|
+
for (const resource of manifest.localResources || []) {
|
|
2284
|
+
lines.push(`- ${resource.name}: ${resource.files} files, ${(resource.bytes / 1024 / 1024).toFixed(2)} MB`);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
if (manifest.redacted?.length) {
|
|
2288
|
+
lines.push(`- Redacted: ${manifest.redacted.join('; ')}`);
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
lines.push('- Use Read/Grep/Glob to inspect any local resource when it matters for the task.');
|
|
2292
|
+
lines.push('- Local resource families: agents.md, awesome-design-md, claude, codex, karpathy-tools.');
|
|
2293
|
+
|
|
2294
|
+
if (claudeSkills.length > 0) {
|
|
2295
|
+
lines.push(`- Claude skills: ${claudeSkills.slice(0, 10).map(item => item.name).join(', ')}${claudeSkills.length > 10 ? ', ...' : ''}`);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (claudePlugins.length > 0) {
|
|
2299
|
+
lines.push(`- Claude plugin roots: ${claudePlugins.slice(0, 10).map(item => item.name).join(', ')}${claudePlugins.length > 10 ? ', ...' : ''}`);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
if (codexSkills.length > 0) {
|
|
2303
|
+
lines.push(`- Codex skills: ${codexSkills.slice(0, 10).map(item => item.name).join(', ')}${codexSkills.length > 10 ? ', ...' : ''}`);
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
if (codexMemories.length > 0) {
|
|
2307
|
+
lines.push(`- Codex memories: ${codexMemories.slice(0, 10).map(item => item.name).join(', ')}${codexMemories.length > 10 ? ', ...' : ''}`);
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
return lines.join('\n');
|
|
2311
|
+
} catch {
|
|
2312
|
+
return '';
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2316
|
+
async bootstrapProjectCapabilities() {
|
|
2317
|
+
const sessionContext = this.session.getContext() || {};
|
|
2318
|
+
|
|
2319
|
+
if (!sessionContext.bootstrapPlan?.id && this.session.getPlans().length === 0) {
|
|
2320
|
+
const plan = await this.session.createPlan(
|
|
2321
|
+
'Bootstrap project context',
|
|
2322
|
+
'Inspect rules, resources, and likely skills before doing any task work.'
|
|
2323
|
+
);
|
|
2324
|
+
await this.session.addPlanStep(plan.id, {
|
|
2325
|
+
description: 'Read project rules, local resources, and attached skill libraries.',
|
|
2326
|
+
});
|
|
2327
|
+
await this.session.addPlanStep(plan.id, {
|
|
2328
|
+
description: 'Choose the smallest relevant skill set before making changes.',
|
|
2329
|
+
});
|
|
2330
|
+
await this.session.updateContext('bootstrapPlan', {
|
|
2331
|
+
id: plan.id,
|
|
2332
|
+
title: plan.title,
|
|
2333
|
+
description: plan.description,
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
const skillSnapshot = await this.inferStartupSkills();
|
|
2338
|
+
await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
|
|
2339
|
+
await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
|
|
2340
|
+
|
|
2341
|
+
const appliedText = skillSnapshot.activeSkills.length > 0
|
|
2342
|
+
? `Auto-applied skills: ${skillSnapshot.activeSkills.join(', ')}`
|
|
2343
|
+
: 'Auto-applied skills: none';
|
|
2344
|
+
await this.session.replaceMemory('[Auto-applied skills]', appliedText, 'skill');
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
async inferStartupSkills() {
|
|
2348
|
+
const catalog = await this.getStartupSkillCatalog();
|
|
2349
|
+
const signals = await this.getProjectSignals();
|
|
2350
|
+
const normalizedSignals = new Set(signals.map(value => value.toLowerCase()));
|
|
2351
|
+
|
|
2352
|
+
const hasAny = (...items) => items.some(item => normalizedSignals.has(item));
|
|
2353
|
+
const activeSkills = new Set([
|
|
2354
|
+
'coding',
|
|
2355
|
+
'debug',
|
|
2356
|
+
'refactor',
|
|
2357
|
+
'test',
|
|
2358
|
+
]);
|
|
2359
|
+
|
|
2360
|
+
if (hasAny('react', 'next', 'nextjs', 'tsx', 'jsx', 'vue', 'svelte', 'vite')) {
|
|
2361
|
+
['vercel-react-best-practices', 'web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
if (hasAny('design', 'ui', 'ux', 'css', 'tailwind', 'styled-components', 'scss', 'style', 'component')) {
|
|
2365
|
+
['web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
if (hasAny('claude', 'agent', 'mcp', 'plugin', 'skill', 'automation', 'workflow')) {
|
|
2369
|
+
['skill-creator', 'claude-automation-recommender', 'claude-md-improver', 'agent-development', 'hook-development', 'command-development', 'plugin-dev'].forEach(skill => activeSkills.add(skill));
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
if (hasAny('docs', 'markdown', 'md', 'readme', 'documentation')) {
|
|
2373
|
+
['claude-md-improver', 'docs', 'writing-rules'].forEach(skill => activeSkills.add(skill));
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
if (hasAny('figma', 'design-md', 'brand', 'brand-guidelines', 'style-guide')) {
|
|
2377
|
+
['vibefigma', 'web-design-guidelines'].forEach(skill => activeSkills.add(skill));
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
const filtered = [...activeSkills].filter(skill => catalog.has(skill));
|
|
2381
|
+
return {
|
|
2382
|
+
availableSkills: [...catalog],
|
|
2383
|
+
activeSkills: filtered,
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
async getStartupSkillCatalog() {
|
|
2388
|
+
const catalog = new Set(['coding', 'design', 'debug', 'refactor', 'test', 'security', 'performance']);
|
|
2389
|
+
const resourcePaths = this.getResourcePaths();
|
|
2390
|
+
const folders = [resourcePaths.claude.skills, resourcePaths.codex.skills];
|
|
2391
|
+
|
|
2392
|
+
for (const folder of folders) {
|
|
2393
|
+
const entries = await this.listPathEntries(folder, 200);
|
|
2394
|
+
for (const entry of entries) {
|
|
2395
|
+
catalog.add(entry.name);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
return catalog;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
async getProjectSignals() {
|
|
2403
|
+
const signals = [];
|
|
2404
|
+
|
|
2405
|
+
try {
|
|
2406
|
+
const packageJsonPath = path.join(this.projectPath, 'package.json');
|
|
2407
|
+
const raw = await fs.readFile(packageJsonPath, 'utf8');
|
|
2408
|
+
const pkg = JSON.parse(raw);
|
|
2409
|
+
|
|
2410
|
+
signals.push(String(pkg.name || '').toLowerCase());
|
|
2411
|
+
signals.push(String(pkg.description || '').toLowerCase());
|
|
2412
|
+
|
|
2413
|
+
for (const key of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
2414
|
+
const deps = pkg[key] || {};
|
|
2415
|
+
for (const depName of Object.keys(deps)) {
|
|
2416
|
+
signals.push(depName.toLowerCase());
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2420
|
+
for (const script of Object.values(pkg.scripts || {})) {
|
|
2421
|
+
signals.push(String(script).toLowerCase());
|
|
2422
|
+
}
|
|
2423
|
+
} catch {
|
|
2424
|
+
// Ignore package.json parsing issues.
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
try {
|
|
2428
|
+
const entries = await fs.readdir(this.projectPath, { withFileTypes: true });
|
|
2429
|
+
for (const entry of entries) {
|
|
2430
|
+
if (!entry.isFile()) continue;
|
|
2431
|
+
signals.push(path.extname(entry.name).toLowerCase().slice(1));
|
|
2432
|
+
signals.push(entry.name.toLowerCase());
|
|
2433
|
+
}
|
|
2434
|
+
} catch {
|
|
2435
|
+
// Ignore directory scan issues.
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
return signals.filter(Boolean);
|
|
2205
2439
|
}
|
|
2206
2440
|
|
|
2207
2441
|
getSystemPrompt(context = '') {
|
|
2208
2442
|
const memories = this.session.getMemory();
|
|
2209
2443
|
const plans = this.session.getPlans();
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
let
|
|
2444
|
+
const sessionContext = this.session.getContext() || {};
|
|
2445
|
+
|
|
2446
|
+
let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
|
|
2447
|
+
limit: 8,
|
|
2448
|
+
maxEntryChars: 220,
|
|
2449
|
+
maxTotalChars: 1600,
|
|
2450
|
+
mapper: memory => memory.text,
|
|
2451
|
+
})}` : '';
|
|
2452
|
+
let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
|
|
2453
|
+
limit: 6,
|
|
2454
|
+
maxEntryChars: 260,
|
|
2455
|
+
maxTotalChars: 1600,
|
|
2456
|
+
mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
|
|
2457
|
+
})}` : '';
|
|
2458
|
+
let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
|
|
2459
|
+
? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
|
|
2460
|
+
: '';
|
|
2461
|
+
let startupPlanStr = sessionContext.bootstrapPlan?.title
|
|
2462
|
+
? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
|
|
2463
|
+
: '';
|
|
2213
2464
|
|
|
2214
2465
|
return `You are Winter, an expert AI coding assistant.
|
|
2215
2466
|
|
|
2216
2467
|
## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
|
|
2217
|
-
1. [THINKING BEFORE CODING]:
|
|
2468
|
+
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
2469
|
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
2470
|
3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
|
|
2220
2471
|
4. [NO HALLUCINATION]: If you don't know, use tools (Grep/Read/Web) to find out. Do not guess file paths or APIs.
|
|
2221
2472
|
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
2473
|
|
|
2474
|
+
## AGENT OPERATING MODE
|
|
2475
|
+
- Treat the repository, its memories, skills, rules, and bundled local resources as first-class context.
|
|
2476
|
+
- 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.
|
|
2477
|
+
- Prefer using the full tool set when needed: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
|
|
2478
|
+
- If a question is ambiguous, inspect the project context first instead of guessing.
|
|
2479
|
+
- 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.
|
|
2480
|
+
- Always begin with a short plan before coding or tool execution, then refine the plan if new facts appear.
|
|
2481
|
+
|
|
2223
2482
|
## CRITICAL LANGUAGE RULE
|
|
2224
2483
|
**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
2484
|
|
|
@@ -2260,8 +2519,8 @@ ${colors.reset}
|
|
|
2260
2519
|
## Project
|
|
2261
2520
|
Working directory: ${this.projectPath}
|
|
2262
2521
|
Current session: ${this.session.getSessionId().substring(0, 8)}
|
|
2263
|
-
${memoryStr}${plansStr}
|
|
2264
|
-
${context ? `\n## Project Context\n${context}` : ''}
|
|
2522
|
+
${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
|
|
2523
|
+
${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}
|
|
2265
2524
|
|
|
2266
2525
|
Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
|
|
2267
2526
|
}
|
|
@@ -2269,12 +2528,17 @@ Be helpful, be precise, and get things done. Always respond in Vietnamese.`;
|
|
|
2269
2528
|
getFastSystemPrompt() {
|
|
2270
2529
|
const memories = this.session.getMemory();
|
|
2271
2530
|
const memoryStr = memories.length > 0
|
|
2272
|
-
? `\nContext nhớ ngắn:\n${memories.slice(-8)
|
|
2531
|
+
? `\nContext nhớ ngắn:\n${this.summarizePromptList(memories.slice(-8), {
|
|
2532
|
+
limit: 8,
|
|
2533
|
+
maxEntryChars: 160,
|
|
2534
|
+
maxTotalChars: 1200,
|
|
2535
|
+
mapper: memory => memory.text,
|
|
2536
|
+
})}`
|
|
2273
2537
|
: '';
|
|
2274
2538
|
|
|
2275
2539
|
return `Bạn là Winter, trợ lý AI trả lời ngắn gọn bằng tiếng Việt.
|
|
2276
|
-
|
|
2277
|
-
Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì
|
|
2540
|
+
Ưu tiên dùng tool và context khi cần; không bịa thông tin.
|
|
2541
|
+
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}`;
|
|
2278
2542
|
}
|
|
2279
2543
|
|
|
2280
2544
|
// Tab completion
|
|
@@ -2296,9 +2560,26 @@ Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì n
|
|
|
2296
2560
|
getAgentSystemPrompt(role, context = '') {
|
|
2297
2561
|
const memories = this.session.getMemory();
|
|
2298
2562
|
const plans = this.session.getPlans();
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
let
|
|
2563
|
+
const sessionContext = this.session.getContext() || {};
|
|
2564
|
+
|
|
2565
|
+
let memoryStr = memories.length > 0 ? `\n## Memories (Important Context)\n${this.summarizePromptList(memories, {
|
|
2566
|
+
limit: 8,
|
|
2567
|
+
maxEntryChars: 220,
|
|
2568
|
+
maxTotalChars: 1600,
|
|
2569
|
+
mapper: memory => memory.text,
|
|
2570
|
+
})}` : '';
|
|
2571
|
+
let plansStr = plans.length > 0 ? `\n## Active Plans & Tasks\n${this.summarizePromptList(plans, {
|
|
2572
|
+
limit: 6,
|
|
2573
|
+
maxEntryChars: 260,
|
|
2574
|
+
maxTotalChars: 1600,
|
|
2575
|
+
mapper: plan => `[${plan.status}] ${plan.title}: ${plan.description}`,
|
|
2576
|
+
})}` : '';
|
|
2577
|
+
let skillsStr = Array.isArray(sessionContext.activeSkills) && sessionContext.activeSkills.length > 0
|
|
2578
|
+
? `\n## Auto-applied Skills\n${sessionContext.activeSkills.slice(0, 12).map(skill => `- ${skill}`).join('\n')}${sessionContext.activeSkills.length > 12 ? '\n- ...' : ''}`
|
|
2579
|
+
: '';
|
|
2580
|
+
let startupPlanStr = sessionContext.bootstrapPlan?.title
|
|
2581
|
+
? `\n## Startup Plan\n- ${sessionContext.bootstrapPlan.title}: ${sessionContext.bootstrapPlan.description}`
|
|
2582
|
+
: '';
|
|
2302
2583
|
|
|
2303
2584
|
let rolePrompt = '';
|
|
2304
2585
|
switch (role) {
|
|
@@ -2323,7 +2604,7 @@ Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì n
|
|
|
2323
2604
|
}
|
|
2324
2605
|
|
|
2325
2606
|
return `## CRITICAL AI RULES (MUST FOLLOW STRICTLY):
|
|
2326
|
-
1. [THINKING BEFORE CODING]:
|
|
2607
|
+
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
2608
|
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
2609
|
3. [CODE QUALITY]: Write clean, modular, SOLID code. Check for syntax errors carefully. Do not generate incomplete code blocks.
|
|
2329
2610
|
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 +2614,53 @@ ${rolePrompt}
|
|
|
2333
2614
|
|
|
2334
2615
|
## Tool Rules
|
|
2335
2616
|
- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.
|
|
2617
|
+
- Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.
|
|
2336
2618
|
- 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
2619
|
- 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
2620
|
- If a tool call fails because of an unknown alias, call the canonical tool name next.
|
|
2621
|
+
- Always start with a brief plan, then refine it when new facts appear.
|
|
2339
2622
|
|
|
2340
2623
|
## Project
|
|
2341
2624
|
Working directory: ${this.projectPath}
|
|
2342
2625
|
Current session: ${this.session.getSessionId().substring(0, 8)}
|
|
2343
|
-
${memoryStr}${plansStr}
|
|
2344
|
-
${context ? `\n## Project Context\n${context}` : ''}`;
|
|
2626
|
+
${memoryStr}${plansStr}${skillsStr}${startupPlanStr}
|
|
2627
|
+
${context ? `\n## Project Context\n${this.compactText(context, 6000, 'project context')}` : ''}`;
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
compactText(text, maxChars = 1200, label = 'text') {
|
|
2631
|
+
const value = String(text ?? '');
|
|
2632
|
+
if (value.length <= maxChars) return value;
|
|
2633
|
+
|
|
2634
|
+
const headChars = Math.max(0, Math.floor(maxChars * 0.7));
|
|
2635
|
+
const tailChars = Math.max(0, Math.floor(maxChars * 0.2));
|
|
2636
|
+
const head = value.slice(0, headChars);
|
|
2637
|
+
const tail = tailChars > 0 ? value.slice(-tailChars) : '';
|
|
2638
|
+
const omitted = Math.max(0, value.length - head.length - tail.length);
|
|
2639
|
+
|
|
2640
|
+
return [
|
|
2641
|
+
head,
|
|
2642
|
+
`[${label} truncated: ${omitted} chars omitted]`,
|
|
2643
|
+
tail,
|
|
2644
|
+
].filter(Boolean).join('\n');
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
summarizePromptList(items, { limit = 8, maxEntryChars = 220, maxTotalChars = 1600, mapper = value => value?.text ?? String(value ?? '') } = {}) {
|
|
2648
|
+
const selected = [];
|
|
2649
|
+
let total = 0;
|
|
2650
|
+
|
|
2651
|
+
for (const item of items.slice(-limit)) {
|
|
2652
|
+
const raw = this.compactText(mapper(item), maxEntryChars, 'entry').trim();
|
|
2653
|
+
if (!raw) continue;
|
|
2654
|
+
if (total + raw.length > maxTotalChars && selected.length > 0) break;
|
|
2655
|
+
selected.push(`- ${raw}`);
|
|
2656
|
+
total += raw.length;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
if (items.length > selected.length) {
|
|
2660
|
+
selected.push(`- ... (${items.length - selected.length} mục đã được lược bớt)`);
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
return selected.join('\n');
|
|
2345
2664
|
}
|
|
2346
2665
|
|
|
2347
2666
|
getAgentTools(role) {
|