winter-super-cli 2026.6.5 → 2026.6.7

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.
@@ -471,7 +471,7 @@ export class ToolExecutor {
471
471
  const cwd = context.cwd || this.projectPath;
472
472
  const resolvedPath = (p) => this.resolveInputPath(p, cwd);
473
473
 
474
- const preflight = this.preflightValidateToolArgs(toolName, input, { cwd });
474
+ const preflight = await this.preflightValidateToolArgs(toolName, input, { cwd });
475
475
  if (preflight?.success === false) {
476
476
  return preflight;
477
477
  }
@@ -568,7 +568,7 @@ export class ToolExecutor {
568
568
  }
569
569
  }
570
570
 
571
- preflightValidateToolArgs(toolName, input, { cwd } = {}) {
571
+ async preflightValidateToolArgs(toolName, input, { cwd } = {}) {
572
572
  const args = (input && typeof input === 'object' && !Array.isArray(input)) ? input : {};
573
573
  const pick = (...keys) => {
574
574
  for (const key of keys) {
@@ -619,6 +619,16 @@ export class ToolExecutor {
619
619
  if (!cmd) {
620
620
  return { success: false, error: 'command is required', recovery: 'Example: Bash {"command":"npm test"}' };
621
621
  }
622
+ const missingScript = await this.findMissingNpmScript(cmd, cwd);
623
+ if (missingScript) {
624
+ return {
625
+ success: false,
626
+ error: `npm script "${missingScript.script}" does not exist in package.json`,
627
+ recovery: missingScript.available.length
628
+ ? `Use an existing script: ${missingScript.available.map(name => `npm run ${name}`).join(', ')}`
629
+ : 'No package scripts are defined. Inspect package.json before running npm scripts.',
630
+ };
631
+ }
622
632
  const next = { command: cmd };
623
633
  if (typeof args.timeout !== 'undefined') next.timeout = args.timeout;
624
634
  if (typeof args.shell !== 'undefined') next.shell = args.shell;
@@ -706,6 +716,21 @@ export class ToolExecutor {
706
716
  return { success: true };
707
717
  }
708
718
 
719
+ async findMissingNpmScript(command, cwd) {
720
+ const match = String(command || '').trim().match(/^(?:npm|pnpm|yarn|bun)\s+run\s+([A-Za-z0-9:_-]+)(?:\s|$)/i);
721
+ if (!match) return null;
722
+ const script = match[1];
723
+ try {
724
+ const packageJsonPath = path.join(cwd || this.projectPath, 'package.json');
725
+ const pkg = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
726
+ const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
727
+ if (Object.prototype.hasOwnProperty.call(scripts, script)) return null;
728
+ return { script, available: Object.keys(scripts).sort() };
729
+ } catch {
730
+ return null;
731
+ }
732
+ }
733
+
709
734
  redactToolInput(input) {
710
735
  if (!input || typeof input !== 'object') return input;
711
736
  const secretPattern = /(api[-_]?key|auth[-_]?token|access[-_]?token|refresh[-_]?token|secret|password)/i;
@@ -1012,10 +1037,60 @@ export class ToolExecutor {
1012
1037
  size: content.length
1013
1038
  };
1014
1039
  } catch (error) {
1015
- return { success: false, error: error.message, path: filePath };
1040
+ if (error?.code === 'ENOENT') {
1041
+ const suggestions = await this.findNearbyPathSuggestions(filePath);
1042
+ return {
1043
+ success: false,
1044
+ error: `File not found: ${filePath}`,
1045
+ code: 'ENOENT',
1046
+ path: filePath,
1047
+ suggestions,
1048
+ recovery: [
1049
+ 'Do not retry the same missing path.',
1050
+ 'Use Glob or Grep to discover the real file path before reading again.',
1051
+ suggestions.length ? `Nearby candidates: ${suggestions.join(', ')}` : `Search from project root: ${this.projectPath}`,
1052
+ ].join(' '),
1053
+ };
1054
+ }
1055
+ return { success: false, error: error.message, code: error?.code, path: filePath };
1016
1056
  }
1017
1057
  }
1018
1058
 
1059
+ async findNearbyPathSuggestions(filePath, limit = 8) {
1060
+ if (!filePath) return [];
1061
+ const targetName = path.basename(filePath).toLowerCase();
1062
+ const targetStem = targetName.replace(/\.[^.]+$/, '');
1063
+ if (!targetName) return [];
1064
+
1065
+ const candidates = [];
1066
+ const ignored = new Set(['node_modules', '.git', 'dist', 'build', '.winter', '.claude', 'VSCode-win32-x64', 'vscode-main']);
1067
+ const walk = async (dir, depth = 0) => {
1068
+ if (depth > 5 || candidates.length >= limit * 4) return;
1069
+ let entries = [];
1070
+ try {
1071
+ entries = await fs.readdir(dir, { withFileTypes: true });
1072
+ } catch {
1073
+ return;
1074
+ }
1075
+
1076
+ for (const entry of entries) {
1077
+ if (ignored.has(entry.name)) continue;
1078
+ const fullPath = path.join(dir, entry.name);
1079
+ if (entry.isDirectory()) {
1080
+ await walk(fullPath, depth + 1);
1081
+ continue;
1082
+ }
1083
+ const name = entry.name.toLowerCase();
1084
+ if (name === targetName || name.includes(targetStem) || targetStem.includes(name.replace(/\.[^.]+$/, ''))) {
1085
+ candidates.push(path.relative(this.projectPath, fullPath) || fullPath);
1086
+ }
1087
+ }
1088
+ };
1089
+
1090
+ await walk(this.projectPath);
1091
+ return [...new Set(candidates)].slice(0, limit);
1092
+ }
1093
+
1019
1094
  async backupFile(filePath) {
1020
1095
  try {
1021
1096
  const fsMod = await import('fs/promises');