jinzd-ai-cli 0.4.295 → 0.4.297

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.
Files changed (32) hide show
  1. package/dist/{batch-3YJGOO27.js → batch-YFQ34DHW.js} +2 -2
  2. package/dist/{chunk-PE3A74UB.js → chunk-3GIKUDYZ.js} +3 -3
  3. package/dist/{chunk-EI5O25IE.js → chunk-56AX43FT.js} +1 -1
  4. package/dist/{chunk-57OZ53J3.js → chunk-6PBWL3XI.js} +1 -1
  5. package/dist/{chunk-5S6TUBTA.js → chunk-CF6RC7OB.js} +3 -3
  6. package/dist/{chunk-ZEITAL5P.js → chunk-D7GLDXPQ.js} +1 -1
  7. package/dist/{chunk-VJQOSTNU.js → chunk-DPNKOXJF.js} +48 -11
  8. package/dist/{chunk-WYKSHBLL.js → chunk-EFHAMIQF.js} +203 -166
  9. package/dist/{chunk-4LQVBW2I.js → chunk-FZUMKY6C.js} +1 -1
  10. package/dist/{chunk-OFRXL4RX.js → chunk-GNJ3XLS3.js} +1 -1
  11. package/dist/{chunk-4VYLPFZJ.js → chunk-J6CYL6RN.js} +1 -1
  12. package/dist/{chunk-A6O7AMFD.js → chunk-LXORMKZ6.js} +1 -1
  13. package/dist/{chunk-TZE5CEJZ.js → chunk-NB2V7H5C.js} +1 -1
  14. package/dist/{chunk-NAMXORM7.js → chunk-O33BYKAD.js} +1 -1
  15. package/dist/{ci-ANIX7GOB.js → ci-AR2RLLJD.js} +4 -4
  16. package/dist/{ci-format-PSAAKM7J.js → ci-format-S7RRDV4A.js} +2 -2
  17. package/dist/{constants-LUKORJS3.js → constants-VZBWOKZZ.js} +1 -1
  18. package/dist/{constants-IMX6ST62.js → constants-W52VYKDV.js} +1 -1
  19. package/dist/{doctor-cli-ALBAFMWO.js → doctor-cli-VX36CZND.js} +4 -4
  20. package/dist/electron-server.js +93 -19
  21. package/dist/{hub-YHUOMU6E.js → hub-LJL3X7KR.js} +1 -1
  22. package/dist/index.js +17 -17
  23. package/dist/{persistent-memory-M24MHHKF.js → persistent-memory-CMXW4WSP.js} +2 -2
  24. package/dist/{persistent-memory-QA46MVVY.js → persistent-memory-WEZMJBIV.js} +2 -2
  25. package/dist/{pr-656V2Z3H.js → pr-GTDFMNKY.js} +4 -4
  26. package/dist/{run-tests-JGKTKLMH.js → run-tests-ILHN2PYU.js} +2 -2
  27. package/dist/{run-tests-PY4EEU2J.js → run-tests-MTWA2NNV.js} +2 -2
  28. package/dist/{server-KBWU6U33.js → server-QXP5NHE7.js} +13 -13
  29. package/dist/{server-G2IOJCTM.js → server-WFXEEX2E.js} +5 -5
  30. package/dist/{task-orchestrator-JNGGZIOX.js → task-orchestrator-HZPIN4PT.js} +5 -5
  31. package/dist/{usage-YBWBEOBO.js → usage-UEN6LLOG.js} +2 -2
  32. package/package.json +3 -1
@@ -4,12 +4,12 @@ import {
4
4
  } from "./chunk-RS7SKYTS.js";
5
5
  import {
6
6
  ConfigManager
7
- } from "./chunk-NAMXORM7.js";
7
+ } from "./chunk-O33BYKAD.js";
8
8
  import "./chunk-TU3L3PW7.js";
9
9
  import {
10
10
  atomicWriteFileSync
11
11
  } from "./chunk-IW3Q7AE5.js";
12
- import "./chunk-57OZ53J3.js";
12
+ import "./chunk-6PBWL3XI.js";
13
13
 
14
14
  // src/cli/batch.ts
15
15
  import Anthropic from "@anthropic-ai/sdk";
@@ -8,13 +8,13 @@ import {
8
8
  listHooks,
9
9
  listInstalledPlugins,
10
10
  resetStats
11
- } from "./chunk-4LQVBW2I.js";
11
+ } from "./chunk-FZUMKY6C.js";
12
12
  import {
13
13
  ProviderRegistry
14
14
  } from "./chunk-FBOVBL7P.js";
15
15
  import {
16
16
  ConfigManager
17
- } from "./chunk-NAMXORM7.js";
17
+ } from "./chunk-O33BYKAD.js";
18
18
  import {
19
19
  getGitRoot
20
20
  } from "./chunk-HOSJZMQS.js";
@@ -26,7 +26,7 @@ import {
26
26
  MCP_PROJECT_CONFIG_NAME,
27
27
  MEMORY_FILE_NAME,
28
28
  VERSION
29
- } from "./chunk-57OZ53J3.js";
29
+ } from "./chunk-6PBWL3XI.js";
30
30
 
31
31
  // src/diagnostics/doctor-report.ts
32
32
  import { existsSync as existsSync3, statSync as statSync2 } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-57OZ53J3.js";
4
+ } from "./chunk-6PBWL3XI.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.295";
4
+ var VERSION = "0.4.297";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  evaluateCiGate,
4
4
  normalizeCiThresholds
5
- } from "./chunk-OFRXL4RX.js";
5
+ } from "./chunk-GNJ3XLS3.js";
6
6
  import {
7
7
  buildReviewPrompt,
8
8
  buildSecurityReviewPrompt,
@@ -16,10 +16,10 @@ import {
16
16
  } from "./chunk-FBOVBL7P.js";
17
17
  import {
18
18
  ConfigManager
19
- } from "./chunk-NAMXORM7.js";
19
+ } from "./chunk-O33BYKAD.js";
20
20
  import {
21
21
  VERSION
22
- } from "./chunk-57OZ53J3.js";
22
+ } from "./chunk-6PBWL3XI.js";
23
23
 
24
24
  // src/cli/ci.ts
25
25
  import { execFileSync } from "child_process";
@@ -1,5 +1,5 @@
1
1
  // src/core/constants.ts
2
- var VERSION = "0.4.295";
2
+ var VERSION = "0.4.297";
3
3
  var APP_NAME = "ai-cli";
4
4
  var CONFIG_DIR_NAME = ".aicli";
5
5
  var CONFIG_FILE_NAME = "config.json";
@@ -11,7 +11,7 @@ import {
11
11
  searchPersistentMemories,
12
12
  touchMemoryReferences,
13
13
  updateMemoryEntry
14
- } from "./chunk-4VYLPFZJ.js";
14
+ } from "./chunk-J6CYL6RN.js";
15
15
  import {
16
16
  indexProject
17
17
  } from "./chunk-4DEMGCK4.js";
@@ -30,7 +30,7 @@ import {
30
30
  runHook,
31
31
  runLifecycleHooks,
32
32
  runTool
33
- } from "./chunk-4LQVBW2I.js";
33
+ } from "./chunk-FZUMKY6C.js";
34
34
  import {
35
35
  buildTruncatedArgumentsError,
36
36
  getDangerLevel,
@@ -50,7 +50,7 @@ import {
50
50
  } from "./chunk-4BKXL7SM.js";
51
51
  import {
52
52
  runTestsTool
53
- } from "./chunk-EI5O25IE.js";
53
+ } from "./chunk-56AX43FT.js";
54
54
  import {
55
55
  CONFIG_DIR_NAME,
56
56
  DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
@@ -58,7 +58,7 @@ import {
58
58
  SUBAGENT_ALLOWED_TOOLS,
59
59
  SUBAGENT_DEFAULT_MAX_ROUNDS,
60
60
  SUBAGENT_MAX_ROUNDS_LIMIT
61
- } from "./chunk-57OZ53J3.js";
61
+ } from "./chunk-6PBWL3XI.js";
62
62
 
63
63
  // src/tools/shell-pool.ts
64
64
  import { spawn as spawn2 } from "child_process";
@@ -532,7 +532,8 @@ Important rules:
532
532
  3. Multiple commands can be combined with semicolons in a single call to reduce rounds.
533
533
  4. To delete directories, use Remove-Item -Recurse (the system will automatically optimize to a more reliable method).
534
534
  5. IMPORTANT: On Windows, "curl" is an alias for Invoke-WebRequest and does NOT support curl flags like -s, -X, -H. Use Invoke-RestMethod instead for HTTP requests. Example: Invoke-RestMethod -Uri "http://localhost:3000/api/health" -Method Get
535
- 6. NEVER run long-running / never-exiting commands through bash \u2014 they block until timeout. This includes starting an Android emulator (emulator -avd X), streaming logs (adb logcat), and dev servers (node server.js, npm run dev, metro / react-native start). Use the 'task_create' tool to run these in the background, then poll status with short bash calls (e.g. adb shell getprop sys.boot_completed).` : `Execute commands in ${SHELL2}.
535
+ 6. NEVER run long-running / never-exiting commands through bash \u2014 they block until timeout. This includes starting an Android emulator (emulator -avd X), streaming logs (adb logcat), and dev servers (node server.js, npm run dev, metro / react-native start). Use the 'task_create' tool to run these in the background, then poll status with short bash calls (e.g. adb shell getprop sys.boot_completed).
536
+ 7. CRITICAL \u2014 this is PowerShell, NOT bash: \\ is NOT an escape character. Writing \\" inside a double-quoted string CLOSES the string and silently splits your command into fragments \u2014 it often still exits 0 with no output, so it looks like it worked when it did not. To escape a quote use \`" or "". For anything with nested quoting (especially ssh + psql -c "SELECT ..."), do NOT try to escape your way out: write the payload to a file with the 'write_file' tool, 'scp' it to the host, and run it with a single level of quoting (e.g. ssh host "psql -d db -f /tmp/x.sql").` : `Execute commands in ${SHELL2}.
536
537
  Important rules:
537
538
  1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir && ls" or "mkdir -p mydir && touch mydir/file.txt".
538
539
  2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
@@ -650,7 +651,7 @@ ${combined || formatFailureFallback(status)}
650
651
  }
651
652
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
652
653
  updateCwdFromCommand(command, effectiveCwd);
653
- return stdout || "(command completed with no output)";
654
+ return stdout || emptyOutputResult(command);
654
655
  }
655
656
  try {
656
657
  const { stdout, stderr, status, signal, timedOut, aborted } = await runShell(
@@ -687,7 +688,7 @@ ${combined || formatFailureFallback(status)}
687
688
  updateCwdFromCommand(command, effectiveCwd);
688
689
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
689
690
  const result = Buffer.isBuffer(stdout) ? IS_WINDOWS3 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
690
- return result || "(command completed with no output)";
691
+ return result || emptyOutputResult(command);
691
692
  } catch (err) {
692
693
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
693
694
  if (err && typeof err === "object") {
@@ -768,6 +769,30 @@ function fixWindowsDeleteCommand(command) {
768
769
  }
769
770
  );
770
771
  }
772
+ function detectPowerShellBackslashQuote(command) {
773
+ if (!IS_WINDOWS3) return false;
774
+ let inSingle = false;
775
+ let inDouble = false;
776
+ for (let i = 0; i < command.length; i++) {
777
+ const ch = command[i];
778
+ if (ch === "\\" && command[i + 1] === '"') {
779
+ if (inDouble && !inSingle) return true;
780
+ i++;
781
+ continue;
782
+ }
783
+ if (ch === "'" && !inDouble) inSingle = !inSingle;
784
+ else if (ch === '"' && !inSingle) inDouble = !inDouble;
785
+ }
786
+ return false;
787
+ }
788
+ var BACKSLASH_QUOTE_SILENT_WARNING = `
789
+
790
+ \u26A0 WARNING: this command contains \\" inside a double-quoted string, but PowerShell does NOT treat \\ as an escape \u2014 that quote CLOSED the string and the command was split into fragments. It exited 0 with no output because the outer program (e.g. ssh) still ran, but it very likely did NOT run what you intended. Do NOT assume this succeeded.
791
+ Fix: write the payload to a local file with the 'write_file' tool, then 'scp' it over and run 'ssh host "psql -d <db> -f /tmp/x.sql"' (single level of quoting). Inside PowerShell strings, escape a quote with \`" or "" \u2014 never \\".`;
792
+ function emptyOutputResult(command) {
793
+ const base = "(command completed with no output)";
794
+ return detectPowerShellBackslashQuote(command) ? base + BACKSLASH_QUOTE_SILENT_WARNING : base;
795
+ }
771
796
  function buildErrorHint(command, stderr) {
772
797
  const hints = [];
773
798
  if (IS_WINDOWS3 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
@@ -814,6 +839,11 @@ function buildErrorHint(command, stderr) {
814
839
  `Hint: PostgreSQL says column "${colMissing[1]}" does not exist. Do NOT retry with a guess \u2014 query the actual schema first: \`psql -c "\\d <table>"\` or \`SELECT column_name FROM information_schema.columns WHERE table_name='<table>';\`. Common pitfall: not every table has a 'deleted' soft-delete column.`
815
840
  );
816
841
  }
842
+ if (detectPowerShellBackslashQuote(command) && !/^\s*ssh\b/.test(command) && /(syntax error|unexpected|ParserError|parser|意外的|语法|所在位置\s+行|EOF)/i.test(stderr)) {
843
+ hints.push(
844
+ `Hint: PowerShell does NOT treat \\ as an escape character \u2014 the \\" in this command closed the double-quoted string and split the command into fragments. Escape quotes with \`" or "" instead. Better: avoid nested quoting entirely \u2014 write the payload to a file with 'write_file', 'scp' it over, and run it with one level of quoting (e.g. ssh host "psql -d <db> -f /tmp/x.sql").`
845
+ );
846
+ }
817
847
  return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
818
848
  }
819
849
  function detectBlockingCommand(command) {
@@ -2474,7 +2504,8 @@ var ToolExecutor = class {
2474
2504
  return Promise.all(calls.map((call) => this.execute(call)));
2475
2505
  }
2476
2506
  console.log();
2477
- console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`));
2507
+ const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
2508
+ console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`) + roundBadge);
2478
2509
  console.log(theme.dim("\u2500".repeat(50)));
2479
2510
  for (let i = 0; i < calls.length; i++) {
2480
2511
  const call = calls[i];
@@ -2860,9 +2891,15 @@ var subAgentGuard = {
2860
2891
  active: false
2861
2892
  };
2862
2893
 
2894
+ // src/tools/text-metrics.ts
2895
+ function countTextLines(text) {
2896
+ if (text === "") return 0;
2897
+ return text.replace(/\n$/, "").split("\n").length;
2898
+ }
2899
+
2863
2900
  // src/tools/builtin/write-file.ts
2864
2901
  function countFileLines(filePath) {
2865
- return readFileSync5(filePath, "utf-8").split("\n").length;
2902
+ return countTextLines(readFileSync5(filePath, "utf-8"));
2866
2903
  }
2867
2904
  var writeFileTool = {
2868
2905
  definition: {
@@ -2921,7 +2958,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
2921
2958
  } else {
2922
2959
  atomicWriteFileSync(filePath, encoding === "utf-8" || encoding === "utf8" ? content : Buffer.from(content, encoding));
2923
2960
  }
2924
- const lines = content.split("\n").length;
2961
+ const lines = countTextLines(content);
2925
2962
  const bytes = Buffer.byteLength(content, encoding);
2926
2963
  const mode = appendMode ? "appended" : "written";
2927
2964
  void (async () => {
@@ -4777,7 +4814,7 @@ Any of these triggers means use save_last_response, NOT write_file:
4777
4814
  undoStack.push(filePath, `save_last_response: ${filePath}`);
4778
4815
  mkdirSync2(dirname4(filePath), { recursive: true });
4779
4816
  atomicWriteFileSync(filePath, content);
4780
- const lines = content.split("\n").length;
4817
+ const lines = countTextLines(content);
4781
4818
  return `File saved: ${filePath} (${lines} lines, ${Buffer.byteLength(content, "utf-8")} bytes)`;
4782
4819
  }
4783
4820
  };
@@ -16,7 +16,7 @@ import {
16
16
  setPreferredAgentName,
17
17
  truncateForPersist,
18
18
  undoStack
19
- } from "./chunk-VJQOSTNU.js";
19
+ } from "./chunk-DPNKOXJF.js";
20
20
  import {
21
21
  buildReviewPrompt,
22
22
  buildSecurityReviewPrompt
@@ -51,7 +51,7 @@ import {
51
51
  touchMemoryReferences,
52
52
  updateMemoryApproval,
53
53
  updateMemoryEntry
54
- } from "./chunk-4VYLPFZJ.js";
54
+ } from "./chunk-J6CYL6RN.js";
55
55
  import {
56
56
  theme
57
57
  } from "./chunk-5C76XDDF.js";
@@ -65,7 +65,7 @@ import {
65
65
  import {
66
66
  buildDoctorReport,
67
67
  formatDoctorReport
68
- } from "./chunk-PE3A74UB.js";
68
+ } from "./chunk-3GIKUDYZ.js";
69
69
  import {
70
70
  describePlugin,
71
71
  getActivePluginAssets,
@@ -80,7 +80,7 @@ import {
80
80
  trustHook,
81
81
  trustPlugin,
82
82
  untrustHook
83
- } from "./chunk-4LQVBW2I.js";
83
+ } from "./chunk-FZUMKY6C.js";
84
84
  import {
85
85
  isCleanDocumentBody,
86
86
  stripOuterCodeFence
@@ -109,7 +109,195 @@ import {
109
109
  MCP_TOOL_PREFIX,
110
110
  REPO_URL,
111
111
  VERSION
112
- } from "./chunk-57OZ53J3.js";
112
+ } from "./chunk-6PBWL3XI.js";
113
+
114
+ // src/skills/manager.ts
115
+ import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
116
+ import { join } from "path";
117
+
118
+ // src/skills/types.ts
119
+ import { readFileSync } from "fs";
120
+ import { basename } from "path";
121
+ function parseSimpleYaml(yaml) {
122
+ const result = {};
123
+ for (const line of yaml.split("\n")) {
124
+ const match = line.replace(/\r$/, "").match(/^(\w+)\s*:\s*(.+)$/);
125
+ if (match) {
126
+ result[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
127
+ }
128
+ }
129
+ return result;
130
+ }
131
+ function parseYamlArray(value) {
132
+ const bracketMatch = value.match(/^\[(.+)]$/);
133
+ if (bracketMatch) {
134
+ return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
135
+ }
136
+ return [value.trim()];
137
+ }
138
+ function parseSkillFile(filePath) {
139
+ let raw;
140
+ try {
141
+ raw = readFileSync(filePath, "utf-8");
142
+ } catch {
143
+ return null;
144
+ }
145
+ const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
146
+ if (!frontmatterMatch) {
147
+ return {
148
+ meta: {
149
+ name: basename(filePath, ".md"),
150
+ description: ""
151
+ },
152
+ content: raw.trim(),
153
+ filePath
154
+ };
155
+ }
156
+ const [, yaml, content] = frontmatterMatch;
157
+ const parsed = parseSimpleYaml(yaml);
158
+ return {
159
+ meta: {
160
+ name: parsed["name"] ?? basename(filePath, ".md"),
161
+ description: parsed["description"] ?? "",
162
+ tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
163
+ },
164
+ content: content.trim(),
165
+ filePath
166
+ };
167
+ }
168
+
169
+ // src/skills/manager.ts
170
+ var SKILL_CONTENT_WARN_CHARS_DEFAULT = 1e4;
171
+ var SkillManager = class {
172
+ skills = /* @__PURE__ */ new Map();
173
+ activeSkill = null;
174
+ skillsDir;
175
+ /** 超大技能文件警告阈值,由调用方从 config 传入;0 = 静默,undefined = 用默认 10000 */
176
+ warnThreshold;
177
+ constructor(skillsDir, warnThreshold) {
178
+ this.skillsDir = skillsDir;
179
+ this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
180
+ }
181
+ /** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
182
+ loadSkills(extraDirs = []) {
183
+ this.skills.clear();
184
+ this.loadSkillDir(this.skillsDir, true);
185
+ for (const dir of extraDirs) this.loadSkillDir(dir, false);
186
+ return this.skills.size;
187
+ }
188
+ loadSkillDir(dir, createIfMissing) {
189
+ if (!existsSync(dir)) {
190
+ if (createIfMissing) {
191
+ try {
192
+ mkdirSync(dir, { recursive: true });
193
+ } catch {
194
+ }
195
+ }
196
+ return;
197
+ }
198
+ let entries;
199
+ try {
200
+ entries = readdirSync(dir);
201
+ } catch {
202
+ return;
203
+ }
204
+ for (const entry of entries) {
205
+ let filePath;
206
+ const fullPath = join(dir, entry);
207
+ if (entry.endsWith(".md")) {
208
+ filePath = fullPath;
209
+ } else {
210
+ try {
211
+ if (statSync(fullPath).isDirectory()) {
212
+ const skillMd = join(fullPath, "SKILL.md");
213
+ if (existsSync(skillMd)) filePath = skillMd;
214
+ else continue;
215
+ } else {
216
+ continue;
217
+ }
218
+ } catch {
219
+ continue;
220
+ }
221
+ }
222
+ const skill = parseSkillFile(filePath);
223
+ if (skill) {
224
+ if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
225
+ if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
226
+ }
227
+ }
228
+ }
229
+ /** 列出所有已加载的技能 */
230
+ listSkills() {
231
+ return [...this.skills.values()];
232
+ }
233
+ /** 按 name 激活技能。返回被激活的 Skill,未找到返回 null。 */
234
+ activate(name) {
235
+ const skill = this.skills.get(name);
236
+ if (!skill) return null;
237
+ this.activeSkill = skill;
238
+ if (this.warnThreshold > 0 && skill.content.length > this.warnThreshold) {
239
+ process.stderr.write(
240
+ `\u26A0 Skill '${skill.meta.name}' is ${skill.content.length} chars (>${this.warnThreshold}) \u2014 this will be injected into the system prompt now. Adjust threshold with: /config set ui.skillSizeWarn <n|0>
241
+ `
242
+ );
243
+ }
244
+ return skill;
245
+ }
246
+ /** 停用当前技能 */
247
+ deactivate() {
248
+ this.activeSkill = null;
249
+ }
250
+ /** 获取当前激活的技能(null 表示无激活技能) */
251
+ getActive() {
252
+ return this.activeSkill;
253
+ }
254
+ /** 获取激活技能的 prompt 内容(null 表示无激活技能) */
255
+ getActivePromptContent() {
256
+ return this.activeSkill?.content ?? null;
257
+ }
258
+ /** 获取激活技能的工具白名单 Set(null 表示无限制) */
259
+ getActiveToolFilter() {
260
+ if (!this.activeSkill?.meta.tools) return null;
261
+ return new Set(this.activeSkill.meta.tools);
262
+ }
263
+ /** 获取技能目录路径 */
264
+ getSkillsDir() {
265
+ return this.skillsDir;
266
+ }
267
+ };
268
+ function levenshtein(a, b) {
269
+ const m = a.length, n = b.length;
270
+ if (m === 0) return n;
271
+ if (n === 0) return m;
272
+ let prev = Array.from({ length: n + 1 }, (_, j) => j);
273
+ for (let i = 1; i <= m; i++) {
274
+ const cur = [i];
275
+ for (let j = 1; j <= n; j++) {
276
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
277
+ cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
278
+ }
279
+ prev = cur;
280
+ }
281
+ return prev[n];
282
+ }
283
+ function suggestSkillName(names, input) {
284
+ const norm = (s) => s.toLowerCase().replace(/[-_\s]/g, "");
285
+ const target = norm(input);
286
+ if (!target) return null;
287
+ const exact = names.find((n) => norm(n) === target);
288
+ if (exact) return exact;
289
+ const limit = Math.min(3, Math.max(1, Math.floor(target.length / 4)));
290
+ let best = null;
291
+ let bestScore = Infinity;
292
+ for (const n of names) {
293
+ const d = levenshtein(norm(n), target);
294
+ if (d <= limit && d < bestScore) {
295
+ best = n;
296
+ bestScore = d;
297
+ }
298
+ }
299
+ return best;
300
+ }
113
301
 
114
302
  // src/mcp/client.ts
115
303
  import { spawn } from "child_process";
@@ -628,161 +816,6 @@ var McpManager = class {
628
816
  }
629
817
  };
630
818
 
631
- // src/skills/manager.ts
632
- import { existsSync, readdirSync, mkdirSync, statSync } from "fs";
633
- import { join } from "path";
634
-
635
- // src/skills/types.ts
636
- import { readFileSync } from "fs";
637
- import { basename } from "path";
638
- function parseSimpleYaml(yaml) {
639
- const result = {};
640
- for (const line of yaml.split("\n")) {
641
- const match = line.replace(/\r$/, "").match(/^(\w+)\s*:\s*(.+)$/);
642
- if (match) {
643
- result[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
644
- }
645
- }
646
- return result;
647
- }
648
- function parseYamlArray(value) {
649
- const bracketMatch = value.match(/^\[(.+)]$/);
650
- if (bracketMatch) {
651
- return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
652
- }
653
- return [value.trim()];
654
- }
655
- function parseSkillFile(filePath) {
656
- let raw;
657
- try {
658
- raw = readFileSync(filePath, "utf-8");
659
- } catch {
660
- return null;
661
- }
662
- const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
663
- if (!frontmatterMatch) {
664
- return {
665
- meta: {
666
- name: basename(filePath, ".md"),
667
- description: ""
668
- },
669
- content: raw.trim(),
670
- filePath
671
- };
672
- }
673
- const [, yaml, content] = frontmatterMatch;
674
- const parsed = parseSimpleYaml(yaml);
675
- return {
676
- meta: {
677
- name: parsed["name"] ?? basename(filePath, ".md"),
678
- description: parsed["description"] ?? "",
679
- tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
680
- },
681
- content: content.trim(),
682
- filePath
683
- };
684
- }
685
-
686
- // src/skills/manager.ts
687
- var SKILL_CONTENT_WARN_CHARS_DEFAULT = 1e4;
688
- var SkillManager = class {
689
- skills = /* @__PURE__ */ new Map();
690
- activeSkill = null;
691
- skillsDir;
692
- /** 超大技能文件警告阈值,由调用方从 config 传入;0 = 静默,undefined = 用默认 10000 */
693
- warnThreshold;
694
- constructor(skillsDir, warnThreshold) {
695
- this.skillsDir = skillsDir;
696
- this.warnThreshold = warnThreshold ?? SKILL_CONTENT_WARN_CHARS_DEFAULT;
697
- }
698
- /** 发现并加载 skillsDir 以及额外插件目录下的技能,返回加载数量 */
699
- loadSkills(extraDirs = []) {
700
- this.skills.clear();
701
- this.loadSkillDir(this.skillsDir, true);
702
- for (const dir of extraDirs) this.loadSkillDir(dir, false);
703
- return this.skills.size;
704
- }
705
- loadSkillDir(dir, createIfMissing) {
706
- if (!existsSync(dir)) {
707
- if (createIfMissing) {
708
- try {
709
- mkdirSync(dir, { recursive: true });
710
- } catch {
711
- }
712
- }
713
- return;
714
- }
715
- let entries;
716
- try {
717
- entries = readdirSync(dir);
718
- } catch {
719
- return;
720
- }
721
- for (const entry of entries) {
722
- let filePath;
723
- const fullPath = join(dir, entry);
724
- if (entry.endsWith(".md")) {
725
- filePath = fullPath;
726
- } else {
727
- try {
728
- if (statSync(fullPath).isDirectory()) {
729
- const skillMd = join(fullPath, "SKILL.md");
730
- if (existsSync(skillMd)) filePath = skillMd;
731
- else continue;
732
- } else {
733
- continue;
734
- }
735
- } catch {
736
- continue;
737
- }
738
- }
739
- const skill = parseSkillFile(filePath);
740
- if (skill) {
741
- if (skill.meta.name === "SKILL" && !entry.endsWith(".md")) skill.meta.name = entry;
742
- if (!this.skills.has(skill.meta.name)) this.skills.set(skill.meta.name, skill);
743
- }
744
- }
745
- }
746
- /** 列出所有已加载的技能 */
747
- listSkills() {
748
- return [...this.skills.values()];
749
- }
750
- /** 按 name 激活技能。返回被激活的 Skill,未找到返回 null。 */
751
- activate(name) {
752
- const skill = this.skills.get(name);
753
- if (!skill) return null;
754
- this.activeSkill = skill;
755
- if (this.warnThreshold > 0 && skill.content.length > this.warnThreshold) {
756
- process.stderr.write(
757
- `\u26A0 Skill '${skill.meta.name}' is ${skill.content.length} chars (>${this.warnThreshold}) \u2014 this will be injected into the system prompt now. Adjust threshold with: /config set ui.skillSizeWarn <n|0>
758
- `
759
- );
760
- }
761
- return skill;
762
- }
763
- /** 停用当前技能 */
764
- deactivate() {
765
- this.activeSkill = null;
766
- }
767
- /** 获取当前激活的技能(null 表示无激活技能) */
768
- getActive() {
769
- return this.activeSkill;
770
- }
771
- /** 获取激活技能的 prompt 内容(null 表示无激活技能) */
772
- getActivePromptContent() {
773
- return this.activeSkill?.content ?? null;
774
- }
775
- /** 获取激活技能的工具白名单 Set(null 表示无限制) */
776
- getActiveToolFilter() {
777
- if (!this.activeSkill?.meta.tools) return null;
778
- return new Set(this.activeSkill.meta.tools);
779
- }
780
- /** 获取技能目录路径 */
781
- getSkillsDir() {
782
- return this.skillsDir;
783
- }
784
- };
785
-
786
819
  // src/core/proxy.ts
787
820
  async function setupProxy(configProxy) {
788
821
  const proxyUrl = process.env.HTTPS_PROXY ?? process.env.HTTP_PROXY ?? process.env.https_proxy ?? process.env.http_proxy ?? configProxy;
@@ -2393,7 +2426,7 @@ No tools match "${filter}".
2393
2426
  const { join: join6 } = await import("path");
2394
2427
  const { existsSync: existsSync6 } = await import("fs");
2395
2428
  const { getGitRoot } = await import("./git-context-EXOEHQSF.js");
2396
- const { MCP_PROJECT_CONFIG_NAME } = await import("./constants-IMX6ST62.js");
2429
+ const { MCP_PROJECT_CONFIG_NAME } = await import("./constants-W52VYKDV.js");
2397
2430
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
2398
2431
  const cwd = process.cwd();
2399
2432
  const projectRoot = getGitRoot(cwd) ?? cwd;
@@ -2542,9 +2575,13 @@ No tools match "${filter}".
2542
2575
  ctx.renderer.printSuccess(`Reloaded: ${count} skill(s) found.`);
2543
2576
  return;
2544
2577
  }
2545
- const target = manager.listSkills().find((s) => s.meta.name === sub);
2578
+ const allSkills = manager.listSkills();
2579
+ const target = allSkills.find((s) => s.meta.name === sub);
2546
2580
  if (!target) {
2547
- ctx.renderer.renderError(`Skill '${sub}' not found. Use /skill list to see available skills.`);
2581
+ const guess = suggestSkillName(allSkills.map((s) => s.meta.name), sub);
2582
+ ctx.renderer.renderError(
2583
+ guess ? `Skill '${sub}' not found. Did you mean '${guess}'? (/skill ${guess})` : `Skill '${sub}' not found. Use /skill list to see available skills.`
2584
+ );
2548
2585
  return;
2549
2586
  }
2550
2587
  const prev = manager.getActive();
@@ -3497,7 +3534,7 @@ function createReviewCommands() {
3497
3534
  usage: "/test [command|filter]",
3498
3535
  async execute(args, ctx) {
3499
3536
  try {
3500
- const { executeTests } = await import("./run-tests-PY4EEU2J.js");
3537
+ const { executeTests } = await import("./run-tests-MTWA2NNV.js");
3501
3538
  const argStr = args.join(" ").trim();
3502
3539
  let testArgs = {};
3503
3540
  if (argStr) {
@@ -5633,6 +5670,8 @@ export {
5633
5670
  agentUnknownMessage,
5634
5671
  parseSkillCommand,
5635
5672
  skillHistoryNote,
5673
+ parseSimpleYaml,
5674
+ SkillManager,
5636
5675
  applyProfileCommand,
5637
5676
  readGitDiff,
5638
5677
  parseRewindArgs,
@@ -5663,7 +5702,6 @@ export {
5663
5702
  getClipboardHint,
5664
5703
  CommandRegistry,
5665
5704
  createDefaultCommands,
5666
- parseSimpleYaml,
5667
5705
  persistToolRound,
5668
5706
  autoTrimSessionIfNeeded,
5669
5707
  estimateConversationTokens,
@@ -5677,7 +5715,6 @@ export {
5677
5715
  loadDevState,
5678
5716
  clearDevState,
5679
5717
  McpManager,
5680
- SkillManager,
5681
5718
  snapshotFile,
5682
5719
  armDeferredSave,
5683
5720
  decideDeferredSave,