jinzd-ai-cli 0.1.57 → 0.1.59

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/CLAUDE.md CHANGED
@@ -350,6 +350,134 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
350
350
  - [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
351
351
  - [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
352
352
 
353
+ ## 本轮开发完成记录(2026-03-10,v0.1.57 → v0.1.58)
354
+
355
+ ### 代码质量修复:19 个低危问题修复 + 代码重构
356
+
357
+ **审查完结篇**:继 v0.1.56 修复高危、v0.1.57 修复中危后,本轮修复全部低危问题,代码审查报告彻底清零。
358
+
359
+ | # | 文件 | 修复内容 |
360
+ |---|------|---------|
361
+ | L1 | `src/tools/truncate.ts` (新) | 提取 `truncateOutput` 为共享模块,消除 executor.ts 与 spawn-agent.ts 代码重复 |
362
+ | L2 | `src/tools/executor.ts` | 改为从共享 `truncate.ts` 导入 |
363
+ | L3 | `src/tools/builtin/spawn-agent.ts` | 15+ 处 chalk→theme 迁移(header/footer/BLOCKED/toolCall/toolResult),移除 chalk 依赖 |
364
+ | L4 | `src/tools/builtin/run-interactive.ts` | timeout 参数限制 1s–300s(与 bash 工具一致) |
365
+ | L5 | `src/tools/builtin/grep-files.ts` | `statSync` 预检文件大小再读取,避免 OOM |
366
+ | L6 | `src/tools/builtin/read-file.ts` | `buf.slice` → `buf.subarray`(消除 Node.js 弃用警告) |
367
+ | L7 | `src/tools/undo-stack.ts` | 提取 `pushEntry()` 统一入栈方法,消除 push/pushNewFile/pushNewDir 三处溢出检查重复 |
368
+ | L8 | `src/tools/builtin/save-memory.ts` | 用 `statSync` 获取文件大小,避免追加后重读整个文件 |
369
+ | L9 | `src/tools/builtin/list-dir.ts` | 不再隐藏 .github/.vscode/.gitignore/.env* 等有用的点文件/目录 |
370
+ | L10 | `src/repl/custom-commands.ts` | 复用 `skills/types.ts` 的 `parseSimpleYaml`,消除函数重复 |
371
+ | L11 | `src/skills/types.ts` | `parseSimpleYaml` 导出为 public + 添加引号剥离 |
372
+ | L12 | `src/tools/git-context.ts` | 移除 `2>/dev/null`(Windows 不兼容,`stdio:pipe` 已捕获 stderr) |
373
+ | L13 | `src/repl/notify.ts` | Windows PowerShell 反引号转义修复 |
374
+ | L14 | `src/tools/hooks.ts` | 模板变量 shell 转义(`shellEscape` 单引号包裹),防止命令注入 |
375
+ | L15 | `src/mcp/client.ts` | 移除 `rejectAllPending` 中冗余的 `clearTimeout`(reject 回调已包含) |
376
+ | L16 | `src/config/config-manager.ts` | 拆分 `toJSON()`→返回对象 + `toFormattedJSON()`→返回字符串,修复 `/config show` API Key 掩码失效 |
377
+ | L17 | `src/tools/builtin/write-file.ts` | append 模式也记录 undo 条目(可撤销追加操作) |
378
+ | L18 | `src/providers/gemini.ts` | 保留空白文本 parts(仅跳过空字符串),避免丢失有意义的空白 |
379
+ | L19 | `src/repl/clipboard.ts` | PowerShell 单引号字符串中的路径转义修复 |
380
+ | L20 | `src/tools/diff-utils.ts` | DP 表内存限制文档化(~2MB 上限) |
381
+ | L21 | `src/tools/permissions.ts` | `pathPattern` 子串匹配行为文档化 |
382
+
383
+ ### 变更文件汇总
384
+
385
+ | 文件 | 变更类型 | 说明 |
386
+ |------|---------|------|
387
+ | `src/tools/truncate.ts` | 新增 | 共享 truncateOutput 函数 |
388
+ | `src/tools/executor.ts` | 修改 | 导入共享 truncate.ts |
389
+ | `src/tools/builtin/spawn-agent.ts` | 修改 | chalk→theme 全量迁移 + 共享 truncate |
390
+ | `src/tools/builtin/run-interactive.ts` | 修改 | timeout 限制 |
391
+ | `src/tools/builtin/grep-files.ts` | 修改 | statSync 预检 |
392
+ | `src/tools/builtin/read-file.ts` | 修改 | buf.subarray |
393
+ | `src/tools/undo-stack.ts` | 修改 | pushEntry DRY |
394
+ | `src/tools/builtin/save-memory.ts` | 修改 | statSync 替代 readFile |
395
+ | `src/tools/builtin/list-dir.ts` | 修改 | 有用点文件可见 |
396
+ | `src/repl/custom-commands.ts` | 修改 | 复用 parseSimpleYaml |
397
+ | `src/skills/types.ts` | 修改 | 导出 parseSimpleYaml |
398
+ | `src/tools/git-context.ts` | 修改 | 移除 2>/dev/null |
399
+ | `src/repl/notify.ts` | 修改 | PS 转义修复 |
400
+ | `src/tools/hooks.ts` | 修改 | shell 转义 |
401
+ | `src/mcp/client.ts` | 修改 | 冗余 clearTimeout |
402
+ | `src/config/config-manager.ts` | 修改 | toJSON 拆分 |
403
+ | `src/repl/commands/index.ts` | 修改 | 适配 toJSON 返回类型 |
404
+ | `src/tools/builtin/write-file.ts` | 修改 | append undo |
405
+ | `src/providers/gemini.ts` | 修改 | 空白 text parts |
406
+ | `src/repl/clipboard.ts` | 修改 | 路径转义 |
407
+ | `src/tools/diff-utils.ts` | 修改 | 文档化 |
408
+ | `src/tools/permissions.ts` | 修改 | 文档化 |
409
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.57 → 0.1.58 |
410
+ | `package.json` | 修改 | version 0.1.57 → 0.1.58 |
411
+
412
+ ### 版本与收尾
413
+ - `src/core/constants.ts`:VERSION `0.1.57` → `0.1.58`
414
+ - `package.json`:version 同步
415
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
416
+
417
+ ### 代码审查完结状态(v0.1.35+ 增量)
418
+ - **高危 H1–H6**:全部已修复(v0.1.56)
419
+ - **中危 M1–M16**:全部已修复(v0.1.57)
420
+ - **低危 L1–L21**:全部已修复(v0.1.58)
421
+ - **安全债务清零**
422
+
423
+ ---
424
+
425
+ ## 本轮开发完成记录(2026-03-10,v0.1.56 → v0.1.57)
426
+
427
+ ### 代码质量修复:16 个中危问题全部修复
428
+
429
+ **审查续篇**:继 v0.1.56 修复 6 个高危后,本轮修复全部 16 个中危问题。
430
+
431
+ | # | 文件 | 修复内容 |
432
+ |---|------|---------|
433
+ | M1 | `commands/index.ts` | `/config set` 原型污染防护(禁止 `__proto__`/`constructor`/`prototype` 路径) |
434
+ | M2 | `commands/index.ts` | `/config show` API Key 掩码(显示 `sk-d***b0` 格式) |
435
+ | M3 | `repl.ts` | `FREE_ROUND_TOOLS` 连续免费轮次上限(MAX_CONSECUTIVE_FREE_ROUNDS=5),防无限循环 |
436
+ | M4 | `openai-compatible.ts` | 截断 JSON 修复时 stderr 警告(提示参数可能丢失) |
437
+ | M5 | `claude.ts` | `chatWithToolsStream` done 事件兜底(`doneEmitted` 标志 + 流结束后补发) |
438
+ | M6 | `claude.ts` | 非 base64 图片 URL 跳过时 stderr 警告 |
439
+ | M7 | `claude.ts` | AbortError/TimeoutError 不再被 `wrapError` 包装为 ProviderError(正确冒泡) |
440
+ | M8 | `openai-compatible.ts` | HALLUCINATION_PATTERNS 收紧:要求文件扩展名或路径引号上下文,减少误报 |
441
+ | M9 | `openai-compatible.ts` | 非流式降级路径保留 `reasoningContent`(DeepSeek-Reasoner 推理内容) |
442
+ | M10 | `deepseek.ts` + `kimi.ts` | Plan C 重试后二次校验:仍虚假声明时 stderr 警告用户手动检查 |
443
+ | M11 | `renderer.ts` | `printTable` ANSI 感知列对齐(用 `stripAnsi` 计算可见宽度) |
444
+ | M12 | `renderer.ts` | `wrapText` ANSI 样式跨行延续(折行后重发活跃样式序列,行末发 reset) |
445
+ | M13 | `theme.ts` | `resolveColor` 无效颜色名 stderr 警告(不再静默回退) |
446
+ | M14 | `session.ts` | `fork()` 深拷贝 multimodal 消息(嵌套 content 数组不再共享引用) |
447
+ | M15 | `bash.ts` | Undo 追踪限制文档化(仅 CWD 子级、仅常见创建命令、不追踪管道/脚本产生) |
448
+ | M16 | `edit-file.ts` | `findSimilarLines` 500KB 文件大小保护(跳过大文件避免性能问题) |
449
+
450
+ ### 变更文件汇总
451
+
452
+ | 文件 | 变更类型 | 说明 |
453
+ |------|---------|------|
454
+ | `src/repl/commands/index.ts` | 修改 | M1 原型污染 + M2 API Key 掩码 |
455
+ | `src/repl/repl.ts` | 修改 | M3 连续免费轮次上限 |
456
+ | `src/providers/openai-compatible.ts` | 修改 | M4 JSON 修复警告 + M8 模式收紧 + M9 reasoningContent |
457
+ | `src/providers/claude.ts` | 修改 | M5 done 兜底 + M6 图片警告 + M7 AbortError |
458
+ | `src/providers/deepseek.ts` | 修改 | M10 重试二次校验 |
459
+ | `src/providers/kimi.ts` | 修改 | M10 重试二次校验 |
460
+ | `src/repl/renderer.ts` | 修改 | M11 表格对齐 + M12 折行样式 |
461
+ | `src/repl/theme.ts` | 修改 | M13 无效颜色警告 |
462
+ | `src/session/session.ts` | 修改 | M14 fork 深拷贝 |
463
+ | `src/tools/builtin/bash.ts` | 修改 | M15 限制文档 |
464
+ | `src/tools/builtin/edit-file.ts` | 修改 | M16 大文件保护 |
465
+ | `src/core/constants.ts` | 修改 | VERSION 0.1.56 → 0.1.57 |
466
+ | `package.json` | 修改 | version 0.1.56 → 0.1.57 |
467
+
468
+ ### 版本与收尾
469
+ - `src/core/constants.ts`:VERSION `0.1.56` → `0.1.57`
470
+ - `package.json`:version 同步
471
+ - 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
472
+ - 发布:`npm publish` → `jinzd-ai-cli@0.1.57`
473
+
474
+ ### 代码审查完结状态(v0.1.35+ 增量)
475
+ - **高危 H1–H6**:全部已修复(v0.1.56)
476
+ - **中危 M1–M16**:全部已修复(v0.1.57)
477
+ - **低危 L1–L28**:待后续改进(多为代码风格、注释、微优化)
478
+
479
+ ---
480
+
353
481
  ## 本轮开发完成记录(2026-03-10,v0.1.55 → v0.1.56)
354
482
 
355
483
  ### 安全修复:v0.1.35+ 增量代码审查 — 6 个高危问题全部修复
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.1.57";
11
+ var VERSION = "0.1.59";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  SUBAGENT_MAX_ROUNDS_LIMIT,
31
31
  VERSION,
32
32
  runTestsTool
33
- } from "./chunk-WWHIELPD.js";
33
+ } from "./chunk-7RQ6QCDG.js";
34
34
 
35
35
  // src/index.ts
36
36
  import { program } from "commander";
@@ -343,10 +343,14 @@ ${err}`
343
343
  current[keys[keys.length - 1]] = value;
344
344
  this.save();
345
345
  }
346
- /** 获取完整配置对象的 JSON 字符串 */
347
- toJSON() {
346
+ /** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
347
+ toFormattedJSON() {
348
348
  return JSON.stringify(this.config, null, 2);
349
349
  }
350
+ /** 获取完整配置对象(JSON 兼容的原始对象) */
351
+ toJSON() {
352
+ return structuredClone(this.config);
353
+ }
350
354
  };
351
355
 
352
356
  // src/providers/claude.ts
@@ -897,7 +901,7 @@ var GeminiProvider = class extends BaseProvider {
897
901
  const parts = [];
898
902
  for (const p of content) {
899
903
  if (p.type === "text") {
900
- if (p.text.trim()) parts.push({ text: p.text });
904
+ if (p.text.length > 0) parts.push({ text: p.text });
901
905
  } else if (p.type === "image_url") {
902
906
  const match = p.image_url.url.match(/^data:([^;]+);base64,(.+)$/);
903
907
  if (match) {
@@ -2108,6 +2112,15 @@ var Session = class _Session {
2108
2112
  this.created = /* @__PURE__ */ new Date();
2109
2113
  this.updated = /* @__PURE__ */ new Date();
2110
2114
  }
2115
+ /**
2116
+ * 更新 session 关联的 provider 和 model(在 /provider 或 /model 切换时调用)。
2117
+ * 保留对话历史和所有状态,仅更新元数据。
2118
+ */
2119
+ updateProvider(provider, model) {
2120
+ this.provider = provider;
2121
+ this.model = model;
2122
+ this.updated = /* @__PURE__ */ new Date();
2123
+ }
2111
2124
  addMessage(message) {
2112
2125
  this.messages.push(message);
2113
2126
  this.updated = /* @__PURE__ */ new Date();
@@ -2420,9 +2433,9 @@ var SessionManager = class {
2420
2433
 
2421
2434
  // src/repl/repl.ts
2422
2435
  import * as readline from "readline";
2423
- import { existsSync as existsSync19, readFileSync as readFileSync13, readdirSync as readdirSync11, statSync as statSync8 } from "fs";
2436
+ import { existsSync as existsSync19, readFileSync as readFileSync12, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
2424
2437
  import { join as join14, resolve as resolve5, extname as extname4, dirname as dirname6, basename as basename6 } from "path";
2425
- import chalk10 from "chalk";
2438
+ import chalk9 from "chalk";
2426
2439
 
2427
2440
  // src/repl/renderer.ts
2428
2441
  import chalk2 from "chalk";
@@ -2967,7 +2980,7 @@ function getGitContext(cwd = process.cwd()) {
2967
2980
  }
2968
2981
  const logOutput = runGit("log --oneline -3", cwd) ?? "";
2969
2982
  const recentCommits = logOutput ? logOutput.split("\n").filter(Boolean) : [];
2970
- const unpushedOutput = runGit("log @{u}..HEAD --oneline 2>/dev/null", cwd);
2983
+ const unpushedOutput = runGit("log @{u}..HEAD --oneline", cwd);
2971
2984
  const hasUnpushed = unpushedOutput !== null && unpushedOutput.trim().length > 0;
2972
2985
  return {
2973
2986
  branch,
@@ -3032,43 +3045,41 @@ var UndoStack = class {
3032
3045
  return;
3033
3046
  }
3034
3047
  }
3035
- this.stack.push({
3048
+ this.pushEntry({
3036
3049
  filePath,
3037
3050
  previousContent,
3038
3051
  description,
3039
3052
  timestamp: /* @__PURE__ */ new Date()
3040
3053
  });
3041
- if (this.stack.length > MAX_UNDO_DEPTH) {
3042
- this.stack.shift();
3043
- }
3044
3054
  }
3045
3055
  /**
3046
3056
  * 推入一个新建文件的条目(previousContent=null),undo 时删除该文件。
3047
3057
  * 用于 bash 工具执行后检测到的新建文件。
3048
3058
  */
3049
3059
  pushNewFile(filePath, description) {
3050
- this.stack.push({
3060
+ this.pushEntry({
3051
3061
  filePath,
3052
3062
  previousContent: null,
3053
3063
  description,
3054
3064
  timestamp: /* @__PURE__ */ new Date()
3055
3065
  });
3056
- if (this.stack.length > MAX_UNDO_DEPTH) {
3057
- this.stack.shift();
3058
- }
3059
3066
  }
3060
3067
  /**
3061
3068
  * 推入一个新建目录的条目(previousContent=null, isDirectory=true),
3062
3069
  * undo 时尝试 rmdir(仅空目录可删)。
3063
3070
  */
3064
3071
  pushNewDir(dirPath, description) {
3065
- this.stack.push({
3072
+ this.pushEntry({
3066
3073
  filePath: dirPath,
3067
3074
  previousContent: null,
3068
3075
  description,
3069
3076
  timestamp: /* @__PURE__ */ new Date(),
3070
3077
  isDirectory: true
3071
3078
  });
3079
+ }
3080
+ /** 内部统一入栈方法,含溢出裁剪 */
3081
+ pushEntry(entry) {
3082
+ this.stack.push(entry);
3072
3083
  if (this.stack.length > MAX_UNDO_DEPTH) {
3073
3084
  this.stack.shift();
3074
3085
  }
@@ -3310,7 +3321,7 @@ function readClipboardImage() {
3310
3321
  return outPath;
3311
3322
  }
3312
3323
  function _readWin32(outPath) {
3313
- const escapedPath = outPath.replace(/\\/g, "\\\\");
3324
+ const escapedPath = outPath.replace(/'/g, "''");
3314
3325
  const script = [
3315
3326
  "Add-Type -AssemblyName System.Windows.Forms",
3316
3327
  "Add-Type -AssemblyName System.Drawing",
@@ -3703,9 +3714,16 @@ function createDefaultCommands() {
3703
3714
  return;
3704
3715
  }
3705
3716
  await ctx.generateDevStateSnapshot();
3717
+ const session = ctx.sessions.current;
3718
+ const msgCount = session?.messages.length ?? 0;
3706
3719
  ctx.setProvider(targetId);
3707
- ctx.sessions.createSession(targetId, ctx.getCurrentModel());
3708
- ctx.renderer.printSuccess(`Switched to provider: ${targetId}`);
3720
+ if (session) {
3721
+ session.updateProvider(targetId, ctx.getCurrentModel());
3722
+ await ctx.sessions.save();
3723
+ }
3724
+ ctx.renderer.printSuccess(
3725
+ `Switched to provider: ${targetId}` + (msgCount > 0 ? ` (conversation preserved: ${msgCount} messages)` : "")
3726
+ );
3709
3727
  }
3710
3728
  },
3711
3729
  {
@@ -3739,9 +3757,16 @@ function createDefaultCommands() {
3739
3757
  return;
3740
3758
  }
3741
3759
  await ctx.generateDevStateSnapshot();
3760
+ const session = ctx.sessions.current;
3761
+ const msgCount = session?.messages.length ?? 0;
3742
3762
  ctx.setProvider(ctx.getCurrentProvider(), targetModel);
3743
- ctx.sessions.createSession(ctx.getCurrentProvider(), targetModel);
3744
- ctx.renderer.printSuccess(`Switched to model: ${targetModel}`);
3763
+ if (session) {
3764
+ session.updateProvider(ctx.getCurrentProvider(), targetModel);
3765
+ await ctx.sessions.save();
3766
+ }
3767
+ ctx.renderer.printSuccess(
3768
+ `Switched to model: ${targetModel}` + (msgCount > 0 ? ` (conversation preserved: ${msgCount} messages)` : "")
3769
+ );
3745
3770
  }
3746
3771
  },
3747
3772
  {
@@ -4211,17 +4236,17 @@ ${text}
4211
4236
  return;
4212
4237
  }
4213
4238
  if (sub === "show") {
4214
- const raw = config.toJSON();
4215
- const masked = JSON.parse(JSON.stringify(raw));
4239
+ const masked = config.toJSON();
4216
4240
  if (masked.apiKeys && typeof masked.apiKeys === "object") {
4217
- for (const key of Object.keys(masked.apiKeys)) {
4218
- const val = masked.apiKeys[key];
4241
+ const keys = masked.apiKeys;
4242
+ for (const key of Object.keys(keys)) {
4243
+ const val = keys[key];
4219
4244
  if (typeof val === "string" && val.length > 8) {
4220
- masked.apiKeys[key] = val.slice(0, 4) + "****" + val.slice(-4);
4245
+ keys[key] = val.slice(0, 4) + "****" + val.slice(-4);
4221
4246
  }
4222
4247
  }
4223
4248
  }
4224
- console.log(masked);
4249
+ console.log(JSON.stringify(masked, null, 2));
4225
4250
  return;
4226
4251
  }
4227
4252
  if (sub === "get") {
@@ -4681,7 +4706,7 @@ ${hint}` : "")
4681
4706
  description: "Run project tests and show structured report",
4682
4707
  usage: "/test [command|filter]",
4683
4708
  async execute(args, _ctx) {
4684
- const { executeTests } = await import("./run-tests-I2LRD36Z.js");
4709
+ const { executeTests } = await import("./run-tests-CUBYICNN.js");
4685
4710
  const argStr = args.join(" ").trim();
4686
4711
  let testArgs = {};
4687
4712
  if (argStr) {
@@ -5507,7 +5532,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
5507
5532
  ".pyc"
5508
5533
  ]);
5509
5534
  function isBinaryBuffer(buf) {
5510
- const sample = buf.slice(0, 512);
5535
+ const sample = buf.subarray(0, 512);
5511
5536
  let nullCount = 0;
5512
5537
  for (let i = 0; i < sample.length; i++) {
5513
5538
  if (sample[i] === 0) nullCount++;
@@ -5699,9 +5724,7 @@ var writeFileTool = {
5699
5724
  const encoding = args["encoding"] ?? "utf-8";
5700
5725
  const appendMode = String(args["append"] ?? "false").toLowerCase() === "true";
5701
5726
  if (!filePath) throw new Error("path is required");
5702
- if (!appendMode) {
5703
- undoStack.push(filePath, `write_file: ${filePath}`);
5704
- }
5727
+ undoStack.push(filePath, `write_file${appendMode ? " (append)" : ""}: ${filePath}`);
5705
5728
  mkdirSync5(dirname4(filePath), { recursive: true });
5706
5729
  if (appendMode) {
5707
5730
  appendFileSync2(filePath, content, encoding);
@@ -6004,7 +6027,8 @@ function listRecursive(basePath, indent, recursive, lines) {
6004
6027
  return a.name.localeCompare(b.name);
6005
6028
  });
6006
6029
  for (const entry of sorted) {
6007
- if (entry.name.startsWith(".") || entry.name === "node_modules") {
6030
+ const USEFUL_DOT_ENTRIES = /* @__PURE__ */ new Set([".github", ".vscode", ".idea", ".gitignore", ".gitattributes", ".editorconfig", ".eslintrc", ".prettierrc", ".npmrc"]);
6031
+ if (entry.name === "node_modules" || entry.name.startsWith(".") && !USEFUL_DOT_ENTRIES.has(entry.name) && !entry.name.startsWith(".env")) {
6008
6032
  if (entry.isDirectory()) {
6009
6033
  lines.push(`${indent}\u{1F4C1} ${entry.name}/ (skipped)`);
6010
6034
  }
@@ -6169,13 +6193,18 @@ function collectFiles(dirPath, filePattern, results, regex, contextLines, maxRes
6169
6193
  }
6170
6194
  }
6171
6195
  function searchInFile(fullPath, displayPath, regex, contextLines, maxResults, results) {
6196
+ try {
6197
+ const stat = statSync6(fullPath);
6198
+ if (stat.size > 1e6) return;
6199
+ } catch {
6200
+ return;
6201
+ }
6172
6202
  let content;
6173
6203
  try {
6174
6204
  content = readFileSync7(fullPath, "utf-8");
6175
6205
  } catch {
6176
6206
  return;
6177
6207
  }
6178
- if (content.length > 1e6) return;
6179
6208
  const lines = content.split("\n");
6180
6209
  regex.lastIndex = 0;
6181
6210
  for (let i = 0; i < lines.length; i++) {
@@ -6384,7 +6413,7 @@ var runInteractiveTool = {
6384
6413
  process.stderr.write(stdinTypeWarning);
6385
6414
  return rawStdin.split(",").map((s) => s.trim()).filter(Boolean);
6386
6415
  })() : [];
6387
- const timeout = Number(args["timeout"] ?? 2e4);
6416
+ const timeout = Math.min(Math.max(Number(args["timeout"] ?? 2e4), 1e3), 3e5);
6388
6417
  if (!executable) {
6389
6418
  throw new Error("executable is required");
6390
6419
  }
@@ -6706,7 +6735,7 @@ var saveLastResponseTool = {
6706
6735
  };
6707
6736
 
6708
6737
  // src/tools/builtin/save-memory.ts
6709
- import { existsSync as existsSync13, readFileSync as readFileSync8, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7 } from "fs";
6738
+ import { existsSync as existsSync13, statSync as statSync8, appendFileSync as appendFileSync3, mkdirSync as mkdirSync7 } from "fs";
6710
6739
  import { join as join9 } from "path";
6711
6740
  import { homedir as homedir3 } from "os";
6712
6741
  function getMemoryFilePath() {
@@ -6744,10 +6773,8 @@ var saveMemoryTool = {
6744
6773
  ${content}
6745
6774
  `;
6746
6775
  appendFileSync3(memoryPath, entry, "utf-8");
6747
- const fullContent = readFileSync8(memoryPath, "utf-8");
6748
- const entryCount = (fullContent.match(/^## \d{4}-\d{2}-\d{2}/gm) ?? []).length;
6749
- const byteSize = Buffer.byteLength(fullContent, "utf-8");
6750
- return `Memory saved successfully. Total: ${entryCount} entries, ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
6776
+ const byteSize = statSync8(memoryPath).size;
6777
+ return `Memory saved successfully. File size: ${byteSize} bytes in ${MEMORY_FILE_NAME}`;
6751
6778
  }
6752
6779
  };
6753
6780
 
@@ -7021,21 +7048,12 @@ function formatResults(query, data, requested) {
7021
7048
  return header + "\n" + results.join("\n\n");
7022
7049
  }
7023
7050
 
7024
- // src/tools/builtin/spawn-agent.ts
7025
- import chalk8 from "chalk";
7026
- var spawnAgentContext = {
7027
- provider: null,
7028
- model: "",
7029
- systemPrompt: void 0,
7030
- modelParams: {},
7031
- configManager: null
7032
- };
7033
- var PREFIX = chalk8.dim(" \u2503 ");
7034
- var MAX_TOOL_OUTPUT_CHARS = 12e3;
7051
+ // src/tools/truncate.ts
7052
+ var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
7035
7053
  function truncateOutput(content, toolName) {
7036
- if (content.length <= MAX_TOOL_OUTPUT_CHARS) return content;
7037
- const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.7);
7038
- const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.2);
7054
+ if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
7055
+ const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
7056
+ const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
7039
7057
  const omitted = content.length - keepHead - keepTail;
7040
7058
  const lines = content.split("\n").length;
7041
7059
  const head = content.slice(0, keepHead);
@@ -7046,6 +7064,16 @@ function truncateOutput(content, toolName) {
7046
7064
 
7047
7065
  ` + tail;
7048
7066
  }
7067
+
7068
+ // src/tools/builtin/spawn-agent.ts
7069
+ var spawnAgentContext = {
7070
+ provider: null,
7071
+ model: "",
7072
+ systemPrompt: void 0,
7073
+ modelParams: {},
7074
+ configManager: null
7075
+ };
7076
+ var PREFIX = theme.dim(" \u2503 ");
7049
7077
  var SubAgentExecutor = class {
7050
7078
  constructor(registry) {
7051
7079
  this.registry = registry;
@@ -7068,7 +7096,7 @@ var SubAgentExecutor = class {
7068
7096
  const dangerLevel = getDangerLevel(call.name, call.arguments);
7069
7097
  if (dangerLevel === "destructive") {
7070
7098
  this.printPrefixed(
7071
- chalk8.red("\u26A0 BLOCKED: ") + `Destructive operation ${chalk8.bold(call.name)} not allowed in sub-agent`
7099
+ theme.error("\u26A0 BLOCKED: ") + `Destructive operation ${call.name} not allowed in sub-agent`
7072
7100
  );
7073
7101
  return {
7074
7102
  callId: call.id,
@@ -7104,9 +7132,9 @@ var SubAgentExecutor = class {
7104
7132
  }
7105
7133
  printToolCall(call, dangerLevel) {
7106
7134
  console.log(PREFIX);
7107
- const icon = dangerLevel === "write" ? chalk8.yellow("\u270E Tool: ") : chalk8.bold.cyan("\u2699 Tool: ");
7108
- const roundBadge = this.totalRounds > 0 ? chalk8.dim(` [${this.round}/${this.totalRounds}]`) : "";
7109
- console.log(PREFIX + icon + chalk8.white(call.name) + roundBadge);
7135
+ const icon = dangerLevel === "write" ? theme.warning("\u270E Tool: ") : theme.toolCall("\u2699 Tool: ");
7136
+ const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
7137
+ console.log(PREFIX + icon + call.name + roundBadge);
7110
7138
  for (const [key, val] of Object.entries(call.arguments)) {
7111
7139
  let valStr;
7112
7140
  if (Array.isArray(val)) {
@@ -7117,22 +7145,22 @@ var SubAgentExecutor = class {
7117
7145
  } else {
7118
7146
  valStr = String(val);
7119
7147
  }
7120
- console.log(PREFIX + chalk8.gray(` ${key}: `) + chalk8.white(valStr));
7148
+ console.log(PREFIX + theme.dim(` ${key}: `) + valStr);
7121
7149
  }
7122
7150
  }
7123
7151
  printToolResult(name, content, isError, wasTruncated) {
7124
7152
  if (isError) {
7125
7153
  console.log(
7126
- PREFIX + chalk8.red(`\u26A0 ${name} error: `) + chalk8.gray(content.slice(0, 300))
7154
+ PREFIX + theme.error(`\u26A0 ${name} error: `) + theme.dim(content.slice(0, 300))
7127
7155
  );
7128
7156
  } else {
7129
7157
  const lines = content.split("\n");
7130
7158
  const maxLines = name === "run_interactive" ? 40 : 8;
7131
7159
  const preview = lines.slice(0, maxLines);
7132
- const prefixedPreview = preview.map((l) => PREFIX + " " + chalk8.gray(l)).join("\n");
7133
- const moreLines = lines.length > maxLines ? "\n" + PREFIX + chalk8.gray(` ... (${lines.length - maxLines} more lines)`) : "";
7134
- const truncNote = wasTruncated ? "\n" + PREFIX + chalk8.yellow(` \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26`) : "";
7135
- console.log(PREFIX + chalk8.green("\u2713 Result:"));
7160
+ const prefixedPreview = preview.map((l) => PREFIX + " " + theme.dim(l)).join("\n");
7161
+ const moreLines = lines.length > maxLines ? "\n" + PREFIX + theme.dim(` ... (${lines.length - maxLines} more lines)`) : "";
7162
+ const truncNote = wasTruncated ? "\n" + PREFIX + theme.warning(` \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD`) : "";
7163
+ console.log(PREFIX + theme.success("\u2713 Result:"));
7136
7164
  console.log(prefixedPreview + moreLines + truncNote);
7137
7165
  }
7138
7166
  }
@@ -7162,25 +7190,25 @@ ${task}
7162
7190
  }
7163
7191
  function printSubAgentHeader(task, maxRounds) {
7164
7192
  console.log();
7165
- console.log(chalk8.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
7166
- console.log(PREFIX + chalk8.bold.magenta("\u{1F916} Sub-Agent Spawned"));
7193
+ console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
7194
+ console.log(PREFIX + theme.toolCall("\u{1F916} Sub-Agent Spawned"));
7167
7195
  console.log(
7168
- PREFIX + chalk8.gray("Task: ") + chalk8.white(task.slice(0, 120) + (task.length > 120 ? "..." : ""))
7196
+ PREFIX + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
7169
7197
  );
7170
- console.log(PREFIX + chalk8.gray(`Max rounds: ${maxRounds}`));
7171
- console.log(chalk8.dim(" \u2503"));
7198
+ console.log(PREFIX + theme.dim(`Max rounds: ${maxRounds}`));
7199
+ console.log(theme.dim(" \u2503"));
7172
7200
  }
7173
7201
  function printSubAgentFooter(usage) {
7174
7202
  console.log(PREFIX);
7175
- console.log(PREFIX + chalk8.bold.magenta("Sub-Agent Complete"));
7203
+ console.log(PREFIX + theme.toolCall("Sub-Agent Complete"));
7176
7204
  if (usage.inputTokens > 0 || usage.outputTokens > 0) {
7177
7205
  console.log(
7178
- PREFIX + chalk8.dim(
7206
+ PREFIX + theme.dim(
7179
7207
  `Tokens: ${usage.inputTokens} in / ${usage.outputTokens} out`
7180
7208
  )
7181
7209
  );
7182
7210
  }
7183
- console.log(chalk8.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
7211
+ console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
7184
7212
  console.log();
7185
7213
  }
7186
7214
  var spawnAgentTool = {
@@ -7426,18 +7454,21 @@ var ToolRegistry = class {
7426
7454
  };
7427
7455
 
7428
7456
  // src/tools/executor.ts
7429
- import chalk9 from "chalk";
7430
- import { existsSync as existsSync15, readFileSync as readFileSync9 } from "fs";
7457
+ import chalk8 from "chalk";
7458
+ import { existsSync as existsSync15, readFileSync as readFileSync8 } from "fs";
7431
7459
 
7432
7460
  // src/tools/hooks.ts
7433
7461
  import { execSync as execSync6 } from "child_process";
7462
+ function shellEscape(value) {
7463
+ return "'" + value.replace(/'/g, "'\\''") + "'";
7464
+ }
7434
7465
  function runHook(template, vars) {
7435
7466
  if (!template) return;
7436
7467
  let cmd = template;
7437
- cmd = cmd.replace(/\{tool\}/g, vars.tool);
7438
- cmd = cmd.replace(/\{dangerLevel\}/g, vars.dangerLevel ?? "");
7439
- cmd = cmd.replace(/\{args\}/g, vars.args ?? "");
7440
- cmd = cmd.replace(/\{status\}/g, vars.status ?? "");
7468
+ cmd = cmd.replace(/\{tool\}/g, shellEscape(vars.tool));
7469
+ cmd = cmd.replace(/\{dangerLevel\}/g, shellEscape(vars.dangerLevel ?? ""));
7470
+ cmd = cmd.replace(/\{args\}/g, shellEscape(vars.args ?? ""));
7471
+ cmd = cmd.replace(/\{status\}/g, shellEscape(vars.status ?? ""));
7441
7472
  try {
7442
7473
  execSync6(cmd, {
7443
7474
  timeout: 5e3,
@@ -7467,21 +7498,6 @@ function checkPermission(toolName, args, dangerLevel, rules, defaultAction = "co
7467
7498
  }
7468
7499
 
7469
7500
  // src/tools/executor.ts
7470
- var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
7471
- function truncateOutput2(content, toolName) {
7472
- if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
7473
- const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
7474
- const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
7475
- const omitted = content.length - keepHead - keepTail;
7476
- const lines = content.split("\n").length;
7477
- const head = content.slice(0, keepHead);
7478
- const tail = content.slice(content.length - keepTail);
7479
- return head + `
7480
-
7481
- ... [\u8F93\u51FA\u5DF2\u622A\u65AD\uFF1A\u5171 ${content.length} \u5B57\u7B26 / ${lines} \u884C\uFF0C\u7701\u7565\u4E86\u4E2D\u95F4 ${omitted} \u5B57\u7B26\u3002\u5982\u9700\u67E5\u770B\u5B8C\u6574\u5185\u5BB9\uFF0C\u8BF7\u4F7F\u7528 read_file \u5206\u6BB5\u8BFB\u53D6` + (toolName === "bash" ? "\uFF0C\u6216\u7F29\u5C0F\u547D\u4EE4\u8303\u56F4" : "") + `] ...
7482
-
7483
- ` + tail;
7484
- }
7485
7501
  var ToolExecutor = class {
7486
7502
  constructor(registry) {
7487
7503
  this.registry = registry;
@@ -7553,7 +7569,7 @@ var ToolExecutor = class {
7553
7569
  this.printToolCall(call);
7554
7570
  try {
7555
7571
  const rawContent = await tool.execute(call.arguments);
7556
- const content = truncateOutput2(rawContent, call.name);
7572
+ const content = truncateOutput(rawContent, call.name);
7557
7573
  const wasTruncated = content !== rawContent;
7558
7574
  this.printToolResult(call.name, rawContent, false, wasTruncated);
7559
7575
  runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
@@ -7592,7 +7608,7 @@ var ToolExecutor = class {
7592
7608
  }
7593
7609
  try {
7594
7610
  const rawContent = await tool.execute(call.arguments);
7595
- const content = truncateOutput2(rawContent, call.name);
7611
+ const content = truncateOutput(rawContent, call.name);
7596
7612
  const wasTruncated = content !== rawContent;
7597
7613
  this.printToolResult(call.name, rawContent, false, wasTruncated);
7598
7614
  runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
@@ -7650,7 +7666,7 @@ var ToolExecutor = class {
7650
7666
  for (let i = 0; i < calls.length; i++) {
7651
7667
  const call = calls[i];
7652
7668
  const filePath = String(call.arguments["path"] ?? "");
7653
- console.log(theme.warning(` [${i + 1}] `) + chalk9.white(call.name) + theme.dim(": ") + theme.accent(filePath));
7669
+ console.log(theme.warning(` [${i + 1}] `) + chalk8.white(call.name) + theme.dim(": ") + theme.accent(filePath));
7654
7670
  this.printDiffPreview(call);
7655
7671
  }
7656
7672
  console.log(theme.dim("\u2500".repeat(50)));
@@ -7667,7 +7683,7 @@ var ToolExecutor = class {
7667
7683
  }
7668
7684
  try {
7669
7685
  const rawContent = await tool.execute(call.arguments);
7670
- const content = truncateOutput2(rawContent, call.name);
7686
+ const content = truncateOutput(rawContent, call.name);
7671
7687
  const wasTruncated = content !== rawContent;
7672
7688
  this.printToolResult(call.name, rawContent, false, wasTruncated);
7673
7689
  results.push({ callId: call.id, content, isError: false });
@@ -7743,7 +7759,7 @@ var ToolExecutor = class {
7743
7759
  console.log();
7744
7760
  const icon = dangerLevel === "write" ? theme.toolCall("\u270E Tool: ") : theme.heading(theme.accent("\u2699 Tool: "));
7745
7761
  const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
7746
- console.log(icon + chalk9.white(call.name) + roundBadge);
7762
+ console.log(icon + chalk8.white(call.name) + roundBadge);
7747
7763
  for (const [key, val] of Object.entries(call.arguments)) {
7748
7764
  let valStr;
7749
7765
  if (Array.isArray(val)) {
@@ -7754,7 +7770,7 @@ var ToolExecutor = class {
7754
7770
  } else {
7755
7771
  valStr = String(val);
7756
7772
  }
7757
- console.log(theme.dim(` ${key}: `) + chalk9.white(valStr));
7773
+ console.log(theme.dim(` ${key}: `) + chalk8.white(valStr));
7758
7774
  }
7759
7775
  }
7760
7776
  /**
@@ -7771,7 +7787,7 @@ var ToolExecutor = class {
7771
7787
  if (existsSync15(filePath)) {
7772
7788
  let oldContent;
7773
7789
  try {
7774
- oldContent = readFileSync9(filePath, "utf-8");
7790
+ oldContent = readFileSync8(filePath, "utf-8");
7775
7791
  } catch {
7776
7792
  return;
7777
7793
  }
@@ -7821,7 +7837,7 @@ var ToolExecutor = class {
7821
7837
  const to = Number(call.arguments["delete_to_line"] ?? from);
7822
7838
  let fileContent;
7823
7839
  try {
7824
- fileContent = readFileSync9(filePath, "utf-8");
7840
+ fileContent = readFileSync8(filePath, "utf-8");
7825
7841
  } catch {
7826
7842
  return;
7827
7843
  }
@@ -7846,7 +7862,7 @@ var ToolExecutor = class {
7846
7862
  const moreLines = lines.length > maxLines ? theme.dim(`
7847
7863
  ... (${lines.length - maxLines} more lines)`) : "";
7848
7864
  const truncatedNote = wasTruncated ? theme.warning(`
7849
- \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS2} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
7865
+ \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
7850
7866
  console.log(theme.toolResult("\u2713 Result: ") + theme.dim(preview) + moreLines + truncatedNote);
7851
7867
  }
7852
7868
  console.log();
@@ -8139,9 +8155,13 @@ Managing ${displayName} API Key`);
8139
8155
  import { existsSync as existsSync16, readFileSync as readFileSync10, readdirSync as readdirSync9, mkdirSync as mkdirSync9 } from "fs";
8140
8156
  import { join as join11, extname as extname3 } from "path";
8141
8157
  import { execSync as execSync7 } from "child_process";
8142
- function parseSimpleYaml(text) {
8158
+
8159
+ // src/skills/types.ts
8160
+ import { readFileSync as readFileSync9 } from "fs";
8161
+ import { basename as basename5 } from "path";
8162
+ function parseSimpleYaml(yaml) {
8143
8163
  const result = {};
8144
- for (const line of text.split("\n")) {
8164
+ for (const line of yaml.split("\n")) {
8145
8165
  const match = line.match(/^(\w+)\s*:\s*(.+)$/);
8146
8166
  if (match) {
8147
8167
  result[match[1]] = match[2].trim().replace(/^['"]|['"]$/g, "");
@@ -8149,6 +8169,45 @@ function parseSimpleYaml(text) {
8149
8169
  }
8150
8170
  return result;
8151
8171
  }
8172
+ function parseYamlArray(value) {
8173
+ const bracketMatch = value.match(/^\[(.+)]$/);
8174
+ if (bracketMatch) {
8175
+ return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
8176
+ }
8177
+ return [value.trim()];
8178
+ }
8179
+ function parseSkillFile(filePath) {
8180
+ let raw;
8181
+ try {
8182
+ raw = readFileSync9(filePath, "utf-8");
8183
+ } catch {
8184
+ return null;
8185
+ }
8186
+ const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
8187
+ if (!frontmatterMatch) {
8188
+ return {
8189
+ meta: {
8190
+ name: basename5(filePath, ".md"),
8191
+ description: ""
8192
+ },
8193
+ content: raw.trim(),
8194
+ filePath
8195
+ };
8196
+ }
8197
+ const [, yaml, content] = frontmatterMatch;
8198
+ const parsed = parseSimpleYaml(yaml);
8199
+ return {
8200
+ meta: {
8201
+ name: parsed["name"] ?? basename5(filePath, ".md"),
8202
+ description: parsed["description"] ?? "",
8203
+ tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
8204
+ },
8205
+ content: content.trim(),
8206
+ filePath
8207
+ };
8208
+ }
8209
+
8210
+ // src/repl/custom-commands.ts
8152
8211
  function parseCommandFile(filePath) {
8153
8212
  let content;
8154
8213
  try {
@@ -8228,9 +8287,11 @@ var CustomCommandManager = class {
8228
8287
  import { existsSync as existsSync17, readFileSync as readFileSync11, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, mkdirSync as mkdirSync10 } from "fs";
8229
8288
  import { join as join12 } from "path";
8230
8289
  import { homedir as homedir4 } from "os";
8231
- var DEV_STATE_MAX_CHARS = 4e3;
8290
+ var DEV_STATE_MAX_CHARS = 6e3;
8232
8291
  var SNAPSHOT_PROMPT = `You are about to be replaced by a different AI model. Please generate a structured development state snapshot so the next model can continue seamlessly.
8233
8292
 
8293
+ CRITICAL: Be SPECIFIC and DETAILED. Include exact values, file paths, format requirements, and constraints \u2014 vague summaries are useless to the next model.
8294
+
8234
8295
  Output ONLY the snapshot in the following exact format (no preamble, no explanation):
8235
8296
 
8236
8297
  ## Development State Snapshot
@@ -8238,26 +8299,39 @@ Output ONLY the snapshot in the following exact format (no preamble, no explanat
8238
8299
  ### Current Task
8239
8300
  [1-2 sentence summary of the primary task/goal the user is working on]
8240
8301
 
8302
+ ### Key Parameters & Constraints
8303
+ - [List ALL specific parameters, values, format requirements discovered during this conversation]
8304
+ - [Include exact numbers, dimensions, scoring rules, naming conventions, etc.]
8305
+ - [Example: "\u8003\u8BD5\u65F6\u95F4 90 \u5206\u949F\uFF0C\u6EE1\u5206 200 \u5206\uFF0C\u5355\u9009 40 \u9898\xD72 \u5206 + \u591A\u9009 20 \u9898\xD74 \u5206 + \u5224\u65AD 20 \u9898\xD72 \u5206"]
8306
+ - [Example: "\u6587\u4EF6\u547D\u540D\u683C\u5F0F YYYYMMDD-NN-\u6A21\u8003-\u96BE\u5EA6.md\uFF0C\u4FDD\u5B58\u5230 exam_papers/ \u76EE\u5F55"]
8307
+
8241
8308
  ### Completed Steps
8242
- - [List each completed step as a bullet point]
8309
+ - [List each completed step with specific details (file paths, key outcomes)]
8243
8310
 
8244
8311
  ### In-Progress Work
8245
8312
  - [List any work that was started but not finished]
8246
8313
 
8247
- ### Key Decisions & Context
8248
- - [Important architectural or design decisions made during this conversation]
8249
- - [Any user preferences or constraints that were established]
8314
+ ### Critical Reference Files
8315
+ - [List file paths that the next model MUST read before doing any work]
8316
+ - [Include brief description of each file's purpose]
8317
+ - [Example: "Exam2025.md \u2014 \u91D1\u6807\u771F\u9898\u683C\u5F0F\u53C2\u8003\uFF0890\u5206\u949F/200\u5206/\u9898\u578B\u5206\u5E03\uFF09"]
8318
+ - [Example: "\u51FA\u9898\u98CE\u683C\u6307\u5357.md \u2014 \u91D1\u6807\u98CE\u683C\u89C4\u8303\uFF08\u4EBA\u7269\u8BBE\u5B9A/\u573A\u666F\u6784\u5EFA/\u65F6\u4E8B\u70ED\u70B9\uFF09"]
8319
+
8320
+ ### Modified/Created Files
8321
+ - [List any files that were created or modified, with brief notes on content]
8250
8322
 
8251
- ### Modified Files
8252
- - [List any files that were created, modified, or discussed, with brief notes]
8323
+ ### Key Decisions & Context
8324
+ - [Important decisions, user preferences, or constraints established]
8253
8325
 
8254
8326
  ### Next Steps
8255
8327
  - [What should be done next to continue this work]
8328
+ - [Include specific instructions the next model should follow]
8256
8329
 
8257
8330
  ### Important Notes
8258
8331
  - [Any warnings, caveats, or critical context the next model needs to know]
8332
+ - [Things that went wrong or should be avoided]
8259
8333
 
8260
- If any section has no content, write "(none)" for that section. Be concise but thorough.`;
8334
+ If any section has no content, write "(none)" for that section. Be thorough \u2014 the next model may have access to our conversation messages, but the detailed tool call results (file contents, command outputs) are NOT preserved. This snapshot is the primary source of specific details and context.`;
8261
8335
  function sessionHasMeaningfulContent(messages) {
8262
8336
  if (messages.length < 2) return false;
8263
8337
  const hasUser = messages.some((m) => m.role === "user");
@@ -8540,10 +8614,9 @@ var McpClient = class {
8540
8614
  });
8541
8615
  });
8542
8616
  }
8543
- /** 拒绝所有挂起的请求 */
8617
+ /** 拒绝所有挂起的请求。pending.reject() 内部的 cleanup() 已包含 clearTimeout。 */
8544
8618
  rejectAllPending(error) {
8545
- for (const [id, pending] of this.pendingRequests) {
8546
- clearTimeout(pending.timer);
8619
+ for (const [, pending] of this.pendingRequests) {
8547
8620
  pending.reject(error);
8548
8621
  }
8549
8622
  this.pendingRequests.clear();
@@ -8800,59 +8873,6 @@ var McpManager = class {
8800
8873
  // src/skills/manager.ts
8801
8874
  import { existsSync as existsSync18, readdirSync as readdirSync10, mkdirSync as mkdirSync11 } from "fs";
8802
8875
  import { join as join13 } from "path";
8803
-
8804
- // src/skills/types.ts
8805
- import { readFileSync as readFileSync12 } from "fs";
8806
- import { basename as basename5 } from "path";
8807
- function parseSimpleYaml2(yaml) {
8808
- const result = {};
8809
- for (const line of yaml.split("\n")) {
8810
- const match = line.match(/^(\w+)\s*:\s*(.+)$/);
8811
- if (match) {
8812
- result[match[1]] = match[2].trim();
8813
- }
8814
- }
8815
- return result;
8816
- }
8817
- function parseYamlArray(value) {
8818
- const bracketMatch = value.match(/^\[(.+)]$/);
8819
- if (bracketMatch) {
8820
- return bracketMatch[1].split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, ""));
8821
- }
8822
- return [value.trim()];
8823
- }
8824
- function parseSkillFile(filePath) {
8825
- let raw;
8826
- try {
8827
- raw = readFileSync12(filePath, "utf-8");
8828
- } catch {
8829
- return null;
8830
- }
8831
- const frontmatterMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
8832
- if (!frontmatterMatch) {
8833
- return {
8834
- meta: {
8835
- name: basename5(filePath, ".md"),
8836
- description: ""
8837
- },
8838
- content: raw.trim(),
8839
- filePath
8840
- };
8841
- }
8842
- const [, yaml, content] = frontmatterMatch;
8843
- const parsed = parseSimpleYaml2(yaml);
8844
- return {
8845
- meta: {
8846
- name: parsed["name"] ?? basename5(filePath, ".md"),
8847
- description: parsed["description"] ?? "",
8848
- tools: parsed["tools"] ? parseYamlArray(parsed["tools"]) : void 0
8849
- },
8850
- content: content.trim(),
8851
- filePath
8852
- };
8853
- }
8854
-
8855
- // src/skills/manager.ts
8856
8876
  var SKILL_CONTENT_WARN_CHARS = 5e3;
8857
8877
  var SkillManager = class {
8858
8878
  skills = /* @__PURE__ */ new Map();
@@ -8942,8 +8962,8 @@ function sendNotification(title, body) {
8942
8962
  { detached: true, stdio: "ignore" }
8943
8963
  ).unref();
8944
8964
  } else if (plat === "win32") {
8945
- const safeTitle = title.replace(/"/g, '`"');
8946
- const safeBody = body.replace(/"/g, '`"');
8965
+ const safeTitle = title.replace(/`/g, "``").replace(/"/g, '`"');
8966
+ const safeBody = body.replace(/`/g, "``").replace(/"/g, '`"');
8947
8967
  const script = [
8948
8968
  "Add-Type -AssemblyName System.Windows.Forms",
8949
8969
  "$n = New-Object System.Windows.Forms.NotifyIcon",
@@ -8992,12 +9012,12 @@ function parseAtReferences(input2, cwd) {
8992
9012
  continue;
8993
9013
  }
8994
9014
  if (mime) {
8995
- const fileSize = statSync8(absPath).size;
9015
+ const fileSize = statSync9(absPath).size;
8996
9016
  if (fileSize > MAX_IMAGE_BYTES) {
8997
9017
  refs.push({ path: rawPath, type: "toolarge" });
8998
9018
  continue;
8999
9019
  }
9000
- const data = readFileSync13(absPath).toString("base64");
9020
+ const data = readFileSync12(absPath).toString("base64");
9001
9021
  imageParts.push({
9002
9022
  type: "image_url",
9003
9023
  image_url: { url: `data:${mime};base64,${data}` }
@@ -9005,7 +9025,7 @@ function parseAtReferences(input2, cwd) {
9005
9025
  refs.push({ path: rawPath, type: "image" });
9006
9026
  textBody = textBody.replace(match[0], "").trim();
9007
9027
  } else {
9008
- const content = readFileSync13(absPath, "utf-8");
9028
+ const content = readFileSync12(absPath, "utf-8");
9009
9029
  const inlined = `
9010
9030
 
9011
9031
  [File: ${rawPath}]
@@ -9203,7 +9223,7 @@ var Repl = class {
9203
9223
  const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
9204
9224
  let isDir;
9205
9225
  try {
9206
- isDir = statSync8(fullPath).isDirectory();
9226
+ isDir = statSync9(fullPath).isDirectory();
9207
9227
  } catch {
9208
9228
  continue;
9209
9229
  }
@@ -9235,7 +9255,7 @@ ${treeLines.join("\n")}`
9235
9255
  const fullPath = join14(dir, name);
9236
9256
  let st;
9237
9257
  try {
9238
- st = statSync8(fullPath);
9258
+ st = statSync9(fullPath);
9239
9259
  } catch {
9240
9260
  continue;
9241
9261
  }
@@ -9247,7 +9267,7 @@ ${treeLines.join("\n")}`
9247
9267
  if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
9248
9268
  if (st.size > MAX_FILE_CHARS * 3) continue;
9249
9269
  try {
9250
- let content = readFileSync13(fullPath, "utf-8");
9270
+ let content = readFileSync12(fullPath, "utf-8");
9251
9271
  if (content.length > MAX_FILE_CHARS) {
9252
9272
  content = content.slice(0, MAX_FILE_CHARS) + `
9253
9273
  ... (\u622A\u65AD\uFF0C\u5171 ${content.length} \u5B57\u7B26)`;
@@ -9282,7 +9302,7 @@ ${content}
9282
9302
  }
9283
9303
  let isDir;
9284
9304
  try {
9285
- isDir = statSync8(absPath).isDirectory();
9305
+ isDir = statSync9(absPath).isDirectory();
9286
9306
  } catch {
9287
9307
  return { success: false, charCount: 0, added: false, error: `Cannot access: ${dirPath}` };
9288
9308
  }
@@ -9313,7 +9333,7 @@ ${content}
9313
9333
  for (const candidate of candidates) {
9314
9334
  const fullPath = join14(dir, candidate);
9315
9335
  if (existsSync19(fullPath)) {
9316
- const content = readFileSync13(fullPath, "utf-8").trim();
9336
+ const content = readFileSync12(fullPath, "utf-8").trim();
9317
9337
  if (content) return { filePath: fullPath, content };
9318
9338
  }
9319
9339
  }
@@ -9344,7 +9364,7 @@ ${content}
9344
9364
  const mcpPath = join14(projectRoot, MCP_PROJECT_CONFIG_NAME);
9345
9365
  if (!existsSync19(mcpPath)) return null;
9346
9366
  try {
9347
- const raw = JSON.parse(readFileSync13(mcpPath, "utf-8"));
9367
+ const raw = JSON.parse(readFileSync12(mcpPath, "utf-8"));
9348
9368
  const servers = raw?.mcpServers;
9349
9369
  if (!servers || typeof servers !== "object") {
9350
9370
  process.stderr.write(
@@ -9391,7 +9411,7 @@ ${content}
9391
9411
  return { layers: [], mergedContent: "" };
9392
9412
  }
9393
9413
  if (existsSync19(fullPath)) {
9394
- const content = readFileSync13(fullPath, "utf-8").trim();
9414
+ const content = readFileSync12(fullPath, "utf-8").trim();
9395
9415
  if (content) {
9396
9416
  const layer = {
9397
9417
  level: "project",
@@ -9450,7 +9470,7 @@ ${content}
9450
9470
  loadMemoryContent() {
9451
9471
  const memoryPath = join14(this.config.getConfigDir(), MEMORY_FILE_NAME);
9452
9472
  if (!existsSync19(memoryPath)) return null;
9453
- let content = readFileSync13(memoryPath, "utf-8").trim();
9473
+ let content = readFileSync12(memoryPath, "utf-8").trim();
9454
9474
  if (!content) return null;
9455
9475
  if (content.length > MEMORY_MAX_CHARS) {
9456
9476
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -9502,6 +9522,7 @@ ${memory.content}`);
9502
9522
  `# Development State Handoff
9503
9523
 
9504
9524
  \u4EE5\u4E0B\u662F\u524D\u4E00\u4E2A AI \u6A21\u578B\u751F\u6210\u7684\u5F00\u53D1\u72B6\u6001\u5FEB\u7167\uFF0C\u8BF7\u636E\u6B64\u65E0\u7F1D\u7EE7\u7EED\u7528\u6237\u7684\u5DE5\u4F5C\u3002
9525
+ \u91CD\u8981\uFF1A\u5982\u679C\u5FEB\u7167\u4E2D\u63D0\u5230\u4E86\u5173\u952E\u53C2\u8003\u6587\u4EF6\uFF08\u5982\u6A21\u677F\u3001\u683C\u5F0F\u89C4\u8303\u3001\u98CE\u683C\u6307\u5357\uFF09\uFF0C\u5728\u6267\u884C\u4EFB\u4F55\u751F\u6210/\u521B\u5EFA\u64CD\u4F5C\u4E4B\u524D\uFF0C\u8BF7\u5148\u7528 read_file \u5DE5\u5177\u8BFB\u53D6\u8FD9\u4E9B\u53C2\u8003\u6587\u4EF6\uFF0C\u786E\u4FDD\u4F60\u5B8C\u5168\u7406\u89E3\u683C\u5F0F\u8981\u6C42\u548C\u7EA6\u675F\u6761\u4EF6\u3002\u4E0D\u8981\u51ED\u5047\u8BBE\u884C\u4E8B\uFF0C\u5FC5\u987B\u57FA\u4E8E\u5B9E\u9645\u6587\u4EF6\u5185\u5BB9\u3002
9505
9526
 
9506
9527
  ` + devState
9507
9528
  );
@@ -9641,7 +9662,7 @@ ${response.content.trim()}
9641
9662
  systemPrompt: this.buildCurrentSystemPrompt(),
9642
9663
  stream: false,
9643
9664
  temperature: 0.3,
9644
- maxTokens: modelParams.maxTokens ?? 2048,
9665
+ maxTokens: Math.min(modelParams.maxTokens ?? 4096, 4096),
9645
9666
  timeout: modelParams.timeout ?? 3e4
9646
9667
  });
9647
9668
  spinner.stop();
@@ -9668,7 +9689,7 @@ ${response.content.trim()}
9668
9689
  const planTag = this.planMode ? theme.warning("[PLAN]") : "";
9669
9690
  const modelParams = this.getModelParams();
9670
9691
  const thinkTag = modelParams.thinking ? theme.accent("[THINK]") : "";
9671
- const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag + chalk10.white(" > ");
9692
+ const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag + chalk9.white(" > ");
9672
9693
  this.rl.setPrompt(promptStr);
9673
9694
  }
9674
9695
  showPrompt() {
@@ -10160,7 +10181,7 @@ Session '${this.resumeSessionId}' not found.
10160
10181
  if (!entry.toLowerCase().startsWith(prefix.toLowerCase())) continue;
10161
10182
  try {
10162
10183
  const fullPath = join14(absDir, entry);
10163
- const stat = statSync8(fullPath);
10184
+ const stat = statSync9(fullPath);
10164
10185
  const rel = dir === "." ? entry : `${dir}/${entry}`;
10165
10186
  results.push(stat.isDirectory() ? `${rel}/` : rel);
10166
10187
  } catch {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-WWHIELPD.js";
5
+ } from "./chunk-7RQ6QCDG.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",