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.
- 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 +64 -13
- package/src/ai/providers.test.js +35 -0
- package/src/cli/commands.js +61 -3
- package/src/cli/commands.test.js +179 -0
- package/src/cli/config.js +12 -0
- package/src/cli/repl.js +475 -150
- package/src/cli/repl.test.js +234 -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,
|
|
@@ -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
|
-
|
|
1036
|
-
await this.
|
|
1037
|
-
|
|
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
|
|
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}`;
|
|
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
|
-
|
|
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
|
-
${
|
|
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}
|
|
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 =
|
|
1362
|
-
const
|
|
1363
|
-
const
|
|
1364
|
-
const
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1367
|
-
console.log(`\n${colors.magenta}
|
|
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
|
|
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
|
-
}
|
|
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(
|
|
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 =
|
|
2083
|
-
const context =
|
|
2137
|
+
const needsTools = true;
|
|
2138
|
+
const context = await this.getProjectContext();
|
|
2084
2139
|
const messages = [
|
|
2085
|
-
{ role: 'system', content:
|
|
2140
|
+
{ role: 'system', content: this.getSystemPrompt(context) }
|
|
2086
2141
|
];
|
|
2087
2142
|
|
|
2088
2143
|
const history = this.getPromptHistory({
|
|
2089
|
-
limit:
|
|
2090
|
-
maxEntryChars:
|
|
2091
|
-
maxTotalChars:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
2224
|
+
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
2175
2225
|
|
|
2176
|
-
for (const file of
|
|
2226
|
+
for (const file of projectInstructionFiles) {
|
|
2177
2227
|
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
|
-
}
|
|
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,
|
|
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}
|
|
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
|
-
|
|
2212
|
-
let
|
|
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]:
|
|
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)
|
|
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
|
-
|
|
2277
|
-
Nếu người dùng yêu cầu sửa file/chạy lệnh/đọc dự án thì
|
|
2546
|
+
Ưu tiên dùng tool và 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 vì 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
|
-
|
|
2301
|
-
let
|
|
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]:
|
|
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) {
|