winter-super-cli 2026.5.30 → 2026.5.31

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/bin/winter.js CHANGED
@@ -18,7 +18,7 @@ const version = pkg.version;
18
18
 
19
19
  const COMMANDS = new Set([
20
20
  'chat', 'call', 'benchmark', 'session', 'skill', 'plugin', 'design', 'config', 'init',
21
- 'help', 'project', 'code', 'review', 'mcp', 'permissions',
21
+ 'help', 'project', 'code', 'review', 'debug', 'auto', 'mcp', 'permissions',
22
22
  'provider', 'providers', 'model', 'models',
23
23
  ]);
24
24
 
@@ -68,6 +68,8 @@ Commands:
68
68
  winter config Show config
69
69
  winter init Initialize local state
70
70
  winter review Code review mode
71
+ winter debug <error/task> Auto-debug with verification
72
+ winter auto <task> Auto-heal with test/build loop
71
73
  winter code Code analysis mode
72
74
 
73
75
  Flags:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "winter-super-cli",
3
- "version": "2026.5.30",
3
+ "version": "2026.5.31",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import { isSmallModel, getModelCapabilityLabel } from '../model-capabilities.js';
8
+ import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from '../../cli/runtime-env.js';
8
9
 
9
10
  const BASE_PRINCIPLES = [
10
11
  'Execute, don\'t describe - Do the work, don\'t write plans about doing the work',
@@ -25,18 +26,9 @@ const TOOL_CATEGORIES = {
25
26
  };
26
27
 
27
28
  function buildEnvironmentSummary() {
28
- const os = process.platform === 'win32' ? 'Windows'
29
- : process.platform === 'darwin' ? 'macOS'
30
- : process.platform === 'linux' ? 'Linux' : process.platform;
31
-
32
- const shellHint = process.platform === 'win32'
33
- ? 'Use shell:"powershell" for PowerShell, shell:"cmd" for cmd.exe'
34
- : `Shell: ${process.env.SHELL || 'bash'}`;
35
-
36
29
  return [
37
- `Host OS: ${os}`,
30
+ formatRuntimeEnvironmentSummary(getRuntimeEnvironment()),
38
31
  `Node: ${process.version}`,
39
- shellHint,
40
32
  ].join('\n');
41
33
  }
42
34
 
@@ -12,6 +12,7 @@ import { PluginManager } from '../plugins/manager.js';
12
12
  import { MCPClient } from '../mcp/client.js';
13
13
  import { BenchmarkRunner } from '../ai/benchmark.js';
14
14
  import { redactSecrets } from './secret-env.js';
15
+ import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from './runtime-env.js';
15
16
 
16
17
  export { redactSecrets } from './secret-env.js';
17
18
 
@@ -60,6 +61,8 @@ export class CommandParser {
60
61
  design: this.handleDesign.bind(this),
61
62
  code: this.handleCode.bind(this),
62
63
  review: this.handleReview.bind(this),
64
+ debug: this.handleDebug.bind(this),
65
+ auto: this.handleDebug.bind(this),
63
66
  config: this.handleConfig.bind(this),
64
67
  init: this.handleInit.bind(this),
65
68
  help: this.handleHelp.bind(this),
@@ -104,6 +107,8 @@ export class CommandParser {
104
107
  '/models': () => this.showModels(),
105
108
  '/mcp': () => this.handleMcp(args),
106
109
  '/permissions': () => this.handlePermissions(args),
110
+ '/debug': () => this.handleDebug(args),
111
+ '/auto': () => this.handleDebug(args),
107
112
  '/exit': () => process.exit(0),
108
113
  };
109
114
 
@@ -115,6 +120,11 @@ export class CommandParser {
115
120
  }
116
121
  }
117
122
 
123
+ async handleDebug(args) {
124
+ const task = args.join(' ') || 'Find the root cause, patch it, and verify with the closest test or build command';
125
+ return this.handleChat([`AUTO DEBUG: ${task}`]);
126
+ }
127
+
118
128
  async handleChat(args) {
119
129
  const message = args.join(' ');
120
130
  if (!message) {
@@ -638,12 +648,7 @@ export class CommandParser {
638
648
 
639
649
  getWinterSystemPrompt() {
640
650
  const environmentSummary = [
641
- `Host OS: ${process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : process.platform === 'linux' ? 'Linux' : process.platform}`,
642
- `Node platform: ${process.platform}`,
643
- `Current shell hint: ${process.platform === 'win32' ? 'powershell-capable or cmd/unknown' : (process.env.SHELL || 'bash/sh')}`,
644
- process.platform === 'win32'
645
- ? 'Shell rule: Use shell:"powershell" for PowerShell cmdlets, shell:"cmd" for cmd.exe syntax, and shell:"auto" when unsure.'
646
- : 'Shell rule: Use the native POSIX shell on non-Windows hosts and leave shell unspecified unless a specific shell is required.',
651
+ formatRuntimeEnvironmentSummary(getRuntimeEnvironment()),
647
652
  ].join('\n');
648
653
 
649
654
  return `You are Winter, an expert AI coding assistant.
@@ -1,6 +1,10 @@
1
1
  import { promises as fs } from 'fs';
2
+ import { existsSync } from 'fs';
2
3
  import path from 'path';
3
4
  import { homedir } from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
4
8
 
5
9
  /**
6
10
  * ContextLoader — Loads project context, resource paths, and instruction files.
@@ -42,8 +46,18 @@ export class ContextLoader {
42
46
  return files;
43
47
  }
44
48
 
49
+ getResourceRoot() {
50
+ const projectLocalRoot = path.join(this.projectPath, 'resources', 'local');
51
+ const packageLocalRoot = path.join(PACKAGE_ROOT, 'resources', 'local');
52
+
53
+ if (this.projectPath === PACKAGE_ROOT) return projectLocalRoot;
54
+ if (existsSync(projectLocalRoot)) return projectLocalRoot;
55
+
56
+ return packageLocalRoot;
57
+ }
58
+
45
59
  getResourcePaths() {
46
- const localRoot = path.join(this.projectPath, 'resources', 'local');
60
+ const localRoot = this.getResourceRoot();
47
61
  return {
48
62
  codex: {
49
63
  root: path.join(localRoot, 'codex'),
@@ -0,0 +1,188 @@
1
+ import { promises as fs } from 'fs';
2
+ import path from 'path';
3
+
4
+ const GENERATED_MARKER = 'File này được tự động tạo bởi Winter CLI.';
5
+
6
+ const CORE_SKILLS = [
7
+ ['coding', 'Inspect source first, make focused code changes, and verify syntax/tests.'],
8
+ ['debug', 'Trace the concrete failing path, explain the first hard blocker, then patch it.'],
9
+ ['design', 'Use awesome-design-md before inventing UI style, spacing, or brand language.'],
10
+ ['refactor', 'Keep behavior stable while reducing complexity in small, reviewable steps.'],
11
+ ['test', 'Add regression coverage near the changed behavior and run the narrow test first.'],
12
+ ['security', 'Protect secrets, validate inputs, avoid unsafe shell/file operations.'],
13
+ ['performance', 'Measure or reason from the hot path before optimizing.'],
14
+ ];
15
+
16
+ async function readJsonIfExists(filePath) {
17
+ try {
18
+ const raw = await fs.readFile(filePath, 'utf8');
19
+ return JSON.parse(raw.replace(/^\uFEFF/, ''));
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ async function listEntries(target, { directories = true, files = false, limit = 100 } = {}) {
26
+ try {
27
+ const entries = await fs.readdir(target, { withFileTypes: true });
28
+ return entries
29
+ .filter(entry => (directories && entry.isDirectory()) || (files && entry.isFile()))
30
+ .map(entry => entry.name)
31
+ .sort((a, b) => a.localeCompare(b))
32
+ .slice(0, limit);
33
+ } catch {
34
+ return [];
35
+ }
36
+ }
37
+
38
+ function formatBytes(bytes = 0) {
39
+ if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
40
+ const mb = bytes / 1024 / 1024;
41
+ if (mb >= 1) return `${mb.toFixed(1)} MB`;
42
+ const kb = bytes / 1024;
43
+ return `${kb.toFixed(1)} KB`;
44
+ }
45
+
46
+ function getResource(manifest, name) {
47
+ return (manifest?.localResources || []).find(resource => resource.name === name);
48
+ }
49
+
50
+ function bulletList(items, emptyText) {
51
+ if (!items.length) return `- ${emptyText}`;
52
+ return items.map(item => `- ${item}`).join('\n');
53
+ }
54
+
55
+ function generatedFooter() {
56
+ return `---\n*${GENERATED_MARKER}*`;
57
+ }
58
+
59
+ export function isWinterGeneratedProjectDoc(content = '') {
60
+ return /File n(?:ày|ày) .*Winter CLI/i.test(content)
61
+ || /được tự động tạo bởi Winter CLI/i.test(content)
62
+ || /được tự động tạo bởi Winter CLI/i.test(content);
63
+ }
64
+
65
+ export async function buildDesignDoc({ projectPath, resourcePaths }) {
66
+ const manifest = await readJsonIfExists(resourcePaths.manifest);
67
+ const designResource = getResource(manifest, 'awesome-design-md');
68
+ const brandNames = await listEntries(resourcePaths.designs, { directories: true, limit: 40 });
69
+ const relativeDesignPath = path.relative(projectPath, resourcePaths.designs) || resourcePaths.designs;
70
+ const relativeReadmePath = path.join(path.relative(projectPath, path.dirname(resourcePaths.designs)), 'README.md');
71
+ const corpusStatus = brandNames.length > 0
72
+ ? `Corpus local sẵn sàng tại \`${relativeDesignPath}\`.`
73
+ : 'Corpus chi tiết chưa có trong working tree hiện tại. Dùng manifest để biết index, hoặc cài/copy resource pack đầy đủ trước khi làm UI/brand task.';
74
+
75
+ return `# Design Guidance
76
+
77
+ File này không phải danh bạ trang trí. Đây là checklist bắt buộc khi Winter xử lý UI, brand, landing page, dashboard, hoặc component.
78
+
79
+ ## Resource Status
80
+ - Source: \`${relativeReadmePath}\`
81
+ - Corpus: \`${relativeDesignPath}\`
82
+ - Manifest: ${designResource ? `${designResource.files} files, ${formatBytes(designResource.bytes)}` : 'không có entry awesome-design-md'}
83
+ - Status: ${corpusStatus}
84
+
85
+ ## How Winter Must Use This
86
+ 1. Khi task liên quan UI/design/brand, tìm brand hoặc style gần nhất trong \`awesome-design-md/design-md\` trước khi viết code.
87
+ 2. Nếu user nêu brand cụ thể, ưu tiên đúng brand đó; nếu không có, chọn brand gần nhất theo domain và nói rõ giả định.
88
+ 3. Không tự bịa palette, spacing, typography, tone khi local design guide có dữ liệu phù hợp.
89
+ 4. Với app/work tool, ưu tiên layout rõ, dense, dễ scan; không dùng hero marketing nếu user đang cần tool thật.
90
+ 5. Sau khi sửa UI, kiểm tra responsive và text overflow nếu có thể chạy app.
91
+
92
+ ## Available Brand Samples (${brandNames.length}${brandNames.length >= 40 ? '+' : ''})
93
+ ${bulletList(brandNames, 'Chưa tìm thấy brand folder trong working tree hiện tại.')}
94
+
95
+ ## Useful Commands
96
+ - \`/designs\` để liệt kê hoặc tìm design systems.
97
+ - \`/read resources/local/awesome-design-md/README.md\` để đọc overview.
98
+ - \`/grep <brand> resources/local/awesome-design-md/design-md\` để tìm brand/style guide.
99
+
100
+ ${generatedFooter()}`;
101
+ }
102
+
103
+ export async function buildSkillDoc({ contextLoader }) {
104
+ const catalog = await contextLoader.getStartupSkillCatalog();
105
+ const skills = [...catalog]
106
+ .filter(skill => skill && !skill.startsWith('.'))
107
+ .sort((a, b) => a.localeCompare(b));
108
+
109
+ return `# Skill Guidance
110
+
111
+ File này định nghĩa cách Winter chọn và áp dụng skill. Không chỉ liệt kê tên skill.
112
+
113
+ ## Default Rule
114
+ - Luôn đọc yêu cầu, repo context, và file liên quan trước khi quyết định skill.
115
+ - Skill là operational context: áp dụng vào hành động thật, không chỉ nhắc lại trong câu trả lời.
116
+ - Model nhỏ vẫn phải theo cùng tiêu chuẩn: inspect, edit bằng tool, verify, rồi mới kết luận.
117
+
118
+ ## Core Skills
119
+ ${CORE_SKILLS.map(([name, description]) => `- **${name}**: ${description}`).join('\n')}
120
+
121
+ ## Available Local Skills (${skills.length})
122
+ ${bulletList(skills, 'Chưa tìm thấy skill local ngoài core skills.')}
123
+
124
+ ## When To Apply
125
+ - Code change: coding + test, thêm debug nếu có lỗi cụ thể.
126
+ - UI/page/component: design + coding + test.
127
+ - Bug/runtime log: debug trước, coding sau.
128
+ - Refactor lớn: refactor + test, giữ behavior stable.
129
+ - Security/config/secret: security bắt buộc.
130
+ - Performance/flicker/slow tool call: performance + debug.
131
+
132
+ ${generatedFooter()}`;
133
+ }
134
+
135
+ export async function buildRuleDoc({ projectPath, resourcePaths, userResourcePaths, readProjectInstructionFiles }) {
136
+ const manifest = await readJsonIfExists(resourcePaths.manifest);
137
+ const resources = manifest?.localResources || [];
138
+ const instructionFiles = (await readProjectInstructionFiles())
139
+ .map(file => file.relativePath)
140
+ .filter(relativePath => relativePath !== 'rule.md');
141
+ const bundledRuleFiles = await listEntries(resourcePaths.codex.rules, { directories: false, files: true, limit: 30 });
142
+ const userRuleFiles = await listEntries(userResourcePaths?.codexRules, { directories: false, files: true, limit: 30 });
143
+ const karpathyPath = path.relative(projectPath, path.join(resourcePaths.karpathy, 'CLAUDE.md'));
144
+ const agentsPath = path.relative(projectPath, path.join(resourcePaths.agents, 'AGENTS.md'));
145
+
146
+ return `# Project Operating Rules
147
+
148
+ File này là contract vận hành cho Winter trong project này.
149
+
150
+ ## Non-Negotiable Behavior
151
+ - Không nói đã sửa/chạy/kiểm tra nếu chưa có tool result trong lượt đó.
152
+ - Trước khi sửa code: đọc file liên quan, hiểu entrypoint/runtime path, rồi mới patch.
153
+ - Giữ thay đổi hẹp, không revert code user không yêu cầu.
154
+ - Sau khi sửa: chạy syntax check hoặc test gần nhất có thể.
155
+ - Với model nhỏ: bắt buộc chia việc thành inspect -> implement -> verify -> report.
156
+
157
+ ## Project Instruction Files
158
+ ${instructionFiles.length ? instructionFiles.map(file => `- [${file}](./${file})`).join('\n') : '- Chưa có instruction file khác.'}
159
+
160
+ ## Mandatory Local Resources
161
+ - Karpathy tools: \`${karpathyPath}\`
162
+ - Agents guide: \`${agentsPath}\`
163
+ - Design corpus: \`${path.relative(projectPath, resourcePaths.designs)}\`
164
+
165
+ ## Resource Inventory
166
+ ${resources.length ? resources.map(resource => `- **${resource.name}**: ${resource.files} files, ${formatBytes(resource.bytes)}`).join('\n') : '- Không đọc được manifest resource.'}
167
+
168
+ ## Extra Rule Files
169
+ - Bundled Codex rules: ${bundledRuleFiles.length ? bundledRuleFiles.join(', ') : 'none found'}
170
+ - User Codex rules: ${userRuleFiles.length ? userRuleFiles.join(', ') : 'none found'}
171
+
172
+ ## Acceptance Checklist
173
+ - Đúng provider/model user chọn, không tự route sai.
174
+ - Tool call phải dùng đúng schema; lỗi thì retry/recover trước khi báo user.
175
+ - Memory/session phải giữ đúng project.
176
+ - UI docs/design phải dùng local resources khi task liên quan.
177
+ - Final answer ngắn, nêu file đã sửa và verification thật.
178
+
179
+ ${generatedFooter()}`;
180
+ }
181
+
182
+ export async function buildProjectDocs(options) {
183
+ return [
184
+ { filename: 'design.md', content: await buildDesignDoc(options) },
185
+ { filename: 'skill.md', content: await buildSkillDoc(options) },
186
+ { filename: 'rule.md', content: await buildRuleDoc(options) },
187
+ ];
188
+ }
@@ -1,3 +1,5 @@
1
+ import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from './runtime-env.js';
2
+
1
3
  /**
2
4
  * PromptBuilder — Builds system prompts for Winter CLI agents.
3
5
  * Extracted from WinterREPL to reduce repl.js size.
@@ -79,6 +81,7 @@ export class PromptBuilder {
79
81
  `Use tools when they materially help. For coding tasks: inspect first, edit second, verify third.`,
80
82
  `Prefer Read/Grep/Glob before editing. Use Write/Edit for file changes.`,
81
83
  `When a task touches coding, agents, UI, brand, or design, inspect the relevant required local resource in depth before deciding.`,
84
+ `If the user asks you to modify, run, inspect, check, publish, commit, or otherwise act on the project, you MUST use tools. Do not claim completion without a tool result from this turn.`,
82
85
  ``,
83
86
  `## Session`,
84
87
  `Working directory: ${this.projectPath}`,
@@ -143,9 +146,7 @@ export class PromptBuilder {
143
146
 
144
147
  const rolePrompt = rolePrompts[role] || 'You are a Winter coding subagent. Solve the task directly, use tools when needed, and return a concise result.';
145
148
 
146
- const osInfo = process.platform === 'win32'
147
- ? 'Windows; Bash auto-detects PowerShell and cmd.exe syntax. Use shell="powershell" or shell="cmd" when needed.'
148
- : process.platform;
149
+ const runtimeSummary = this.tools?.getRuntimeEnvironmentSummary?.() || this._defaultEnvironmentSummary();
149
150
 
150
151
  return [
151
152
  '## CRITICAL AI RULES (MUST FOLLOW STRICTLY):',
@@ -161,8 +162,9 @@ export class PromptBuilder {
161
162
  '## Tool Rules',
162
163
  '- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, BrowserDebug, WebFetch, WebSearch.',
163
164
  '- Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.',
164
- `- Current OS is ${osInfo}.`,
165
+ `- Runtime environment:\n${runtimeSummary}`,
165
166
  '- 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.',
167
+ '- For action requests, use tools before claiming anything is done. Never claim files changed, tests ran, or checks passed unless this conversation contains the matching tool result.',
166
168
  '- If a tool call fails because of an unknown alias, call the canonical tool name next.',
167
169
  '- Always start with a brief plan, then refine it when new facts appear.',
168
170
  '',
@@ -196,14 +198,7 @@ export class PromptBuilder {
196
198
 
197
199
  /** @private */
198
200
  _defaultEnvironmentSummary() {
199
- return [
200
- `Host OS: ${process.platform === 'win32' ? 'Windows' : process.platform === 'darwin' ? 'macOS' : process.platform === 'linux' ? 'Linux' : process.platform}`,
201
- `Node platform: ${process.platform}`,
202
- `Current shell hint: ${process.platform === 'win32' ? 'powershell-capable or cmd/unknown' : (process.env.SHELL || 'bash/sh')}`,
203
- process.platform === 'win32'
204
- ? 'Shell rule: Use shell:"powershell" for PowerShell cmdlets, shell:"cmd" for cmd.exe syntax, and shell:"auto" when unsure.'
205
- : 'Shell rule: Use the native POSIX shell on non-Windows hosts and leave shell unspecified unless a specific shell is required.',
206
- ].join('\n');
201
+ return formatRuntimeEnvironmentSummary(getRuntimeEnvironment());
207
202
  }
208
203
 
209
204
  /** @private */
@@ -217,6 +217,10 @@ export async function handleSlashCommand(repl, input) {
217
217
  const task = args.join(' ') || 'Run tests until they pass';
218
218
  await repl.runAutoHealing(task);
219
219
  return;
220
+ case '/debug':
221
+ const debugTask = args.join(' ') || 'Find the root cause of the current failure, patch it, and verify with the closest test or build command';
222
+ await repl.runAutoHealing(`AUTO DEBUG: ${debugTask}`);
223
+ return;
220
224
  case '/plan:':
221
225
  case '/plan-gen':
222
226
  if (args.length === 0) {
package/src/cli/repl.js CHANGED
@@ -21,6 +21,7 @@ import { formatMarkdown } from './markdown-format.js';
21
21
  import { Spinner } from './spinner.js';
22
22
  import { ContextLoader } from './context-loader.js';
23
23
  import { PromptBuilder } from './prompt-builder.js';
24
+ import { buildProjectDocs, isWinterGeneratedProjectDoc } from './project-docs.js';
24
25
  import { classifyModelTier, isSmallModel } from '../ai/model-capabilities.js';
25
26
  import {
26
27
  addUsage as mergeUsage,
@@ -231,104 +232,30 @@ export class WinterREPL {
231
232
  }
232
233
 
233
234
  // ── Tự động tạo design.md, skill.md, rule.md nếu chưa có ──────────────
234
- const autoCreateDocs = [
235
- {
236
- filename: 'design.md',
237
- generate: async () => {
238
- const designDir = this.getResourcePaths().designs;
239
- let brands = [];
240
- try {
241
- const entries = await fsPromises.readdir(designDir, { withFileTypes: true });
242
- brands = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
243
- } catch {}
244
- return `# Design Resources
245
-
246
- Danh sách các design system có sẵn trong local resources:
247
-
248
- ## Available Brands (${brands.length})
249
-
250
- ${brands.length > 0 ? brands.map(b => `- ${b}`).join('\n') : '- Không tìm thấy design system nào.'}
251
-
252
- ---
253
- *File này được tự động tạo bởi Winter CLI.*`;
254
- },
255
- },
256
- {
257
- filename: 'skill.md',
258
- generate: async () => {
259
- const catalog = await this.contextLoader.getStartupSkillCatalog();
260
- const skills = [...catalog].sort();
261
- return `# Available Skills
262
-
263
- Danh sách các skill có sẵn trong hệ thống:
264
-
265
- ## Core Skills
266
- - **coding**: Code analysis, generation, review
267
- - **design**: Design system integration
268
- - **debug**: Debugging assistance
269
- - **refactor**: Code refactoring
270
- - **test**: Test generation
271
- - **security**: Security review
272
- - **performance**: Performance optimization
273
-
274
- ## All Available Skills (${skills.length})
275
-
276
- ${skills.map(s => `- ${s}`).join('\n')}
277
-
278
- ---
279
- *File này được tự động tạo bởi Winter CLI.*`;
280
- },
281
- },
282
- {
283
- filename: 'rule.md',
284
- generate: async () => {
285
- const parts = ['# Project Rules', '', '## Quy tắc dự án', ''];
286
- // Load từ các instruction files đã có
287
- const files = await this.readProjectInstructionFiles();
288
- for (const file of files) {
289
- if (file.relativePath === 'rule.md') continue; // skip self
290
- parts.push(`- [${file.relativePath}](./${file.relativePath})`);
291
- }
292
- // Liệt kê các thư mục rules
293
- const rulesDirs = [
294
- this.getResourcePaths().codex.rules,
295
- this.getUserResourcePaths()?.codexRules,
296
- ].filter(Boolean);
297
- for (const dir of rulesDirs) {
298
- try {
299
- const entries = await fsPromises.readdir(dir, { withFileTypes: true });
300
- const ruleFiles = entries.filter(e => e.isFile() && e.name.endsWith('.md')).map(e => e.name);
301
- if (ruleFiles.length > 0) {
302
- parts.push('', `## Rules from ${path.basename(path.dirname(dir))}/${path.basename(dir)}`, '');
303
- for (const f of ruleFiles) {
304
- parts.push(`- ${f}`);
305
- }
306
- }
307
- } catch {}
308
- }
309
- // Liệt kê local resource rules
310
- parts.push('', '## Local Resource Guidelines', '');
311
- parts.push('- Karpathy tools guidelines available in local resources');
312
- parts.push('- Agents.md project guidelines available in local resources');
313
- parts.push('', '---', '*File này được tự động tạo bởi Winter CLI.*');
314
- return parts.join('\n');
315
- },
316
- },
317
- ];
235
+ const autoCreateDocs = await buildProjectDocs({
236
+ projectPath: this.projectPath,
237
+ resourcePaths: this.getResourcePaths(),
238
+ userResourcePaths: this.getUserResourcePaths(),
239
+ contextLoader: this.contextLoader,
240
+ readProjectInstructionFiles: () => this.readProjectInstructionFiles(),
241
+ });
318
242
 
319
243
  for (const doc of autoCreateDocs) {
320
244
  const filePath = path.join(this.projectPath, doc.filename);
321
245
  try {
322
- await fsPromises.stat(filePath);
323
- // File đã tồn tại, bỏ qua
246
+ const existing = await fsPromises.readFile(filePath, 'utf8');
247
+ if (!isWinterGeneratedProjectDoc(existing)) continue;
248
+
249
+ await fsPromises.writeFile(filePath, doc.content, 'utf8');
250
+ console.log(`${colors.green}✓ Đã nâng cấp file ${doc.filename} từ local resources.${colors.reset}`);
251
+ const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
252
+ await this.session.replaceMemory(memoryKey, doc.content);
324
253
  } catch {
325
- // File chưa tồn tại, tự động tạo
326
254
  try {
327
- const content = await doc.generate();
328
- await fsPromises.writeFile(filePath, content, 'utf8');
255
+ await fsPromises.writeFile(filePath, doc.content, 'utf8');
329
256
  console.log(`${colors.green}✓ Đã tự động tạo file ${doc.filename} từ local resources!${colors.reset}`);
330
257
  const memoryKey = `[Quy tắc dự án từ ${doc.filename}]`;
331
- await this.session.replaceMemory(memoryKey, content);
258
+ await this.session.replaceMemory(memoryKey, doc.content);
332
259
  } catch (err) {
333
260
  // Bỏ qua nếu không tạo được
334
261
  }
@@ -894,7 +821,8 @@ ${colors.magenta}╭${'─'.repeat(w)}╮${colors.reset}
894
821
  row(`${c.yellow}/config${c.reset} Xem cấu hình`, `${c.yellow}/exit${c.reset} Thoát`),
895
822
  '',
896
823
  `${c.bright}AI & Công cụ${c.reset}`,
897
- row(`${c.yellow}/auto${c.reset} TDD tự sửa lỗi`, `${c.yellow}/agent${c.reset} Chạy sub-agent`),
824
+ row(`${c.yellow}/auto${c.reset} TDD t? s?a l?i`, `${c.yellow}/debug${c.reset} Auto debug l?i`),
825
+ row(`${c.yellow}/agent${c.reset} Ch?y sub-agent`, `${c.yellow}/swe${c.reset} SWE workflow`),
898
826
  row(`${c.yellow}/read${c.reset} Đọc file`, `${c.yellow}/write${c.reset} Ghi file`),
899
827
  row(`${c.yellow}/bash${c.reset} Chạy lệnh terminal`, `${c.yellow}/grep${c.reset} Tìm trong file`),
900
828
  row(`${c.yellow}/glob${c.reset} Tìm file theo pattern`, `${c.yellow}/image${c.reset} Phân tích UI`),
@@ -949,6 +877,8 @@ ${colors.white}Plans & Tasks:${colors.reset}
949
877
  /task <desc> Create task
950
878
  /tasks List tasks
951
879
  /agent [role] <task> Run a subagent
880
+ /auto [task] Auto-heal with test/build loop
881
+ /debug [error] Auto-debug and verify a failure
952
882
 
953
883
  ${colors.white}Tools:${colors.reset}
954
884
  /read <file> Read file
@@ -1150,6 +1080,8 @@ ${colors.reset}
1150
1080
  const totalUsage = {};
1151
1081
  const toolSignatureHistory = [];
1152
1082
  const executionProfile = this.selectExecutionProfile(messages, { enableTools: true });
1083
+ const requireToolEvidence = this.actionRequiresTools(messages);
1084
+ let noToolActionRetries = 0;
1153
1085
  try {
1154
1086
  for (let i = 0; i < 8; i++) {
1155
1087
  if (this.isCancelled) throw new Error('AbortError');
@@ -1157,6 +1089,7 @@ ${colors.reset}
1157
1089
  provider: executionProfile.provider,
1158
1090
  model: executionProfile.model,
1159
1091
  enableTools: true,
1092
+ requireToolEvidence: requireToolEvidence && !usedTools,
1160
1093
  }, startedAt, totalUsage);
1161
1094
 
1162
1095
  const assistantMsg = turn.assistantMsg || {};
@@ -1167,6 +1100,25 @@ ${colors.reset}
1167
1100
  }
1168
1101
 
1169
1102
  if (toolCalls.length === 0) {
1103
+ if (turn.finishReason === 'tool_evidence_required') {
1104
+ noToolActionRetries++;
1105
+ if (noToolActionRetries > 2) {
1106
+ finalContent = 'Chưa thực hiện được: model trả lời mà không dùng tool nên Winter đã chặn để tránh báo xạo.';
1107
+ console.log(`\n${colors.yellow}${finalContent}${colors.reset}\n`);
1108
+ reachedToolLimit = false;
1109
+ break;
1110
+ }
1111
+ messages.push({
1112
+ role: 'assistant',
1113
+ content: assistantMsg.content || '',
1114
+ });
1115
+ messages.push({
1116
+ role: 'user',
1117
+ content: this.buildToolEvidenceCorrection(messages),
1118
+ });
1119
+ finalContent = '';
1120
+ continue;
1121
+ }
1170
1122
  if (turn.finishReason === 'length') {
1171
1123
  console.log(`\n${colors.yellow}ℹ Phản hồi bị cắt cụt do hết token. Đang tự động tiếp tục...${colors.reset}`);
1172
1124
  messages.push({
@@ -1304,6 +1256,51 @@ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls).
1304
1256
  return { finalContent, usedTools };
1305
1257
  }
1306
1258
 
1259
+ getLatestUserText(messages = []) {
1260
+ const list = Array.isArray(messages) ? messages : [{ role: 'user', content: String(messages || '') }];
1261
+ for (let i = list.length - 1; i >= 0; i--) {
1262
+ const message = list[i];
1263
+ if (message?.role && message.role !== 'user') continue;
1264
+ const content = message?.content;
1265
+ if (typeof content === 'string') return content;
1266
+ if (Array.isArray(content)) {
1267
+ return content.map(part => part?.text || '').filter(Boolean).join('\n');
1268
+ }
1269
+ }
1270
+ return '';
1271
+ }
1272
+
1273
+ actionRequiresTools(messages = []) {
1274
+ const text = this.getLatestUserText(messages).toLowerCase();
1275
+ if (!text.trim()) return false;
1276
+
1277
+ 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)\b|(?:sửa|fix|làm|tạo|ghi|đọc|xóa|xoá|chạy|test|build|commit|push|publish|kiểm tra|check|cài|thêm|đổi|sửa lỗi|refactor|review|soát|quét|tìm|áp dụng)/i;
1278
+ const targetPattern = /\b(file|repo|project|code|src|test|build|git|npm|node|folder|directory|cli|tool|provider|model|config|readme|package\.json)\b|[A-Za-z]:[\\/]|\.js\b|\.ts\b|\.tsx\b|\.json\b|\.md\b|(?:dự án|mã|thư mục|tệp|file|cấu hình|lỗi|chức năng|tool|provider|model)/i;
1279
+ const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|tại sao|vì sao|là gì|có nên|có phải)\b/i;
1280
+
1281
+ if (pureQuestionPattern.test(text) && !actionPattern.test(text)) return false;
1282
+ return actionPattern.test(text) && targetPattern.test(text);
1283
+ }
1284
+
1285
+ responseNeedsToolEvidence(content = '') {
1286
+ const text = String(content || '').toLowerCase();
1287
+ if (!text.trim()) return false;
1288
+
1289
+ 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)/i;
1290
+ if (clarification.test(text)) return false;
1291
+ return true;
1292
+ }
1293
+
1294
+ buildToolEvidenceCorrection(messages = []) {
1295
+ const request = this.getLatestUserText(messages);
1296
+ return [
1297
+ 'Runtime correction: the user requested an action that requires tool evidence.',
1298
+ 'Your previous response did not use any tool, so it was blocked to avoid falsely claiming completion.',
1299
+ 'Now use the available tools to inspect/edit/run/check as needed. Do not say the task is done until a tool result proves it.',
1300
+ `Original user request: ${request}`,
1301
+ ].join('\n');
1302
+ }
1303
+
1307
1304
  async requestAssistantTurn(messages, options, startedAt, totalUsage) {
1308
1305
  if (typeof this.ai.streamRequest === 'function') {
1309
1306
  try {
@@ -1321,6 +1318,9 @@ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls).
1321
1318
  const finishReason = response.choices?.[0]?.finish_reason;
1322
1319
 
1323
1320
  if (assistantMsg.content && toolCalls.length === 0) {
1321
+ if (options?.requireToolEvidence && this.responseNeedsToolEvidence(assistantMsg.content)) {
1322
+ return { assistantMsg, toolCalls, finalContent: '', finishReason: 'tool_evidence_required' };
1323
+ }
1324
1324
  this.printAssistantAnswer(assistantMsg.content, startedAt, totalUsage);
1325
1325
  return { assistantMsg, toolCalls, finalContent: assistantMsg.content, finishReason };
1326
1326
  }
@@ -1389,6 +1389,14 @@ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls).
1389
1389
  const visibleContent = inlineToolExtraction.content || content;
1390
1390
 
1391
1391
  if (bufferToolModeContent && toolCalls.length === 0 && visibleContent) {
1392
+ if (options?.requireToolEvidence && this.responseNeedsToolEvidence(visibleContent)) {
1393
+ return {
1394
+ assistantMsg: { content: visibleContent },
1395
+ toolCalls,
1396
+ finalContent: '',
1397
+ finishReason: 'tool_evidence_required',
1398
+ };
1399
+ }
1392
1400
  this.printAssistantAnswer(visibleContent, startedAt, totalUsage);
1393
1401
  return {
1394
1402
  assistantMsg: { content: visibleContent },
@@ -1576,8 +1584,9 @@ ${colors.yellow}ℹ AI tool loop detected (3 consecutive identical tool calls).
1576
1584
  if (query === '/') {
1577
1585
  const preferred = [
1578
1586
  '/help', '/exit', '/pwd', '/cd',
1579
- '/provider', '/model', '/models', '/providers',
1580
- '/read', '/write', '/glob', '/grep', '/bash',
1587
+ '/provider', '/model', '/models', '/providers',
1588
+ '/auto', '/debug', '/swe',
1589
+ '/read', '/write', '/glob', '/grep', '/bash',
1581
1590
  '/codex', '/claude', '/karpathy', '/agents',
1582
1591
  '/resources', '/designs', '/skills',
1583
1592
  ];
@@ -0,0 +1,130 @@
1
+ import { execFileSync } from 'child_process';
2
+
3
+ let cachedDefaultProfile = null;
4
+ let cachedParentProcessName = null;
5
+
6
+ export function getHostOs(platform = process.platform) {
7
+ if (platform === 'win32') return 'Windows';
8
+ if (platform === 'darwin') return 'macOS';
9
+ if (platform === 'linux') return 'Linux';
10
+ return platform;
11
+ }
12
+
13
+ export function detectTerminalApp(env = process.env, platform = process.platform) {
14
+ if (env.WT_SESSION) return 'Windows Terminal';
15
+ if (env.TERM_PROGRAM) return env.TERM_PROGRAM;
16
+ if (env.VSCODE_PID || env.TERM_PROGRAM === 'vscode') return 'VS Code integrated terminal';
17
+ if (env.ConEmuANSI || env.ConEmuBuild) return 'ConEmu/Cmder';
18
+ if (env.TERMINUS_SUBLIME || env.TERMINUS_SESSION) return 'Terminus';
19
+ if (env.TERM) return env.TERM;
20
+ if (platform === 'win32') return 'Windows console host';
21
+ return 'unknown';
22
+ }
23
+
24
+ export function detectCurrentShell(env = process.env, platform = process.platform) {
25
+ if (platform !== 'win32') {
26
+ return {
27
+ name: env.SHELL ? env.SHELL.split(/[\\/]/).pop() : 'sh',
28
+ kind: env.SHELL ? env.SHELL.split(/[\\/]/).pop() : 'sh',
29
+ source: 'SHELL',
30
+ path: env.SHELL || '',
31
+ };
32
+ }
33
+
34
+ const shellPath = env.SHELL || '';
35
+ const lowerShell = shellPath.toLowerCase();
36
+ const parentName = detectParentProcessName(env, platform).toLowerCase();
37
+ const parentProcess = [
38
+ env.WINTER_PARENT_PROCESS,
39
+ env.npm_config_script_shell,
40
+ parentName,
41
+ ].filter(Boolean).join(' ').toLowerCase();
42
+
43
+ if (lowerShell.includes('pwsh') || parentProcess.includes('pwsh')) {
44
+ return { name: 'pwsh', kind: 'powershell', source: 'env', path: shellPath };
45
+ }
46
+ if (lowerShell.includes('powershell') || parentProcess.includes('powershell')) {
47
+ return { name: 'powershell.exe', kind: 'powershell', source: 'env', path: shellPath };
48
+ }
49
+ if (lowerShell.includes('cmd.exe') || parentProcess.includes('cmd.exe')) {
50
+ return { name: 'cmd.exe', kind: 'cmd', source: 'parent/env', path: shellPath || env.ComSpec || '' };
51
+ }
52
+ return {
53
+ name: parentName || 'unknown Windows shell',
54
+ kind: 'unknown',
55
+ source: parentName ? 'parent' : 'unknown',
56
+ path: shellPath || env.ComSpec || '',
57
+ };
58
+ }
59
+
60
+ export function detectParentProcessName(env = process.env, platform = process.platform) {
61
+ if (env.WINTER_PARENT_PROCESS) return env.WINTER_PARENT_PROCESS;
62
+ if (platform !== 'win32') return '';
63
+ if (env === process.env && cachedParentProcessName !== null) return cachedParentProcessName;
64
+
65
+ try {
66
+ const script = `$p=(Get-CimInstance Win32_Process -Filter "ProcessId=${process.ppid}" -ErrorAction SilentlyContinue); if($p){$p.Name}`;
67
+ const parentName = execFileSync('powershell.exe', ['-NoProfile', '-Command', script], {
68
+ encoding: 'utf8',
69
+ windowsHide: true,
70
+ timeout: 1500,
71
+ stdio: ['ignore', 'pipe', 'ignore'],
72
+ }).trim();
73
+ if (env === process.env) cachedParentProcessName = parentName;
74
+ return parentName;
75
+ } catch {
76
+ if (env === process.env) cachedParentProcessName = '';
77
+ return '';
78
+ }
79
+ }
80
+
81
+ export function getRuntimeEnvironment(env = process.env, platform = process.platform) {
82
+ if (env === process.env && platform === process.platform && cachedDefaultProfile) {
83
+ return cachedDefaultProfile;
84
+ }
85
+
86
+ const shell = detectCurrentShell(env, platform);
87
+ const terminalApp = detectTerminalApp(env, platform);
88
+ const defaultExecutionShell = platform === 'win32'
89
+ ? (shell.kind === 'cmd' ? 'cmd' : 'powershell')
90
+ : (env.SHELL || 'sh');
91
+
92
+ const profile = {
93
+ hostOs: getHostOs(platform),
94
+ platform,
95
+ arch: process.arch,
96
+ terminalApp,
97
+ shell,
98
+ defaultExecutionShell,
99
+ isWindows: platform === 'win32',
100
+ };
101
+
102
+ if (env === process.env && platform === process.platform) {
103
+ cachedDefaultProfile = profile;
104
+ }
105
+
106
+ return profile;
107
+ }
108
+
109
+ export function formatRuntimeEnvironmentSummary(profile = getRuntimeEnvironment()) {
110
+ const shellPath = profile.shell?.path ? ` (${profile.shell.path})` : '';
111
+ const shellRule = profile.isWindows
112
+ ? [
113
+ 'Bash tool shell rules:',
114
+ '- default/auto executes PowerShell unless the command clearly uses cmd.exe syntax.',
115
+ '- use shell:"powershell" for PowerShell cmdlets, pipes, Get-ChildItem, Select-String, npm/node commands.',
116
+ '- use shell:"cmd" for cmd builtins such as type, copy, del, dir /b, echo foo>file, && chains.',
117
+ '- do not mix PowerShell-only syntax with shell:"cmd" or cmd-only redirection with shell:"powershell".',
118
+ ].join('\n')
119
+ : 'Bash tool shell rule: use the native POSIX shell; leave shell unspecified unless a specific shell is required.';
120
+
121
+ return [
122
+ `Host OS: ${profile.hostOs}`,
123
+ `Node platform: ${profile.platform}`,
124
+ `CPU arch: ${profile.arch}`,
125
+ `Terminal app: ${profile.terminalApp}`,
126
+ `Detected current shell: ${profile.shell?.name || 'unknown'} [${profile.shell?.kind || 'unknown'}]${shellPath}`,
127
+ `Bash default execution shell: ${profile.defaultExecutionShell}`,
128
+ shellRule,
129
+ ].join('\n');
130
+ }
@@ -14,6 +14,9 @@ export const SLASH_COMMANDS = [
14
14
  { cmd: '/tasks', desc: 'List tasks' },
15
15
  { cmd: '/task', desc: 'Create task', usage: '/task <description>' },
16
16
  { cmd: '/agent', desc: 'Launch subagent', usage: '/agent <task>' },
17
+ { cmd: '/auto', desc: 'Auto-heal with test/build loop', usage: '/auto [task]' },
18
+ { cmd: '/debug', desc: 'Auto-debug errors with tool verification', usage: '/debug [error or task]' },
19
+ { cmd: '/tdd', desc: 'Alias for auto-healing loop', usage: '/tdd [task]' },
17
20
  { cmd: '/read', desc: 'Read file', usage: '/read <file>' },
18
21
  { cmd: '/write', desc: 'Write file', usage: '/write <file> <content>' },
19
22
  { cmd: '/glob', desc: 'Find files', usage: '/glob <pattern>' },
@@ -47,4 +50,3 @@ export const SLASH_COMMANDS = [
47
50
  { cmd: '/exit', desc: 'Exit Winter' },
48
51
  { cmd: '/quit', desc: 'Exit Winter' },
49
52
  ];
50
-
@@ -40,12 +40,40 @@ export class SessionManager {
40
40
  console.log(`\x1b[33m⚠ Không tìm thấy session ${options.sessionId}, tạo session mới...\x1b[0m`);
41
41
  }
42
42
 
43
- // Luôn tạo session mới nếu không yêu cầu load hoặc load thất bại
43
+ // Mặc định resume session mới nhất của đúng project để /memories không bị rỗng sau restart.
44
+ if (options.resume !== false && !options.newSession) {
45
+ const success = await this.loadLatestProjectSession(options.project || process.cwd());
46
+ if (success) {
47
+ await this.rememberProject(this.currentSession?.project || options.project || process.cwd());
48
+ this.initialized = true;
49
+ return;
50
+ }
51
+ }
52
+
44
53
  await this.newSession(options);
45
54
  await this.rememberProject(options.project || this.currentSession?.project || process.cwd());
46
55
  this.initialized = true;
47
56
  }
48
57
 
58
+ normalizeProjectPath(projectPath) {
59
+ if (!projectPath) return '';
60
+ const normalized = path.resolve(projectPath);
61
+ return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
62
+ }
63
+
64
+ hydrateSession(session) {
65
+ this.currentSession = session;
66
+ this.context = session.context || {};
67
+ this.plans = session.plans || [];
68
+ this.memory = session.memory || [];
69
+ this.currentSession.history = session.history || [];
70
+ }
71
+
72
+ async writeCurrentPointer(sessionId) {
73
+ const currentPath = path.join(this.sessionsDir, 'active', 'current.json');
74
+ await fs.writeFile(currentPath, JSON.stringify({ id: sessionId }));
75
+ }
76
+
49
77
  async loadSession(sessionId) {
50
78
  const sessionPath = path.join(this.sessionsDir, 'active', `${sessionId}.json`);
51
79
  try {
@@ -64,6 +92,43 @@ export class SessionManager {
64
92
  }
65
93
  }
66
94
 
95
+ async loadLatestProjectSession(projectPath) {
96
+ const activeDir = path.join(this.sessionsDir, 'active');
97
+ const targetProject = this.normalizeProjectPath(projectPath);
98
+ if (!targetProject) return false;
99
+
100
+ try {
101
+ const files = await fs.readdir(activeDir);
102
+ const candidates = [];
103
+
104
+ for (const file of files) {
105
+ if (!file.endsWith('.json') || file === 'current.json') continue;
106
+
107
+ try {
108
+ const sessionData = await fs.readFile(path.join(activeDir, file), 'utf8');
109
+ const session = JSON.parse(sessionData);
110
+ if (this.normalizeProjectPath(session.project) !== targetProject) continue;
111
+
112
+ candidates.push({
113
+ session,
114
+ time: Date.parse(session.updatedAt || session.createdAt || 0) || 0,
115
+ });
116
+ } catch {
117
+ // Skip corrupt or partially-written session files.
118
+ }
119
+ }
120
+
121
+ candidates.sort((a, b) => b.time - a.time);
122
+ if (candidates.length === 0) return false;
123
+
124
+ this.hydrateSession(candidates[0].session);
125
+ await this.writeCurrentPointer(this.currentSession.id);
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
67
132
  async newSession(options = {}) {
68
133
  const sessionId = crypto.randomUUID();
69
134
  const sessionPath = path.join(this.sessionsDir, 'active', `${sessionId}.json`);
@@ -20,6 +20,7 @@ import { AgentTool } from './agent.js';
20
20
  import { InsertTextTool } from './insert-text.js';
21
21
  import { StrReplaceAllTool } from './str-replace-all.js';
22
22
  import { WebArchiveTool } from './web-archive.js';
23
+ import { formatRuntimeEnvironmentSummary, getRuntimeEnvironment } from '../cli/runtime-env.js';
23
24
 
24
25
  const execAsync = promisify(exec);
25
26
  const execFileAsync = promisify(execFile);
@@ -44,28 +45,7 @@ export class ToolExecutor {
44
45
  }
45
46
 
46
47
  getRuntimeEnvironmentSummary() {
47
- const hostOs = process.platform === 'win32'
48
- ? 'Windows'
49
- : process.platform === 'darwin'
50
- ? 'macOS'
51
- : process.platform === 'linux'
52
- ? 'Linux'
53
- : process.platform;
54
-
55
- const currentShell = process.platform === 'win32'
56
- ? (process.env.PSModulePath ? 'powershell-capable' : 'cmd/unknown')
57
- : (process.env.SHELL || 'bash/sh');
58
-
59
- const shellGuidance = process.platform === 'win32'
60
- ? 'Use shell:"powershell" for PowerShell cmdlets, shell:"cmd" for cmd.exe syntax, and shell:"auto" when unsure.'
61
- : 'Use the native POSIX shell on non-Windows hosts and leave shell unspecified unless a specific shell is required.';
62
-
63
- return [
64
- `Host OS: ${hostOs}`,
65
- `Node platform: ${process.platform}`,
66
- `Current shell hint: ${currentShell}`,
67
- `Shell rule: ${shellGuidance}`,
68
- ].join('\n');
48
+ return formatRuntimeEnvironmentSummary(getRuntimeEnvironment());
69
49
  }
70
50
 
71
51
  getToolDefinitions() {
@@ -1057,6 +1037,9 @@ export class ToolExecutor {
1057
1037
  }
1058
1038
 
1059
1039
  try {
1040
+ const executedShell = process.platform === 'win32'
1041
+ ? (requestedShell === 'auto' ? this.detectWindowsShell(command) : requestedShell)
1042
+ : (process.env.SHELL || 'native');
1060
1043
  const { stdout, stderr } = process.platform === 'win32'
1061
1044
  ? await this.execWindowsCommand(command, cwd, timeout, requestedShell)
1062
1045
  : await execAsync(command, { cwd, timeout, shell: true, maxBuffer: 10 * 1024 * 1024 });
@@ -1064,7 +1047,8 @@ export class ToolExecutor {
1064
1047
  success: true,
1065
1048
  stdout: stdout || '',
1066
1049
  stderr: stderr || '',
1067
- exitCode: 0
1050
+ exitCode: 0,
1051
+ shell: executedShell,
1068
1052
  };
1069
1053
  } catch (error) {
1070
1054
  return {