winter-super-cli 2026.6.5 → 2026.6.6
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/package.json +3 -3
- package/src/agent/runtime.js +3 -0
- package/src/ai/model-capabilities.js +2 -1
- package/src/ai/prompts/system-prompt.js +7 -48
- package/src/ai/providers.js +47 -7
- package/src/ai/small-model-amplifier.js +5 -17
- package/src/cli/commands.js +150 -0
- package/src/cli/repl.js +189 -180
- package/src/codebase-index/codegraph-adapter.js +154 -0
- package/src/codebase-index/indexer.js +1 -1
- package/src/codebase-index/search.js +31 -2
- package/src/context/router.js +4 -41
package/src/cli/repl.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* WINTER REPL
|
|
3
3
|
* Claude Code / Codex style interactive REPL
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
buildPromptToolResultWithTokenJuice,
|
|
31
31
|
} from './tool-runtime.js';
|
|
32
32
|
import { TokenJuice } from '../context/token-juice.js';
|
|
33
|
-
import { classifyModelTier
|
|
33
|
+
import { classifyModelTier } from '../ai/model-capabilities.js';
|
|
34
34
|
import {
|
|
35
35
|
addUsage as mergeUsage,
|
|
36
36
|
buildToolCallSignature as buildToolCallSignatureText,
|
|
@@ -77,8 +77,8 @@ export class WinterREPL {
|
|
|
77
77
|
this.ai = new AIProviderManager(this.config);
|
|
78
78
|
this.tools = new ToolExecutor(this);
|
|
79
79
|
this.projectPath = options.projectPath || process.cwd();
|
|
80
|
-
this.sessionId = options.sessionId || null; //
|
|
81
|
-
this.version = options.version || '1.0.0'; //
|
|
80
|
+
this.sessionId = options.sessionId || null; // Nhận sessionId từ bin
|
|
81
|
+
this.version = options.version || '1.0.0'; // Nhận version từ bin
|
|
82
82
|
this.running = true;
|
|
83
83
|
this.history = [];
|
|
84
84
|
this.maxHistory = 500;
|
|
@@ -118,7 +118,7 @@ export class WinterREPL {
|
|
|
118
118
|
|
|
119
119
|
async initCodebaseSearch() {
|
|
120
120
|
if (this.codebaseSearcher) return;
|
|
121
|
-
this.codebaseSearcher = new CodebaseSearch({ projectPath: this.projectPath });
|
|
121
|
+
this.codebaseSearcher = new CodebaseSearch({ projectPath: this.projectPath, enableCodeGraph: true });
|
|
122
122
|
await this.codebaseSearcher.init();
|
|
123
123
|
this.atContext = new AtContextResolver({
|
|
124
124
|
projectPath: this.projectPath,
|
|
@@ -166,7 +166,7 @@ export class WinterREPL {
|
|
|
166
166
|
await this.codebaseSearcher.clear();
|
|
167
167
|
}
|
|
168
168
|
const stats = await this.codebaseSearcher.reindex();
|
|
169
|
-
console.log(`${colors.green}
|
|
169
|
+
console.log(`${colors.green}✓ Codebase indexed:${colors.reset}`);
|
|
170
170
|
console.log(` ${colors.dim}Files: ${stats.totalFiles}, Chunks: ${stats.totalChunks}, Indexed: ${stats.indexedFiles}, Skipped: ${stats.skipped}${colors.reset}`);
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -229,11 +229,11 @@ export class WinterREPL {
|
|
|
229
229
|
if (before.totalChunks > 0) return before;
|
|
230
230
|
|
|
231
231
|
if (verbose) {
|
|
232
|
-
console.log(`${colors.dim}
|
|
232
|
+
console.log(`${colors.dim}Indexing codebase for semantic search...${colors.reset}`);
|
|
233
233
|
}
|
|
234
234
|
const indexedStats = await this.codebaseSearcher.reindex();
|
|
235
235
|
if (verbose) {
|
|
236
|
-
console.log(`${colors.green}
|
|
236
|
+
console.log(`${colors.green}✓ Codebase indexed: ${indexedStats.totalFiles} files, ${indexedStats.totalChunks} chunks${colors.reset}`);
|
|
237
237
|
}
|
|
238
238
|
return this.codebaseSearcher.indexer.getStats();
|
|
239
239
|
}
|
|
@@ -278,7 +278,7 @@ export class WinterREPL {
|
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
return this.compactText(lines.join('\n'),
|
|
281
|
+
return this.compactText(lines.join('\n'), 4200, 'codebase context');
|
|
282
282
|
} catch (error) {
|
|
283
283
|
return `[Codebase Index]\nUnavailable: ${error.message}`;
|
|
284
284
|
}
|
|
@@ -312,7 +312,7 @@ export class WinterREPL {
|
|
|
312
312
|
if (ans.trim().toLowerCase() === 'y') {
|
|
313
313
|
const ok = await this.diffView.restoreFromBackup(latest.backup, latest.original);
|
|
314
314
|
if (ok) {
|
|
315
|
-
console.log(`${colors.green}
|
|
315
|
+
console.log(`${colors.green}✓ Restored ${latest.original} from backup${colors.reset}`);
|
|
316
316
|
} else {
|
|
317
317
|
console.log(`${colors.red}Failed to restore${colors.reset}`);
|
|
318
318
|
}
|
|
@@ -456,7 +456,7 @@ export class WinterREPL {
|
|
|
456
456
|
});
|
|
457
457
|
await this.session.replaceMemory('[Project Anchor]', `Current project is ${this.projectPath}. Treat this path as the canonical working directory for the session.`, 'info');
|
|
458
458
|
|
|
459
|
-
//
|
|
459
|
+
// Tự động đọc và ghi nhớ một số tài nguyên cục bộ an toàn.
|
|
460
460
|
const fsPromises = await import('fs/promises');
|
|
461
461
|
const resourcePaths = this.getResourcePaths();
|
|
462
462
|
const autoLoadTargets = [resourcePaths.agents, resourcePaths.designs, resourcePaths.karpathy, resourcePaths.pageAgent];
|
|
@@ -469,9 +469,9 @@ export class WinterREPL {
|
|
|
469
469
|
if (stat.isFile()) {
|
|
470
470
|
const content = await fsPromises.readFile(targetPath, 'utf8');
|
|
471
471
|
const fileName = path.basename(targetPath);
|
|
472
|
-
const memoryKey = `[
|
|
472
|
+
const memoryKey = `[Tự động ghi nhớ file ${fileName}]`;
|
|
473
473
|
await this.session.replaceMemory(memoryKey, content);
|
|
474
|
-
console.log(`${colors.dim}
|
|
474
|
+
console.log(`${colors.dim}✓ Đã tự động nạp và ghi nhớ file ${fileName}${colors.reset}`);
|
|
475
475
|
continue;
|
|
476
476
|
}
|
|
477
477
|
|
|
@@ -485,9 +485,9 @@ export class WinterREPL {
|
|
|
485
485
|
const cstat = await fsPromises.stat(p).catch(() => null);
|
|
486
486
|
if (cstat && cstat.isFile()) {
|
|
487
487
|
const content = await fsPromises.readFile(p, 'utf8');
|
|
488
|
-
const memoryKey = `[
|
|
488
|
+
const memoryKey = `[Tự động ghi nhớ file ${path.basename(targetPath)}/${c}]`;
|
|
489
489
|
await this.session.replaceMemory(memoryKey, content);
|
|
490
|
-
console.log(`${colors.dim}
|
|
490
|
+
console.log(`${colors.dim}✓ Đã tự động nạp và ghi nhớ ${path.basename(targetPath)}/${c}${colors.reset}`);
|
|
491
491
|
loaded = true;
|
|
492
492
|
break;
|
|
493
493
|
}
|
|
@@ -505,63 +505,63 @@ export class WinterREPL {
|
|
|
505
505
|
}
|
|
506
506
|
}
|
|
507
507
|
|
|
508
|
-
//
|
|
508
|
+
// Nạp các file quy tắc dự án theo thứ tự ưu tiên.
|
|
509
509
|
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
510
510
|
try {
|
|
511
511
|
if (projectInstructionFiles.length > 0) {
|
|
512
512
|
for (const file of projectInstructionFiles) {
|
|
513
|
-
const memoryKey = `[Quy
|
|
513
|
+
const memoryKey = `[Quy tắc dự án từ ${file.relativePath}]`;
|
|
514
514
|
await this.session.replaceMemory(memoryKey, file.content);
|
|
515
|
-
console.log(`${colors.dim}
|
|
515
|
+
console.log(`${colors.dim}✓ Đã nạp quy tắc dự án từ ${file.relativePath}${colors.reset}`);
|
|
516
516
|
}
|
|
517
517
|
}
|
|
518
518
|
} catch (e) {
|
|
519
|
-
//
|
|
519
|
+
// Nếu không có, tự động tạo file mẫu.
|
|
520
520
|
const template = `# Winter Project Rules
|
|
521
521
|
|
|
522
|
-
##
|
|
523
|
-
- **Name**: [
|
|
524
|
-
- **Description**: [
|
|
522
|
+
## Project Overview
|
|
523
|
+
- **Name**: [Tên dự án]
|
|
524
|
+
- **Description**: [Mô tả ngắn về dự án]
|
|
525
525
|
|
|
526
|
-
##
|
|
526
|
+
## Tech Stack
|
|
527
527
|
- **Languages**: JavaScript / TypeScript
|
|
528
528
|
- **Runtime**: Node.js
|
|
529
|
-
- **Frameworks**: [
|
|
529
|
+
- **Frameworks**: [Tự điền nếu có, VD: Express, React...]
|
|
530
530
|
|
|
531
|
-
##
|
|
531
|
+
## AI Behavior & Coding Guidelines
|
|
532
532
|
|
|
533
|
-
### 1.
|
|
534
|
-
-
|
|
535
|
-
-
|
|
536
|
-
-
|
|
533
|
+
### 1. Nguyên tắc Code (Coding Standards)
|
|
534
|
+
- Luôn ưu tiên viết code sạch (Clean Code), dễ đọc và dễ bảo trì.
|
|
535
|
+
- Sử dụng ES Modules (\`import/export\`) thay vì CommonJS (\`require\`) trừ khi có lý do đặc biệt.
|
|
536
|
+
- Giữ nguyên các comment và JSDoc hiện có trong file trừ khi được yêu cầu sửa.
|
|
537
537
|
|
|
538
|
-
### 2.
|
|
539
|
-
-
|
|
540
|
-
- Khi
|
|
541
|
-
-
|
|
538
|
+
### 2. Tương tác với người dùng (User Interaction)
|
|
539
|
+
- Luôn giải thích ngắn gọn lý do thực hiện thay đổi trước khi sửa file.
|
|
540
|
+
- Khi gặp lỗi, hãy đề xuất giải pháp thay vì chỉ báo lỗi.
|
|
541
|
+
- Không tự tiện xóa code cũ của user trừ khi chắc chắn không còn dùng hoặc được yêu cầu.
|
|
542
542
|
|
|
543
543
|
### 3. Git & Commits
|
|
544
|
-
-
|
|
545
|
-
-
|
|
544
|
+
- Viết commit message theo chuẩn Conventional Commits (VD: \`feat:\`, \`fix:\`, \`docs:\`).
|
|
545
|
+
- Luôn kiểm tra \`git status\` trước khi thực hiện thay đổi lớn.
|
|
546
546
|
|
|
547
|
-
### 4.
|
|
548
|
-
-
|
|
549
|
-
-
|
|
547
|
+
### 4. Xử lý File (File Operations)
|
|
548
|
+
- Chỉ sửa những dòng cần thiết, tránh viết lại toàn bộ file nếu không cần.
|
|
549
|
+
- Luôn đảm bảo file không bị lỗi cú pháp sau khi sửa.
|
|
550
550
|
`;
|
|
551
551
|
try {
|
|
552
552
|
const projectWinterMd = path.join(this.projectPath, 'winter.md');
|
|
553
553
|
await fsPromises.writeFile(projectWinterMd, template, 'utf8');
|
|
554
|
-
console.log(`\n${colors.green}
|
|
555
|
-
console.log(`${colors.dim}
|
|
554
|
+
console.log(`\n${colors.green}✓ Đã tự động tạo file winter.md mẫu cho dự án mới!${colors.reset}`);
|
|
555
|
+
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`);
|
|
556
556
|
|
|
557
|
-
//
|
|
558
|
-
await this.session.replaceMemory(`[Quy
|
|
557
|
+
// Nạp luôn vào memory.
|
|
558
|
+
await this.session.replaceMemory(`[Quy tắc dự án từ winter.md]`, template);
|
|
559
559
|
} catch (err) {
|
|
560
|
-
//
|
|
560
|
+
// Bỏ qua nếu không ghi được file.
|
|
561
561
|
}
|
|
562
562
|
}
|
|
563
563
|
|
|
564
|
-
//
|
|
564
|
+
// Tự động tạo design.md, skill.md, rule.md nếu chưa có.
|
|
565
565
|
const autoCreateDocs = await buildProjectDocs({
|
|
566
566
|
projectPath: this.projectPath,
|
|
567
567
|
resourcePaths: this.getResourcePaths(),
|
|
@@ -577,17 +577,17 @@ export class WinterREPL {
|
|
|
577
577
|
if (!isWinterGeneratedProjectDoc(existing)) continue;
|
|
578
578
|
|
|
579
579
|
await fsPromises.writeFile(filePath, doc.content, 'utf8');
|
|
580
|
-
console.log(`${colors.green}
|
|
581
|
-
const memoryKey = `[Quy
|
|
580
|
+
console.log(`${colors.green}✓ Đã nâng cấp file ${doc.filename} từ local resources.${colors.reset}`);
|
|
581
|
+
const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
|
|
582
582
|
await this.session.replaceMemory(memoryKey, doc.content);
|
|
583
583
|
} catch {
|
|
584
584
|
try {
|
|
585
585
|
await fsPromises.writeFile(filePath, doc.content, 'utf8');
|
|
586
|
-
console.log(`${colors.green}
|
|
587
|
-
const memoryKey = `[Quy
|
|
586
|
+
console.log(`${colors.green}✓ Đã tự động tạo file ${doc.filename} từ local resources!${colors.reset}`);
|
|
587
|
+
const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
|
|
588
588
|
await this.session.replaceMemory(memoryKey, doc.content);
|
|
589
589
|
} catch (err) {
|
|
590
|
-
//
|
|
590
|
+
// Bỏ qua nếu không tạo được.
|
|
591
591
|
}
|
|
592
592
|
}
|
|
593
593
|
}
|
|
@@ -619,13 +619,13 @@ export class WinterREPL {
|
|
|
619
619
|
this.showStatus();
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
//
|
|
622
|
+
// Hiển thị lịch sử chat nếu đang load lại session cũ, nhưng chỉ replay bản tóm tắt.
|
|
623
623
|
const sessionHistory = this.session.getHistory(4);
|
|
624
624
|
if (sessionHistory.length > 0) {
|
|
625
625
|
const columns = process.stdout.columns || 80;
|
|
626
626
|
const W = Math.max(60, Math.min(Math.floor(columns * 0.95), 100));
|
|
627
|
-
const titleStr = '
|
|
628
|
-
const ruleChar = this.useUnicodeUi ? '
|
|
627
|
+
const titleStr = ' Lịch sử phiên làm việc ';
|
|
628
|
+
const ruleChar = this.useUnicodeUi ? '─' : '-';
|
|
629
629
|
const sideLine = ruleChar.repeat(Math.max(0, Math.floor((W - titleStr.length) / 2)));
|
|
630
630
|
const bottomLine = ruleChar.repeat(W);
|
|
631
631
|
|
|
@@ -633,7 +633,7 @@ export class WinterREPL {
|
|
|
633
633
|
for (const msg of sessionHistory) {
|
|
634
634
|
const text = this.formatStartupHistoryEntry(msg.content);
|
|
635
635
|
if (msg.role === 'user') {
|
|
636
|
-
console.log(`\n${colors.cyan}
|
|
636
|
+
console.log(`\n${colors.cyan}Bạn:${colors.reset} ${text}`);
|
|
637
637
|
} else if (msg.role === 'assistant') {
|
|
638
638
|
console.log(`\n${colors.bright}${colors.magenta}Winter:${colors.reset} ${text}`);
|
|
639
639
|
}
|
|
@@ -645,12 +645,12 @@ export class WinterREPL {
|
|
|
645
645
|
this.rl = readline.createInterface({
|
|
646
646
|
input: process.stdin,
|
|
647
647
|
output: process.stdout,
|
|
648
|
-
prompt: `${colors.bright}${colors.cyan}
|
|
648
|
+
prompt: `${colors.bright}${colors.cyan}winter > ${colors.reset}`,
|
|
649
649
|
completer: this.completer.bind(this),
|
|
650
650
|
});
|
|
651
651
|
this.inputController.installSlashSuggestions();
|
|
652
652
|
|
|
653
|
-
//
|
|
653
|
+
// Bắt sự kiện Ctrl+C để in ra lệnh tiếp tục session.
|
|
654
654
|
this.rl.on('SIGINT', () => {
|
|
655
655
|
console.log(`\n\n${colors.cyan}Cảm ơn đã sử dụng Winter!${colors.reset}`);
|
|
656
656
|
console.log(`${colors.yellow}Tiếp tục phiên làm việc:${colors.reset}`);
|
|
@@ -658,7 +658,7 @@ export class WinterREPL {
|
|
|
658
658
|
process.exit(0);
|
|
659
659
|
});
|
|
660
660
|
|
|
661
|
-
//
|
|
661
|
+
// Hiển thị prompt lần đầu tiên ngay khi khởi động xong.
|
|
662
662
|
this.showInputPrompt();
|
|
663
663
|
|
|
664
664
|
this.rl.on('line', (line) => {
|
|
@@ -695,7 +695,7 @@ export class WinterREPL {
|
|
|
695
695
|
if (!this.useUnicodeUi) {
|
|
696
696
|
text = text
|
|
697
697
|
.replace(/\p{Extended_Pictographic}/gu, '')
|
|
698
|
-
.replace(/[
|
|
698
|
+
.replace(/[─━—–]/g, '-')
|
|
699
699
|
.replace(/\s+/g, ' ')
|
|
700
700
|
.trim();
|
|
701
701
|
}
|
|
@@ -917,7 +917,7 @@ export class WinterREPL {
|
|
|
917
917
|
async handleInput(input) {
|
|
918
918
|
if (this.isProcessing) {
|
|
919
919
|
const pos = this.taskQueue.length + 1;
|
|
920
|
-
console.log(`${colors.magenta}
|
|
920
|
+
console.log(`${colors.magenta}•${colors.reset} ${colors.dim}Đã xếp hàng chờ (vị trí #${pos})${colors.reset}`);
|
|
921
921
|
this.taskQueue.push(input);
|
|
922
922
|
return;
|
|
923
923
|
}
|
|
@@ -998,9 +998,9 @@ export class WinterREPL {
|
|
|
998
998
|
}
|
|
999
999
|
} catch (error) {
|
|
1000
1000
|
if (error.message === 'AbortError') {
|
|
1001
|
-
console.log(colors.red + '\
|
|
1001
|
+
console.log(colors.red + '\nĐã hủy công việc hiện tại.' + colors.reset);
|
|
1002
1002
|
} else {
|
|
1003
|
-
console.log(colors.red + '\
|
|
1003
|
+
console.log(colors.red + '\nLỗi: ' + error.message + colors.reset);
|
|
1004
1004
|
}
|
|
1005
1005
|
} finally {
|
|
1006
1006
|
this.isProcessing = false;
|
|
@@ -1063,13 +1063,13 @@ export class WinterREPL {
|
|
|
1063
1063
|
|
|
1064
1064
|
let printedLines = 0;
|
|
1065
1065
|
const render = () => {
|
|
1066
|
-
//
|
|
1066
|
+
// Xóa những dòng đã in trước đó.
|
|
1067
1067
|
if (printedLines > 0) {
|
|
1068
1068
|
process.stdout.write('\x1b[' + printedLines + 'A\x1b[J');
|
|
1069
1069
|
}
|
|
1070
1070
|
|
|
1071
1071
|
let out = `\n[36m${title}[0m\n`;
|
|
1072
|
-
out += `[2mD
|
|
1072
|
+
out += `[2mDùng mũi tên (↑/↓) để di chuyển, [Space] để chọn/bỏ chọn, [Enter] để xác nhận[0m\n\n`;
|
|
1073
1073
|
|
|
1074
1074
|
for (let i = 0; i < items.length; i++) {
|
|
1075
1075
|
const isHover = i === cursor;
|
|
@@ -1119,11 +1119,11 @@ export class WinterREPL {
|
|
|
1119
1119
|
}
|
|
1120
1120
|
|
|
1121
1121
|
async generateInteractivePlan(task) {
|
|
1122
|
-
this.spinner = new Spinner('
|
|
1122
|
+
this.spinner = new Spinner('Đang phân tích và chia nhỏ yêu cầu...');
|
|
1123
1123
|
this.spinner.start();
|
|
1124
1124
|
|
|
1125
1125
|
const messages = [
|
|
1126
|
-
{ role: 'system', content: '
|
|
1126
|
+
{ role: 'system', content: 'Bạn là chuyên gia lập kế hoạch. Hãy chia nhỏ yêu cầu của người dùng thành các bước cụ thể, hành động được, rất ngắn gọn (dưới 15 chữ mỗi bước). Chỉ trả về một mảng JSON các chuỗi, không giải thích gì thêm. Ví dụ: ["Tạo file index.html", "Thêm CSS styling", "Viết script.js"]' },
|
|
1127
1127
|
{ role: 'user', content: task }
|
|
1128
1128
|
];
|
|
1129
1129
|
|
|
@@ -1146,40 +1146,40 @@ export class WinterREPL {
|
|
|
1146
1146
|
}
|
|
1147
1147
|
|
|
1148
1148
|
if (!Array.isArray(items) || items.length === 0) {
|
|
1149
|
-
console.log(`\x1b[33mKh
|
|
1149
|
+
console.log(`\x1b[33mKhông thể parse kế hoạch. AI phản hồi: ${text}\x1b[0m`);
|
|
1150
1150
|
return;
|
|
1151
1151
|
}
|
|
1152
1152
|
|
|
1153
|
-
const selectedSteps = await this.showInteractiveChecklist('
|
|
1153
|
+
const selectedSteps = await this.showInteractiveChecklist('KẾ HOẠCH THỰC HIỆN:', items);
|
|
1154
1154
|
|
|
1155
1155
|
if (selectedSteps.length > 0) {
|
|
1156
1156
|
for (const step of selectedSteps) {
|
|
1157
1157
|
await this.session.createPlan(step, task);
|
|
1158
1158
|
}
|
|
1159
|
-
console.log(`\x1b[32m
|
|
1159
|
+
console.log(`\x1b[32m✓ Đã thêm ${selectedSteps.length} công việc vào Memory (gõ /plans để xem).\x1b[0m`);
|
|
1160
1160
|
|
|
1161
|
-
this.rl.question(`\n\x1b[36mB
|
|
1161
|
+
this.rl.question(`\n\x1b[36mBạn có muốn AI bắt tay làm công việc ĐẦU TIÊN ngay bây giờ không? [y/N]: \x1b[0m`, async (answer) => {
|
|
1162
1162
|
if (answer.toLowerCase() === 'y') {
|
|
1163
|
-
await this.chat(`
|
|
1163
|
+
await this.chat(`Hãy bắt đầu thực hiện bước đầu tiên: ${selectedSteps[0]}`);
|
|
1164
1164
|
}
|
|
1165
1165
|
});
|
|
1166
1166
|
} else {
|
|
1167
|
-
console.log(`\n\x1b[2m
|
|
1167
|
+
console.log(`\n\x1b[2mĐã huỷ kế hoạch.\x1b[0m`);
|
|
1168
1168
|
}
|
|
1169
1169
|
} catch (e) {
|
|
1170
1170
|
if (this.spinner) this.spinner.stop();
|
|
1171
|
-
console.log(`\x1b[31mL
|
|
1171
|
+
console.log(`\x1b[31mLỗi: ${e.message}\x1b[0m`);
|
|
1172
1172
|
}
|
|
1173
1173
|
}
|
|
1174
1174
|
|
|
1175
1175
|
|
|
1176
1176
|
async runAutoHealing(task) {
|
|
1177
|
-
console.log(`\n\x1b[35m[ TDD AUTO-HEALING MODE ]\x1b[0m
|
|
1177
|
+
console.log(`\n\x1b[35m[ TDD AUTO-HEALING MODE ]\x1b[0m Kích hoạt vòng lặp tự sửa lỗi.`);
|
|
1178
1178
|
|
|
1179
|
-
//
|
|
1179
|
+
// Tăng giới hạn loop lên 15 để cho phép AI tự sửa lỗi nhiều lần.
|
|
1180
1180
|
const originalRequestAssistantTurn = this.requestAssistantTurn;
|
|
1181
1181
|
|
|
1182
|
-
//
|
|
1182
|
+
// Chèn prompt đặc biệt ép AI phải verify.
|
|
1183
1183
|
const verifyCommands = await this.inferVerificationCommands(task);
|
|
1184
1184
|
const autoPrompt = `TASK: ${task}
|
|
1185
1185
|
|
|
@@ -1195,7 +1195,7 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1195
1195
|
}
|
|
1196
1196
|
|
|
1197
1197
|
async runAutoCommit(context = '') {
|
|
1198
|
-
console.log(`\n${colors.cyan}
|
|
1198
|
+
console.log(`\n${colors.cyan}✓ Tạo commit message tự động...${colors.reset}`);
|
|
1199
1199
|
const diffResult = await this.tools.execute('Bash', { command: 'git diff --cached' }, { cwd: this.projectPath });
|
|
1200
1200
|
|
|
1201
1201
|
let diff = diffResult.stdout;
|
|
@@ -1207,14 +1207,14 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1207
1207
|
}
|
|
1208
1208
|
|
|
1209
1209
|
if (!diff || diff.trim() === '') {
|
|
1210
|
-
console.log(`${colors.yellow}
|
|
1210
|
+
console.log(`${colors.yellow}Không có thay đổi nào để commit.${colors.reset}`);
|
|
1211
1211
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1212
1212
|
return;
|
|
1213
1213
|
}
|
|
1214
1214
|
|
|
1215
|
-
const prompt = `
|
|
1215
|
+
const prompt = `Bạn là chuyên gia Git. Hãy viết MỘT commit message duy nhất (không giải thích thêm) cho những thay đổi sau đây. Dùng format chuẩn Conventional Commits (feat/fix/chore/refactor: ...). ${context ? `\nNgữ cảnh: ${context}` : ''}\n\nDiff:\n${diff.slice(0, 6000)}`;
|
|
1216
1216
|
|
|
1217
|
-
this.spinner = new Spinner('
|
|
1217
|
+
this.spinner = new Spinner('Đang sinh commit message...');
|
|
1218
1218
|
this.spinner.start();
|
|
1219
1219
|
try {
|
|
1220
1220
|
const response = await this.ai.sendRequest([{ role: 'user', content: prompt }], {
|
|
@@ -1222,19 +1222,19 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1222
1222
|
});
|
|
1223
1223
|
if (this.spinner) this.spinner.stop();
|
|
1224
1224
|
|
|
1225
|
-
const message = response.choices?.[0]?.message?.content?.trim().replace(/^["']|["']$/g, '') || '
|
|
1225
|
+
const message = response.choices?.[0]?.message?.content?.trim().replace(/^["']|["']$/g, '') || 'Cập nhật mã nguồn';
|
|
1226
1226
|
|
|
1227
|
-
console.log(`\n${colors.green}
|
|
1227
|
+
console.log(`\n${colors.green}Đề xuất commit message:${colors.reset}`);
|
|
1228
1228
|
console.log(`${colors.bright}${message}${colors.reset}\n`);
|
|
1229
1229
|
|
|
1230
|
-
this.rl.question(`${colors.yellow}
|
|
1230
|
+
this.rl.question(`${colors.yellow}Bạn có muốn commit với message này không? [y/N/e (tự sửa)]: ${colors.reset}`, async (ans) => {
|
|
1231
1231
|
const choice = ans.trim().toLowerCase();
|
|
1232
1232
|
if (choice === 'y') {
|
|
1233
1233
|
if (!isStaged) await this.tools.execute('Bash', { command: 'git add .' }, { cwd: this.projectPath });
|
|
1234
1234
|
const res = await this.tools.execute('Bash', { command: `git commit -m "${message.replace(/"/g, '\\"')}"` }, { cwd: this.projectPath });
|
|
1235
1235
|
console.log(res.stdout);
|
|
1236
1236
|
} else if (choice === 'e') {
|
|
1237
|
-
this.rl.question(`${colors.cyan}
|
|
1237
|
+
this.rl.question(`${colors.cyan}Nhập commit message: ${colors.reset}`, async (customMsg) => {
|
|
1238
1238
|
if (customMsg.trim()) {
|
|
1239
1239
|
if (!isStaged) await this.tools.execute('Bash', { command: 'git add .' }, { cwd: this.projectPath });
|
|
1240
1240
|
const res = await this.tools.execute('Bash', { command: `git commit -m "${customMsg.replace(/"/g, '\\"')}"` }, { cwd: this.projectPath });
|
|
@@ -1244,28 +1244,28 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1244
1244
|
});
|
|
1245
1245
|
return;
|
|
1246
1246
|
} else {
|
|
1247
|
-
console.log(`${colors.dim}
|
|
1247
|
+
console.log(`${colors.dim}Đã huỷ commit.${colors.reset}`);
|
|
1248
1248
|
}
|
|
1249
1249
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1250
1250
|
});
|
|
1251
1251
|
} catch (e) {
|
|
1252
1252
|
if (this.spinner) this.spinner.stop();
|
|
1253
|
-
console.log(`${colors.red}
|
|
1253
|
+
console.log(`${colors.red}Lỗi: ${e.message}${colors.reset}`);
|
|
1254
1254
|
if (this.running && !this.readlineClosed) this.rl.prompt();
|
|
1255
1255
|
}
|
|
1256
1256
|
}
|
|
1257
1257
|
|
|
1258
1258
|
async runCodeReview(context = '') {
|
|
1259
|
-
console.log(`\n${colors.cyan}
|
|
1259
|
+
console.log(`\n${colors.cyan}AI đang soi code của bạn...${colors.reset}`);
|
|
1260
1260
|
const diffResult = await this.tools.execute('Bash', { command: 'git diff HEAD' }, { cwd: this.projectPath });
|
|
1261
1261
|
const diff = diffResult.stdout;
|
|
1262
1262
|
|
|
1263
1263
|
if (!diff || diff.trim() === '') {
|
|
1264
|
-
console.log(`${colors.yellow}
|
|
1264
|
+
console.log(`${colors.yellow}Không có thay đổi nào để review.${colors.reset}`);
|
|
1265
1265
|
return;
|
|
1266
1266
|
}
|
|
1267
1267
|
|
|
1268
|
-
const prompt = `
|
|
1268
|
+
const prompt = `Hãy đóng vai một Senior Developer khó tính. Review nhanh các thay đổi sau đây. Chỉ ra bug nếu có, vấn đề bảo mật, hoặc những chỗ cần clean code. Không cần khen ngợi, hãy nói thẳng vào vấn đề. Trình bày dạng Markdown đẹp mắt.\n\n${context ? `Ngữ cảnh: ${context}\n` : ''}Diff:\n${diff.slice(0, 8000)}`;
|
|
1269
1269
|
|
|
1270
1270
|
await this.chat(prompt);
|
|
1271
1271
|
}
|
|
@@ -1286,39 +1286,39 @@ CRITICAL DEBUG/AGENT RULES:
|
|
|
1286
1286
|
};
|
|
1287
1287
|
|
|
1288
1288
|
const body = [
|
|
1289
|
-
`${c.bright}${c.cyan}${this.useUnicodeUi ? '
|
|
1289
|
+
`${c.bright}${c.cyan}${this.useUnicodeUi ? '❄ ' : ''}WINTER COMMANDS${c.reset}`,
|
|
1290
1290
|
`${c.dim}@file context | @Agent task | !cmd bash | /theme:toggle${c.reset}`,
|
|
1291
1291
|
'',
|
|
1292
|
-
`${c.bright}
|
|
1293
|
-
row(`${c.yellow}/pwd${c.reset}
|
|
1294
|
-
row(`${c.yellow}/cd${c.reset}
|
|
1295
|
-
row(`${c.yellow}/config${c.reset} Xem
|
|
1292
|
+
`${c.bright}Dự án & Phiên làm việc${c.reset}`,
|
|
1293
|
+
row(`${c.yellow}/pwd${c.reset} Thư mục hiện tại`, `${c.yellow}/session${c.reset} Phiên làm việc`),
|
|
1294
|
+
row(`${c.yellow}/cd${c.reset} Đổi thư mục`, `${c.yellow}/clear${c.reset} Xóa màn hình`),
|
|
1295
|
+
row(`${c.yellow}/config${c.reset} Xem cấu hình`, `${c.yellow}/exit${c.reset} Thoát`),
|
|
1296
1296
|
'',
|
|
1297
|
-
`${c.bright}AI &
|
|
1298
|
-
row(`${c.yellow}/auto${c.reset} TDD
|
|
1299
|
-
row(`${c.yellow}/doctor${c.reset}
|
|
1300
|
-
row(`${c.yellow}/swe${c.reset} SWE workflow`, `${c.yellow}/plan${c.reset}
|
|
1301
|
-
row(`${c.yellow}/read${c.reset}
|
|
1302
|
-
row(`${c.yellow}/bash${c.reset}
|
|
1303
|
-
row(`${c.yellow}/glob${c.reset}
|
|
1304
|
-
row(`${c.yellow}/paste${c.reset}
|
|
1305
|
-
row(`${c.yellow}/complete${c.reset}
|
|
1306
|
-
row(`${c.yellow}/browse${c.reset}
|
|
1307
|
-
row(`${c.yellow}/ensemble${c.reset}
|
|
1308
|
-
row(`${c.yellow}/orchestrate${c.reset} Pipeline
|
|
1297
|
+
`${c.bright}AI & Công cụ${c.reset}`,
|
|
1298
|
+
row(`${c.yellow}/auto${c.reset} TDD tự sửa lỗi`, `${c.yellow}/debug${c.reset} Auto debug lỗi`),
|
|
1299
|
+
row(`${c.yellow}/doctor${c.reset} Kiểm tra tool-call`, `${c.yellow}/agent${c.reset} Chạy sub-agent`),
|
|
1300
|
+
row(`${c.yellow}/swe${c.reset} SWE workflow`, `${c.yellow}/plan${c.reset} Lập kế hoạch`),
|
|
1301
|
+
row(`${c.yellow}/read${c.reset} Đọc file`, `${c.yellow}/write${c.reset} Ghi file`),
|
|
1302
|
+
row(`${c.yellow}/bash${c.reset} Chạy lệnh terminal`, `${c.yellow}/grep${c.reset} Tìm trong file`),
|
|
1303
|
+
row(`${c.yellow}/glob${c.reset} Tìm file theo pattern`, `${c.yellow}/image${c.reset} Ảnh/file/clipboard`),
|
|
1304
|
+
row(`${c.yellow}/paste${c.reset} Dán text/ảnh clipboard`, `${c.yellow}/composer${c.reset} Multi-file edit`),
|
|
1305
|
+
row(`${c.yellow}/complete${c.reset} Gợi ý code`, `${c.yellow}/search${c.reset} Tìm kiếm code`),
|
|
1306
|
+
row(`${c.yellow}/browse${c.reset} Mở URL trong trình duyệt`, `${c.yellow}/page-agent${c.reset} GUI Agent resources`),
|
|
1307
|
+
row(`${c.yellow}/ensemble${c.reset} Chạy nhiều AI`, `${c.yellow}/vote${c.reset} Bình chọn hay nhất`),
|
|
1308
|
+
row(`${c.yellow}/orchestrate${c.reset} Pipeline đa model`, `${c.yellow}/undo${c.reset} Undo backup`),
|
|
1309
1309
|
row(`${c.yellow}/ecc${c.reset} ECC resource browser`, `${c.yellow}/codex${c.reset} Codex resources`),
|
|
1310
1310
|
'',
|
|
1311
1311
|
`${c.bright}Git Auto-Pilot${c.reset}`,
|
|
1312
|
-
row(`${c.yellow}/commit${c.reset} AI
|
|
1312
|
+
row(`${c.yellow}/commit${c.reset} AI tự viết commit`, `${c.yellow}/review${c.reset} AI review code thay đổi`),
|
|
1313
1313
|
'',
|
|
1314
|
-
`${c.bright}
|
|
1315
|
-
row(`${c.yellow}/provider${c.reset}
|
|
1316
|
-
row(`${c.yellow}/providers${c.reset} Danh
|
|
1317
|
-
row(`${c.yellow}/mcp${c.reset} MCP server mgmt`, `${c.yellow}/permissions${c.reset}
|
|
1314
|
+
`${c.bright}Cấu hình Model${c.reset}`,
|
|
1315
|
+
row(`${c.yellow}/provider${c.reset} Đổi provider AI`, `${c.yellow}/model${c.reset} Đổi model`),
|
|
1316
|
+
row(`${c.yellow}/providers${c.reset} Danh sách provider`, `${c.yellow}/models${c.reset} Danh sách model`),
|
|
1317
|
+
row(`${c.yellow}/mcp${c.reset} MCP server mgmt`, `${c.yellow}/permissions${c.reset} Quyền/allowlist`),
|
|
1318
1318
|
'',
|
|
1319
|
-
`${c.bright}
|
|
1320
|
-
row(`${c.yellow}/remember${c.reset}
|
|
1321
|
-
row(`${c.yellow}/skills${c.reset} Danh
|
|
1319
|
+
`${c.bright}Bộ nhớ & Kỹ năng${c.reset}`,
|
|
1320
|
+
row(`${c.yellow}/remember${c.reset} Lưu vào bộ nhớ`, `${c.yellow}/memories${c.reset} Xem bộ nhớ`),
|
|
1321
|
+
row(`${c.yellow}/skills${c.reset} Danh sách kỹ năng`, `${c.yellow}/designs${c.reset} Hệ thống thiết kế`),
|
|
1322
1322
|
];
|
|
1323
1323
|
|
|
1324
1324
|
console.log(`
|
|
@@ -1329,14 +1329,14 @@ ${renderBox({
|
|
|
1329
1329
|
titleColor: c.cyan,
|
|
1330
1330
|
body,
|
|
1331
1331
|
})}
|
|
1332
|
-
${c.dim}
|
|
1332
|
+
${c.dim}Gửi tin nhắn trực tiếp để chat, ESC để hủy${c.reset}
|
|
1333
1333
|
`);
|
|
1334
1334
|
}
|
|
1335
1335
|
|
|
1336
1336
|
showHelp() {
|
|
1337
1337
|
console.log(`
|
|
1338
|
-
${colors.cyan}? WINTER COMMANDS${colors.reset}
|
|
1339
|
-
${colors.dim}${''.padEnd(50,
|
|
1338
|
+
${colors.cyan}${this.useUnicodeUi ? '❄ ' : ''}WINTER COMMANDS${colors.reset}
|
|
1339
|
+
${colors.dim}${''.padEnd(50, this.useUnicodeUi ? '─' : '-')}${colors.reset}
|
|
1340
1340
|
|
|
1341
1341
|
${colors.white}Project:${colors.reset}
|
|
1342
1342
|
/project, /pwd Show current project
|
|
@@ -1417,7 +1417,7 @@ ${colors.reset}
|
|
|
1417
1417
|
}
|
|
1418
1418
|
console.log(`${colors.cyan}Design Systems:${colors.reset}`);
|
|
1419
1419
|
filtered.forEach(e => {
|
|
1420
|
-
const icon = e.isDirectory ? '
|
|
1420
|
+
const icon = e.isDirectory ? '[dir]' : '[file]';
|
|
1421
1421
|
console.log(` ${icon} ${e.name}`);
|
|
1422
1422
|
});
|
|
1423
1423
|
} catch (error) {
|
|
@@ -1437,7 +1437,7 @@ ${colors.reset}
|
|
|
1437
1437
|
const entries = await this.listPathEntries(section.path, 50);
|
|
1438
1438
|
if (entries.length > 0) {
|
|
1439
1439
|
console.log(`${colors.cyan}${section.label}:${colors.reset}`);
|
|
1440
|
-
entries.forEach(e => console.log(` ${e.isDirectory ? '
|
|
1440
|
+
entries.forEach(e => console.log(` ${e.isDirectory ? '[dir]' : '[file]'} ${e.name}`));
|
|
1441
1441
|
}
|
|
1442
1442
|
} catch { }
|
|
1443
1443
|
}
|
|
@@ -1451,8 +1451,8 @@ ${colors.reset}
|
|
|
1451
1451
|
const providers = this.ai.listProviders();
|
|
1452
1452
|
console.log(`${colors.cyan}Configured Models:${colors.reset}`);
|
|
1453
1453
|
providers.forEach(p => {
|
|
1454
|
-
const active = p.name === this.ai.getActiveProvider() ? ` ${colors.green}
|
|
1455
|
-
const status = p.ready ? `${colors.green}
|
|
1454
|
+
const active = p.name === this.ai.getActiveProvider() ? ` ${colors.green}< active${colors.reset}` : '';
|
|
1455
|
+
const status = p.ready ? `${colors.green}ok${colors.reset}` : `${colors.red}off${colors.reset}`;
|
|
1456
1456
|
console.log(` ${status} ${colors.bright}${p.name}${colors.reset}: ${p.model}${active}`);
|
|
1457
1457
|
});
|
|
1458
1458
|
|
|
@@ -1506,7 +1506,7 @@ ${colors.reset}
|
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
1508
|
shouldUseCompactPrompt() {
|
|
1509
|
-
return
|
|
1509
|
+
return false;
|
|
1510
1510
|
}
|
|
1511
1511
|
|
|
1512
1512
|
selectExecutionProfile(messages = [], options = {}) {
|
|
@@ -1573,9 +1573,9 @@ ${colors.reset}
|
|
|
1573
1573
|
const text = this.getLatestUserText(messages).toLowerCase();
|
|
1574
1574
|
if (!text.trim()) return false;
|
|
1575
1575
|
|
|
1576
|
-
const actionPattern = /\b(fix|repair|bug|debug|implement|create|write|edit|modify|update|delete|remove|refactor|run|test|build|commit|push|publish|install|check|inspect|read|scan|grep|search|change|apply|patch|sua|lam|tao|ghi|doc|xoa|chay|kiem tra|cai|them|doi|review|tim)\b/i;
|
|
1577
|
-
const targetPattern = /\b(file|repo|project|code|src|test|build|git|npm|node|folder|directory|cli|tool|provider|model|config|readme|package\.json|du an|thu muc|tap tin|loi|chuc nang)\b|[A-Za-z]:[\\/]|\.js\b|\.ts\b|\.tsx\b|\.json\b|\.md\b/i;
|
|
1578
|
-
const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|tai sao|vi sao|la gi|co nen|co phai)\b/i;
|
|
1576
|
+
const actionPattern = /\b(fix|repair|bug|debug|implement|create|write|edit|modify|update|delete|remove|refactor|run|test|build|commit|push|publish|install|check|inspect|read|scan|grep|search|change|apply|patch|sua|lam|tao|ghi|doc|xoa|chay|kiem tra|cai|them|doi|review|tim|sửa|làm|tạo|đọc|xóa|xoá|chạy|kiểm tra|cài|thêm|đổi|tìm)\b/i;
|
|
1577
|
+
const targetPattern = /\b(file|repo|project|code|src|test|build|git|npm|node|folder|directory|cli|tool|provider|model|config|readme|package\.json|du an|thu muc|tap tin|loi|chuc nang|dự án|thư mục|tập tin|lỗi|chức năng)\b|[A-Za-z]:[\\/]|\.js\b|\.ts\b|\.tsx\b|\.json\b|\.md\b/i;
|
|
1578
|
+
const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|tai sao|vi sao|la gi|co nen|co phai|tại sao|vì sao|là gì|có nên|có phải)\b/i;
|
|
1579
1579
|
|
|
1580
1580
|
if (pureQuestionPattern.test(text) && !actionPattern.test(text)) return false;
|
|
1581
1581
|
return actionPattern.test(text) && targetPattern.test(text);
|
|
@@ -1585,17 +1585,17 @@ ${colors.reset}
|
|
|
1585
1585
|
if (!usedMutatingTools) return false;
|
|
1586
1586
|
const text = String(originalMessage || '').toLowerCase();
|
|
1587
1587
|
if (!text.trim()) return false;
|
|
1588
|
-
if (/\b(skip tests?|no verify|don't verify|khong test|khong verify|bo qua test)\b/i.test(text)) {
|
|
1588
|
+
if (/\b(skip tests?|no verify|don't verify|khong test|khong verify|bo qua test|không test|không verify|bỏ qua test)\b/i.test(text)) {
|
|
1589
1589
|
return false;
|
|
1590
1590
|
}
|
|
1591
|
-
return /\b(fix|bug|error|test|build|lint|typecheck|compile|refactor|implement|edit|write|change|patch|debug|sua|loi|kiem tra|bien dich|trien khai|lam|doi|viet)\b/i.test(text);
|
|
1591
|
+
return /\b(fix|bug|error|test|build|lint|typecheck|compile|refactor|implement|edit|write|change|patch|debug|sua|loi|kiem tra|bien dich|trien khai|lam|doi|viet|sửa|lỗi|kiểm tra|biên dịch|triển khai|làm|đổi|viết)\b/i.test(text);
|
|
1592
1592
|
}
|
|
1593
1593
|
|
|
1594
1594
|
responseNeedsToolEvidence(content = '') {
|
|
1595
1595
|
const text = String(content || '').toLowerCase();
|
|
1596
1596
|
if (!text.trim()) return false;
|
|
1597
1597
|
|
|
1598
|
-
const clarification = /(?:
|
|
1598
|
+
const clarification = /(?:cần thêm|cho mình|vui lòng|please provide|which file|what file|need more|clarify|không rõ|chưa rõ|file nào|thư mục nào|c?n th?m|cho m?nh|vui l?ng|kh?ng r?|ch?a r?|file n?o|th? m?c n?o)/i;
|
|
1599
1599
|
if (clarification.test(text)) return false;
|
|
1600
1600
|
return true;
|
|
1601
1601
|
}
|
|
@@ -1806,7 +1806,7 @@ ${colors.reset}
|
|
|
1806
1806
|
if (key.name === 'escape' && this.isProcessing) {
|
|
1807
1807
|
this.isCancelled = true;
|
|
1808
1808
|
if (this.spinner) this.spinner.stop();
|
|
1809
|
-
console.log(`\n\x1b[31m[
|
|
1809
|
+
console.log(`\n\x1b[31m[ Đã nhận lệnh HỦY... AI sẽ kết thúc ở thao tác tiếp theo ]\x1b[0m`);
|
|
1810
1810
|
return;
|
|
1811
1811
|
}
|
|
1812
1812
|
|
|
@@ -1842,7 +1842,7 @@ ${colors.reset}
|
|
|
1842
1842
|
})
|
|
1843
1843
|
.catch((error) => {
|
|
1844
1844
|
this.closeInputBox();
|
|
1845
|
-
console.log(`\n${colors.red}
|
|
1845
|
+
console.log(`\n${colors.red}✖ Paste image error: ${error.message}${colors.reset}\n`);
|
|
1846
1846
|
if (this.running && !this.readlineClosed) this.showInputPrompt();
|
|
1847
1847
|
});
|
|
1848
1848
|
return true;
|
|
@@ -1956,13 +1956,13 @@ ${colors.reset}
|
|
|
1956
1956
|
|
|
1957
1957
|
if (matches.length > maxDisplay) {
|
|
1958
1958
|
readline.clearLine(process.stdout, 1);
|
|
1959
|
-
process.stdout.write(` ${colors.dim}...
|
|
1959
|
+
process.stdout.write(` ${colors.dim}... và ${matches.length - maxDisplay} lệnh khác (gõ tiếp để lọc)${colors.reset}\n`);
|
|
1960
1960
|
}
|
|
1961
1961
|
|
|
1962
1962
|
readline.clearLine(process.stdout, 1);
|
|
1963
|
-
process.stdout.write(`${colors.dim}
|
|
1963
|
+
process.stdout.write(`${colors.dim}↑/↓ chọn · Enter/Tab dùng · Esc đóng${colors.reset}\n`);
|
|
1964
1964
|
|
|
1965
|
-
//
|
|
1965
|
+
// Xóa các dòng thừa nếu số lượng dòng mới ít hơn số lượng dòng cũ.
|
|
1966
1966
|
const currentLines = Math.min(matches.length, maxDisplay) + 3 + (matches.length > maxDisplay ? 1 : 0);
|
|
1967
1967
|
if (this.slashMenu.printedLines > currentLines) {
|
|
1968
1968
|
for (let i = 0; i < this.slashMenu.printedLines - currentLines; i++) {
|
|
@@ -2174,7 +2174,7 @@ ${colors.reset}
|
|
|
2174
2174
|
body: [...body, '', `${colors.dim}${footer}${colors.reset}`],
|
|
2175
2175
|
})}\n`);
|
|
2176
2176
|
return;
|
|
2177
|
-
console.log(`${colors.dim}${
|
|
2177
|
+
console.log(`${colors.dim}${(this.useUnicodeUi ? '─' : '-').repeat(50)}${colors.reset}`);
|
|
2178
2178
|
console.log(`${colors.dim}${this.formatAnswerFooter(startedAt, usage)}${colors.reset}\n`);
|
|
2179
2179
|
}
|
|
2180
2180
|
|
|
@@ -2223,16 +2223,16 @@ ${colors.reset}
|
|
|
2223
2223
|
}
|
|
2224
2224
|
|
|
2225
2225
|
async promptToolPermission(commandText) {
|
|
2226
|
-
const side = this.useUnicodeUi ? '
|
|
2227
|
-
const warn = this.useUnicodeUi ? '
|
|
2228
|
-
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.yellow}${warn} AI
|
|
2229
|
-
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}1.${colors.reset} Cho
|
|
2230
|
-
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}2.${colors.reset} Cho
|
|
2231
|
-
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}3.${colors.reset}
|
|
2226
|
+
const side = this.useUnicodeUi ? '│' : '|';
|
|
2227
|
+
const warn = this.useUnicodeUi ? '!' : '!';
|
|
2228
|
+
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.yellow}${warn} AI muốn chạy: ${colors.bright}${commandText}${colors.reset}\n`);
|
|
2229
|
+
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}1.${colors.reset} Cho phép\n`);
|
|
2230
|
+
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}2.${colors.reset} Cho phép trong phiên\n`);
|
|
2231
|
+
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.cyan}3.${colors.reset} Không cho phép\n`);
|
|
2232
2232
|
|
|
2233
2233
|
while (true) {
|
|
2234
2234
|
const answer = await new Promise(resolve => {
|
|
2235
|
-
this.rl.question(`${colors.magenta}${side}${colors.reset} ${colors.yellow}
|
|
2235
|
+
this.rl.question(`${colors.magenta}${side}${colors.reset} ${colors.yellow}Chọn [1/2/3]: ${colors.reset}`, resolve);
|
|
2236
2236
|
});
|
|
2237
2237
|
|
|
2238
2238
|
const choice = String(answer || '').trim().toLowerCase();
|
|
@@ -2246,7 +2246,7 @@ ${colors.reset}
|
|
|
2246
2246
|
return false;
|
|
2247
2247
|
}
|
|
2248
2248
|
|
|
2249
|
-
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.dim}Vui
|
|
2249
|
+
process.stdout.write(`${colors.magenta}${side}${colors.reset} ${colors.dim}Vui lòng chọn 1, 2 hoặc 3.${colors.reset}\n`);
|
|
2250
2250
|
}
|
|
2251
2251
|
}
|
|
2252
2252
|
|
|
@@ -2297,7 +2297,7 @@ ${colors.reset}
|
|
|
2297
2297
|
await this.session.replaceMemory('[Conversation Summary]', compressed.summary, 'summary');
|
|
2298
2298
|
|
|
2299
2299
|
if (verbose) {
|
|
2300
|
-
console.log(`${colors.green}
|
|
2300
|
+
console.log(`${colors.green}✓ Compressed ${compressed.omittedCount} old message(s) into session summary.${colors.reset}`);
|
|
2301
2301
|
}
|
|
2302
2302
|
return compressed;
|
|
2303
2303
|
}
|
|
@@ -2371,7 +2371,7 @@ ${colors.reset}
|
|
|
2371
2371
|
this.rl.question(`${colors.yellow}Apply is not automatic here. Continue? [y/N]: ${colors.reset}`, resolve);
|
|
2372
2372
|
});
|
|
2373
2373
|
console.log(/^y(es)?$/i.test(String(answer || '').trim())
|
|
2374
|
-
? `${colors.green}
|
|
2374
|
+
? `${colors.green}✓ Confirmed${colors.reset}`
|
|
2375
2375
|
: `${colors.dim}Cancelled${colors.reset}`);
|
|
2376
2376
|
}
|
|
2377
2377
|
}
|
|
@@ -2380,7 +2380,7 @@ ${colors.reset}
|
|
|
2380
2380
|
const action = args[0];
|
|
2381
2381
|
if (action === 'stop') {
|
|
2382
2382
|
this.stopWatchers();
|
|
2383
|
-
console.log(`${colors.green}
|
|
2383
|
+
console.log(`${colors.green}✓ Watcher stopped${colors.reset}`);
|
|
2384
2384
|
return;
|
|
2385
2385
|
}
|
|
2386
2386
|
|
|
@@ -2405,7 +2405,7 @@ ${colors.reset}
|
|
|
2405
2405
|
run(name);
|
|
2406
2406
|
});
|
|
2407
2407
|
this.watchers.push(watcher);
|
|
2408
|
-
console.log(`${colors.green}
|
|
2408
|
+
console.log(`${colors.green}✓ Watching ${this.projectPath}${colors.reset}`);
|
|
2409
2409
|
console.log(`${colors.dim}Command: ${command}. Use /watch stop to stop.${colors.reset}`);
|
|
2410
2410
|
}
|
|
2411
2411
|
|
|
@@ -2438,7 +2438,7 @@ ${colors.reset}
|
|
|
2438
2438
|
const promptHistory = this.getCompressedPromptHistory({
|
|
2439
2439
|
limit: 20,
|
|
2440
2440
|
keepRecent: 14,
|
|
2441
|
-
maxTotalChars:
|
|
2441
|
+
maxTotalChars: 16000,
|
|
2442
2442
|
});
|
|
2443
2443
|
if (promptHistory.summary) {
|
|
2444
2444
|
messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
|
|
@@ -2468,7 +2468,7 @@ ${colors.reset}
|
|
|
2468
2468
|
await this.session.addToHistory({ role: 'user', content: message });
|
|
2469
2469
|
await this.session.addToHistory({ role: 'assistant', content: finalContent });
|
|
2470
2470
|
|
|
2471
|
-
//
|
|
2471
|
+
// Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build.
|
|
2472
2472
|
if (finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
|
|
2473
2473
|
const sessionContext = this.session?.getContext?.() || {};
|
|
2474
2474
|
const profile = String(sessionContext.workflowProfile || 'general');
|
|
@@ -2479,7 +2479,7 @@ ${colors.reset}
|
|
|
2479
2479
|
}
|
|
2480
2480
|
|
|
2481
2481
|
} catch (error) {
|
|
2482
|
-
console.log(`\n${colors.red}
|
|
2482
|
+
console.log(`\n${colors.red}✖ Error: ${error.message}${colors.reset}\n`);
|
|
2483
2483
|
}
|
|
2484
2484
|
}
|
|
2485
2485
|
|
|
@@ -2541,7 +2541,7 @@ ${colors.reset}
|
|
|
2541
2541
|
}
|
|
2542
2542
|
|
|
2543
2543
|
/**
|
|
2544
|
-
*
|
|
2544
|
+
* Chạy verification commands (test, build) và trả về kết quả.
|
|
2545
2545
|
*/
|
|
2546
2546
|
async inferVerificationCommands(task = '') {
|
|
2547
2547
|
const fs = await import('fs/promises');
|
|
@@ -2551,7 +2551,7 @@ ${colors.reset}
|
|
|
2551
2551
|
const pkg = JSON.parse(await fs.readFile(packagePath, 'utf8'));
|
|
2552
2552
|
const scripts = pkg.scripts || {};
|
|
2553
2553
|
if (scripts.test) candidates.push('npm test');
|
|
2554
|
-
if (scripts.build && /\b(build|compile|type|typescript|tsc|frontend|ui|design|next|vite|react|debug|fix|bug|error|
|
|
2554
|
+
if (scripts.build && /\b(build|compile|type|typescript|tsc|frontend|ui|design|next|vite|react|debug|fix|bug|error|loi|lỗi)\b/i.test(task)) {
|
|
2555
2555
|
candidates.push('npm run build');
|
|
2556
2556
|
}
|
|
2557
2557
|
if (scripts.lint && /\b(lint|style|eslint|quality|review)\b/i.test(task)) candidates.push('npm run lint');
|
|
@@ -2593,10 +2593,10 @@ ${colors.reset}
|
|
|
2593
2593
|
}
|
|
2594
2594
|
|
|
2595
2595
|
/**
|
|
2596
|
-
*
|
|
2597
|
-
* -
|
|
2598
|
-
* -
|
|
2599
|
-
* -
|
|
2596
|
+
* Vòng lặp tự động verify + sửa lỗi:
|
|
2597
|
+
* - Chạy test/build
|
|
2598
|
+
* - Nếu fail, gửi lại cho AI fix
|
|
2599
|
+
* - Lặp đến khi pass hết hoặc hết số lần thử
|
|
2600
2600
|
*/
|
|
2601
2601
|
async verifyAndHeal(messages, tools, maxAttempts = 5) {
|
|
2602
2602
|
const verifCommands = await this.inferVerificationCommands(this.getLatestUserText(messages));
|
|
@@ -2607,7 +2607,7 @@ ${colors.reset}
|
|
|
2607
2607
|
const result = await this.runVerification(verifCommands);
|
|
2608
2608
|
|
|
2609
2609
|
if (result.passed) {
|
|
2610
|
-
console.log(`\n${colors.green}
|
|
2610
|
+
console.log(`\n${colors.green}✓ All verifications passed!${colors.reset}\n`);
|
|
2611
2611
|
return;
|
|
2612
2612
|
}
|
|
2613
2613
|
|
|
@@ -2617,7 +2617,7 @@ ${colors.reset}
|
|
|
2617
2617
|
.map(r => `Command: ${r.cmd}\n${r.output}`)
|
|
2618
2618
|
.join('\n\n---\n\n');
|
|
2619
2619
|
|
|
2620
|
-
console.log(`\n${colors.yellow}
|
|
2620
|
+
console.log(`\n${colors.yellow}Verification failed. Sending errors back to AI for fix...${colors.reset}\n`);
|
|
2621
2621
|
|
|
2622
2622
|
// Push error output as user message for AI to fix
|
|
2623
2623
|
const fixPrompt = `VERIFICATION FAILED (attempt ${attempt}/${maxAttempts}):
|
|
@@ -2635,12 +2635,12 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2635
2635
|
const { usedTools: fixUsedTools } = await this.runConversation(messages, 'Fixing', tools);
|
|
2636
2636
|
|
|
2637
2637
|
if (!fixUsedTools) {
|
|
2638
|
-
console.log(`\n${colors.red}
|
|
2638
|
+
console.log(`\n${colors.red}AI did not attempt to fix the errors. Stopping.${colors.reset}\n`);
|
|
2639
2639
|
break;
|
|
2640
2640
|
}
|
|
2641
2641
|
}
|
|
2642
2642
|
|
|
2643
|
-
console.log(`\n${colors.red}
|
|
2643
|
+
console.log(`\n${colors.red}Max verification attempts (${maxAttempts}) reached. Some issues may remain.${colors.reset}\n`);
|
|
2644
2644
|
}
|
|
2645
2645
|
|
|
2646
2646
|
shouldUseTools(message = '', imageAttachments = []) {
|
|
@@ -2676,9 +2676,9 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2676
2676
|
];
|
|
2677
2677
|
|
|
2678
2678
|
const promptHistory = this.getCompressedPromptHistory({
|
|
2679
|
-
limit:
|
|
2680
|
-
keepRecent:
|
|
2681
|
-
maxTotalChars:
|
|
2679
|
+
limit: 40,
|
|
2680
|
+
keepRecent: 16,
|
|
2681
|
+
maxTotalChars: 16000,
|
|
2682
2682
|
});
|
|
2683
2683
|
if (promptHistory.summary) {
|
|
2684
2684
|
messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
|
|
@@ -2770,14 +2770,14 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2770
2770
|
const passed = result.usedTools && Boolean(readEvent || /readme\.md/i.test(result.finalContent || ''));
|
|
2771
2771
|
|
|
2772
2772
|
if (passed) {
|
|
2773
|
-
console.log(`${colors.green}
|
|
2773
|
+
console.log(`${colors.green}✓ Tool calling works for ${provider}/${model}.${colors.reset}`);
|
|
2774
2774
|
if (readEvent) {
|
|
2775
2775
|
console.log(`${colors.dim} Last Read result: ${readEvent.result?.path || probePath}${colors.reset}`);
|
|
2776
2776
|
}
|
|
2777
2777
|
return { success: true, provider, model, usedTools: result.usedTools, beforeEvents };
|
|
2778
2778
|
}
|
|
2779
2779
|
|
|
2780
|
-
console.log(`${colors.red}
|
|
2780
|
+
console.log(`${colors.red}✖ Tool calling did not execute for ${provider}/${model}.${colors.reset}`);
|
|
2781
2781
|
console.log(`${colors.yellow} Try a stronger model or use a provider that supports OpenAI-compatible tools/fallback text output.${colors.reset}`);
|
|
2782
2782
|
return { success: false, provider, model, usedTools: result.usedTools, beforeEvents };
|
|
2783
2783
|
}
|
|
@@ -2864,11 +2864,17 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2864
2864
|
context.push(requiredLocalResources);
|
|
2865
2865
|
}
|
|
2866
2866
|
|
|
2867
|
+
const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
|
|
2868
|
+
const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
|
|
2869
|
+
if (localResources) {
|
|
2870
|
+
context.push(localResources);
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2867
2873
|
const projectInstructionFiles = await this.readProjectInstructionFiles();
|
|
2868
2874
|
|
|
2869
2875
|
for (const file of projectInstructionFiles) {
|
|
2870
2876
|
try {
|
|
2871
|
-
const preview = this.compactText(file.content,
|
|
2877
|
+
const preview = this.compactText(file.content, 1200, 'project instruction');
|
|
2872
2878
|
context.push(`[${file.relativePath}]\n${preview}`);
|
|
2873
2879
|
} catch { }
|
|
2874
2880
|
}
|
|
@@ -2878,21 +2884,24 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2878
2884
|
const stat = await fs.stat(packageJsonPath);
|
|
2879
2885
|
if (stat.isFile()) {
|
|
2880
2886
|
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
2881
|
-
context.push(`[package.json]\n${this.compactText(content,
|
|
2887
|
+
context.push(`[package.json]\n${this.compactText(content, 1600, 'package.json')}`);
|
|
2882
2888
|
}
|
|
2883
2889
|
} catch { }
|
|
2884
2890
|
|
|
2885
|
-
const shouldIncludeResources = /\b(resource|resources|skill|skills|plugin|plugins|claude|codex|agent|agents|design|ui|figma|brand|mcp)\b/i.test(String(task || ''));
|
|
2886
|
-
const localResources = shouldIncludeResources ? await this.getLocalResourceContext() : '';
|
|
2887
|
-
if (localResources) {
|
|
2888
|
-
context.push(localResources);
|
|
2889
|
-
}
|
|
2890
|
-
|
|
2891
2891
|
const codebaseContext = await this.buildCodebaseContext(task);
|
|
2892
2892
|
if (codebaseContext) {
|
|
2893
2893
|
context.push(codebaseContext);
|
|
2894
2894
|
}
|
|
2895
2895
|
|
|
2896
|
+
const graphContext = await this.codebaseSearcher?.buildGraphContext?.(task, {
|
|
2897
|
+
maxNodes: 24,
|
|
2898
|
+
maxCodeBlocks: 8,
|
|
2899
|
+
maxCodeBlockSize: 1800,
|
|
2900
|
+
});
|
|
2901
|
+
if (graphContext) {
|
|
2902
|
+
context.push(`[CodeGraph Context]\n${this.compactText(graphContext, 5200, 'codegraph context')}`);
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2896
2905
|
// Git Context
|
|
2897
2906
|
try {
|
|
2898
2907
|
const { execSync } = await import('child_process');
|
|
@@ -2902,20 +2911,20 @@ Do NOT stop until all errors are resolved.`;
|
|
|
2902
2911
|
|
|
2903
2912
|
const gitSummary = execSync('git diff --stat --summary', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim();
|
|
2904
2913
|
if (gitSummary) {
|
|
2905
|
-
context.push(`[Git Summary]\n${this.compactText(gitSummary,
|
|
2914
|
+
context.push(`[Git Summary]\n${this.compactText(gitSummary, 1200, 'git summary')}`);
|
|
2906
2915
|
}
|
|
2907
2916
|
|
|
2908
2917
|
// Get brief git diff for context
|
|
2909
2918
|
const gitDiff = execSync('git diff', { cwd: this.projectPath, encoding: 'utf8', stdio: 'pipe', maxBuffer: 1024 * 50 }).trim().split('\n').slice(0, 30).join('\n');
|
|
2910
2919
|
if (gitDiff) {
|
|
2911
|
-
context.push(`[Git Diff]\n${this.compactText(gitDiff,
|
|
2920
|
+
context.push(`[Git Diff]\n${this.compactText(gitDiff, 2200, 'git diff')}`);
|
|
2912
2921
|
}
|
|
2913
2922
|
}
|
|
2914
2923
|
} catch (e) {
|
|
2915
2924
|
// Not a git repo or git not installed
|
|
2916
2925
|
}
|
|
2917
2926
|
|
|
2918
|
-
return this.compactText(context.join('\n\n') || 'No project context found.',
|
|
2927
|
+
return this.compactText(context.join('\n\n') || 'No project context found.', 14000, 'project context');
|
|
2919
2928
|
}
|
|
2920
2929
|
|
|
2921
2930
|
async getLocalResourceContext() {
|
|
@@ -3017,7 +3026,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3017
3026
|
this.promptBuilder.tools = this.tools;
|
|
3018
3027
|
this.promptBuilder.sessionPermissionGrants = this.sessionPermissionGrants;
|
|
3019
3028
|
return this.promptBuilder.buildSystemPrompt(context, {
|
|
3020
|
-
projectContextBudget:
|
|
3029
|
+
projectContextBudget: 5200,
|
|
3021
3030
|
});
|
|
3022
3031
|
}
|
|
3023
3032
|
|
|
@@ -3064,7 +3073,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3064
3073
|
}
|
|
3065
3074
|
config.mcp.servers.forEach(server => {
|
|
3066
3075
|
const enabled = server.enabled === false ? `${colors.red}disabled${colors.reset}` : `${colors.green}enabled${colors.reset}`;
|
|
3067
|
-
console.log(`
|
|
3076
|
+
console.log(` - ${server.name} (${enabled}) -> ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''}`);
|
|
3068
3077
|
});
|
|
3069
3078
|
break;
|
|
3070
3079
|
case 'add': {
|
|
@@ -3086,7 +3095,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3086
3095
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3087
3096
|
config.mcp.servers.push({ name, command, args: parsedArgs, enabled: true });
|
|
3088
3097
|
await this.config.save(config);
|
|
3089
|
-
console.log(`${colors.green}
|
|
3098
|
+
console.log(`${colors.green}✓ Added MCP server: ${name}${colors.reset}`);
|
|
3090
3099
|
break;
|
|
3091
3100
|
}
|
|
3092
3101
|
case 'remove': {
|
|
@@ -3097,7 +3106,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3097
3106
|
}
|
|
3098
3107
|
config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
|
|
3099
3108
|
await this.config.save(config);
|
|
3100
|
-
console.log(`${colors.green}
|
|
3109
|
+
console.log(`${colors.green}✓ Removed MCP server: ${name}${colors.reset}`);
|
|
3101
3110
|
break;
|
|
3102
3111
|
}
|
|
3103
3112
|
case 'allow': {
|
|
@@ -3107,7 +3116,7 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3107
3116
|
break;
|
|
3108
3117
|
}
|
|
3109
3118
|
await this.config.setPermissionAllowlist({ mcpServers: [name] });
|
|
3110
|
-
console.log(`${colors.green}
|
|
3119
|
+
console.log(`${colors.green}✓ MCP server allowed: ${name}${colors.reset}`);
|
|
3111
3120
|
break;
|
|
3112
3121
|
}
|
|
3113
3122
|
default:
|
|
@@ -3142,13 +3151,13 @@ Do NOT stop until all errors are resolved.`;
|
|
|
3142
3151
|
break;
|
|
3143
3152
|
}
|
|
3144
3153
|
await this.config.setPermissionAllowlist({ [field]: [value] });
|
|
3145
|
-
console.log(`${colors.green}
|
|
3154
|
+
console.log(`${colors.green}✓ Allowed ${kind}: ${value}${colors.reset}`);
|
|
3146
3155
|
break;
|
|
3147
3156
|
}
|
|
3148
3157
|
case 'prompt': {
|
|
3149
3158
|
const value = String(rest[0] || '').toLowerCase();
|
|
3150
3159
|
await this.config.setPermissionAllowlist({ promptByDefault: !(value === 'off' || value === 'false' || value === '0' || value === 'no') });
|
|
3151
|
-
console.log(`${colors.green}
|
|
3160
|
+
console.log(`${colors.green}✓ Updated prompt policy${colors.reset}`);
|
|
3152
3161
|
break;
|
|
3153
3162
|
}
|
|
3154
3163
|
default:
|