jinzd-ai-cli 0.1.49 → 0.1.51
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,55 @@ 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-08,v0.1.49 → v0.1.50)
|
|
354
|
+
|
|
355
|
+
### 代码质量:L1 低危修复 — run-tests.ts package.json 细粒度错误处理
|
|
356
|
+
|
|
357
|
+
**问题**:`detectProject()` 中 `JSON.parse(readFileSync('package.json'))` 错误处理粗糙——文件读取和 JSON 解析混在同一个 catch 中;解析失败时静默 fallthrough 可能误判为 Python/Go 项目;无 test script 时不尝试检测已安装的测试框架。
|
|
358
|
+
|
|
359
|
+
**修复**(`src/tools/builtin/run-tests.ts`):
|
|
360
|
+
|
|
361
|
+
新增 `safeReadPackageJson(cwd)` 辅助函数——细粒度四层处理:
|
|
362
|
+
1. **文件读取错误**:区分 `EACCES`/`EPERM`(权限问题)和其他读取错误,分别给出不同提示
|
|
363
|
+
2. **UTF-8 BOM 处理**:自动剥离 BOM(Windows Notepad 等编辑器常见问题),防止 `JSON.parse` 失败
|
|
364
|
+
3. **空文件检测**:`raw.trim() === ''` 时给出明确 "package.json is empty" 提示
|
|
365
|
+
4. **JSON 解析错误细分**:区分语法错误(`Unexpected token`)、截断文件(`Unexpected end`)、根不是对象(数组或其他类型)
|
|
366
|
+
|
|
367
|
+
新增 `detectNodeTestFramework(cwd, pkg)` 辅助函数——当 package.json 有效但无 `scripts.test` 时:
|
|
368
|
+
- 从 `devDependencies` + `dependencies` + 配置文件检测 5 种测试框架:
|
|
369
|
+
- **Vitest**:`vitest` 依赖 或 `vitest.config.{ts,js,mts}`
|
|
370
|
+
- **Jest**:`jest` 依赖 或 `jest.config.{js,ts,mjs}`
|
|
371
|
+
- **Mocha**:`mocha` 依赖 或 `.mocharc.{yml,yaml,json,js}`
|
|
372
|
+
- **Ava**:`ava` 依赖
|
|
373
|
+
- **Playwright**:`@playwright/test` 依赖
|
|
374
|
+
- 检测到框架时直接使用 `npx <framework>` 命令,无需 `scripts.test`
|
|
375
|
+
|
|
376
|
+
更新 filter 参数处理——不同 Node 测试框架使用正确的 filter 语法:
|
|
377
|
+
- vitest/jest → `-t "filter"`
|
|
378
|
+
- mocha/playwright → `--grep "filter"`
|
|
379
|
+
- npm test → `-- --grep "filter"`(passthrough)
|
|
380
|
+
|
|
381
|
+
**改善效果**:
|
|
382
|
+
- package.json 格式错误时不再静默 fallthrough,返回 `npm (package.json error)` 仍识别为 Node 项目
|
|
383
|
+
- 安装了 vitest/jest/mocha 但未配置 `scripts.test` 的项目现在能自动检测并运行测试
|
|
384
|
+
- 错误信息更具体、更有指导性
|
|
385
|
+
|
|
386
|
+
### 版本与收尾
|
|
387
|
+
- `src/core/constants.ts`:VERSION `0.1.49` → `0.1.50`
|
|
388
|
+
- `package.json`:version 同步
|
|
389
|
+
- 构建验证:`npm run build` 零错误(ESM + CJS 双产物)
|
|
390
|
+
- 代码审查报告 L1 条目标记为 ✅ 已修复
|
|
391
|
+
|
|
392
|
+
### 本轮变更文件汇总
|
|
393
|
+
|
|
394
|
+
| 文件 | 变更类型 | 说明 |
|
|
395
|
+
|------|---------|------|
|
|
396
|
+
| `src/tools/builtin/run-tests.ts` | 修改 | safeReadPackageJson + detectNodeTestFramework + filter 语法更新 |
|
|
397
|
+
| `src/core/constants.ts` | 修改 | VERSION 0.1.49 → 0.1.50 |
|
|
398
|
+
| `package.json` | 修改 | version 0.1.49 → 0.1.50 |
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
353
402
|
## 本轮开发完成记录(2026-03-08,v0.1.47 → v0.1.48)
|
|
354
403
|
|
|
355
404
|
### Tier 2 体验增强:/undo 增强 + /fork 对话分支
|
|
@@ -415,7 +464,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
415
464
|
### 下一步建议
|
|
416
465
|
|
|
417
466
|
#### Tier 2 — 体验增强(剩余)
|
|
418
|
-
1.
|
|
467
|
+
1. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
|
|
419
468
|
2. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
|
|
420
469
|
3. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
|
|
421
470
|
|
|
@@ -608,7 +657,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
608
657
|
3. **`/diff` 命令**:显示当前 session 内所有文件修改的汇总 diff,便于 AI 操作后快速审查变更
|
|
609
658
|
|
|
610
659
|
#### Tier 2 — 体验增强
|
|
611
|
-
4.
|
|
660
|
+
4. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复(safeReadPackageJson + detectNodeTestFramework)
|
|
612
661
|
5. **IDE 集成**:VS Code 扩展(架构已准备就绪,core/providers/tools 无终端依赖)
|
|
613
662
|
6. **OAuth/浏览器登录**:无需手动填 API Key,打开浏览器完成 OAuth 流程自动保存 token
|
|
614
663
|
7. ~~**`/undo` 增强**~~:✅ 已在 v0.1.48 实现(bash 文件追踪 + /undo list + /undo <n>)
|
|
@@ -687,7 +736,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
687
736
|
### 下一步建议
|
|
688
737
|
1. ~~**Extended Thinking**~~:✅ 已在 v0.1.38 实现
|
|
689
738
|
2. ~~**theme 迁移扩展**~~:✅ 已在 v0.1.38 全覆盖迁移
|
|
690
|
-
3.
|
|
739
|
+
3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
|
|
691
740
|
4. **IDE 集成**:VS Code 扩展(架构已准备就绪)
|
|
692
741
|
5. **OAuth/浏览器登录**:无需手动填 API Key
|
|
693
742
|
|
|
@@ -777,7 +826,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
777
826
|
2. ~~**`--resume <id>` 启动参数**~~:✅ 已在 v0.1.37 实现
|
|
778
827
|
3. ~~**Word wrap 配置**~~:✅ 已在 v0.1.37 实现
|
|
779
828
|
4. ~~**主题/颜色自定义**~~:✅ 已在 v0.1.37 实现
|
|
780
|
-
5.
|
|
829
|
+
5. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
|
|
781
830
|
|
|
782
831
|
---
|
|
783
832
|
|
|
@@ -851,7 +900,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
851
900
|
### 下一步建议
|
|
852
901
|
1. **P0 功能缺口**:并行工具调用、`/add-dir` 命令
|
|
853
902
|
2. **P1 功能缺口**:`/memory` 命令、`/doctor` 健康检查、`/bug` 反馈
|
|
854
|
-
3.
|
|
903
|
+
3. ~~**L1 低危**~~:✅ 已在 v0.1.50 修复
|
|
855
904
|
|
|
856
905
|
---
|
|
857
906
|
|
|
@@ -1437,7 +1486,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
1437
1486
|
|
|
1438
1487
|
| # | 文件 | 问题描述 |
|
|
1439
1488
|
|---|------|---------|
|
|
1440
|
-
| L1 | `src/tools/builtin/run-tests.ts
|
|
1489
|
+
| L1 | `src/tools/builtin/run-tests.ts` | ✅ v0.1.50 已修复:`safeReadPackageJson()` 细粒度错误处理 + BOM + 框架自动检测 |
|
|
1441
1490
|
| L2 | `src/tools/builtin/web-fetch.ts` | 恶意 HTML 大量 script 标签时正则性能下降 |
|
|
1442
1491
|
| L3 | `src/session/session.ts` | `new Date(d.created)` 非法字符串返回 Invalid Date,比较失败 |
|
|
1443
1492
|
| L4 | `src/tools/builtin/ask-user.ts` / `google-search.ts` | 模块级全局上下文,多会话架构下会串扰 |
|
|
@@ -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.
|
|
11
|
+
var VERSION = "0.1.51";
|
|
12
12
|
var APP_NAME = "ai-cli";
|
|
13
13
|
var CONFIG_DIR_NAME = ".aicli";
|
|
14
14
|
var CONFIG_FILE_NAME = "config.json";
|
|
@@ -80,6 +80,73 @@ var REPO_URL = "https://gitee.com/jinzhengdong/ai-courses";
|
|
|
80
80
|
|
|
81
81
|
// src/tools/builtin/run-tests.ts
|
|
82
82
|
var IS_WINDOWS = platform() === "win32";
|
|
83
|
+
function detectNodeTestFramework(cwd, pkg) {
|
|
84
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
85
|
+
const deps = pkg.dependencies ?? {};
|
|
86
|
+
const allDeps = { ...deps, ...devDeps };
|
|
87
|
+
if ("vitest" in allDeps || existsSync(join(cwd, "vitest.config.ts")) || existsSync(join(cwd, "vitest.config.js")) || existsSync(join(cwd, "vitest.config.mts"))) {
|
|
88
|
+
return { type: "node", framework: "vitest", command: "npx vitest run" };
|
|
89
|
+
}
|
|
90
|
+
if ("jest" in allDeps || existsSync(join(cwd, "jest.config.js")) || existsSync(join(cwd, "jest.config.ts")) || existsSync(join(cwd, "jest.config.mjs"))) {
|
|
91
|
+
return { type: "node", framework: "jest", command: "npx jest" };
|
|
92
|
+
}
|
|
93
|
+
if ("mocha" in allDeps || existsSync(join(cwd, ".mocharc.yml")) || existsSync(join(cwd, ".mocharc.yaml")) || existsSync(join(cwd, ".mocharc.json")) || existsSync(join(cwd, ".mocharc.js"))) {
|
|
94
|
+
return { type: "node", framework: "mocha", command: "npx mocha" };
|
|
95
|
+
}
|
|
96
|
+
if ("ava" in allDeps) {
|
|
97
|
+
return { type: "node", framework: "ava", command: "npx ava" };
|
|
98
|
+
}
|
|
99
|
+
if ("@playwright/test" in allDeps) {
|
|
100
|
+
return { type: "node", framework: "playwright", command: "npx playwright test" };
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function safeReadPackageJson(cwd) {
|
|
105
|
+
const filePath = join(cwd, "package.json");
|
|
106
|
+
let raw;
|
|
107
|
+
try {
|
|
108
|
+
raw = readFileSync(filePath, "utf-8");
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const code = err.code;
|
|
111
|
+
if (code === "EACCES" || code === "EPERM") {
|
|
112
|
+
process.stderr.write(`[Warning] Permission denied reading package.json (${code})
|
|
113
|
+
`);
|
|
114
|
+
} else {
|
|
115
|
+
process.stderr.write(
|
|
116
|
+
`[Warning] Cannot read package.json: ${err instanceof Error ? err.message : String(err)}
|
|
117
|
+
`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
if (raw.charCodeAt(0) === 65279) {
|
|
123
|
+
raw = raw.slice(1);
|
|
124
|
+
}
|
|
125
|
+
if (!raw.trim()) {
|
|
126
|
+
process.stderr.write("[Warning] package.json is empty\n");
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
const parsed = JSON.parse(raw);
|
|
131
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
132
|
+
process.stderr.write("[Warning] package.json root is not a JSON object\n");
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return parsed;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
138
|
+
if (msg.includes("Unexpected token")) {
|
|
139
|
+
process.stderr.write(`[Warning] package.json has syntax error: ${msg}
|
|
140
|
+
`);
|
|
141
|
+
} else if (msg.includes("Unexpected end")) {
|
|
142
|
+
process.stderr.write("[Warning] package.json is truncated or incomplete\n");
|
|
143
|
+
} else {
|
|
144
|
+
process.stderr.write(`[Warning] package.json parse failed: ${msg}
|
|
145
|
+
`);
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
83
150
|
function detectProject(cwd) {
|
|
84
151
|
if (existsSync(join(cwd, "pom.xml"))) {
|
|
85
152
|
return { type: "java", framework: "Maven (JUnit)", command: IS_WINDOWS ? "mvn.cmd test" : "mvn test" };
|
|
@@ -90,17 +157,16 @@ function detectProject(cwd) {
|
|
|
90
157
|
return { type: "java", framework: "Gradle (JUnit)", command: cmd };
|
|
91
158
|
}
|
|
92
159
|
if (existsSync(join(cwd, "package.json"))) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const testScript = pkg
|
|
160
|
+
const pkg = safeReadPackageJson(cwd);
|
|
161
|
+
if (pkg) {
|
|
162
|
+
const testScript = pkg.scripts?.test;
|
|
96
163
|
if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') {
|
|
97
164
|
return { type: "node", framework: "npm", command: "npm test" };
|
|
98
165
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
);
|
|
166
|
+
const frameworkDetected = detectNodeTestFramework(cwd, pkg);
|
|
167
|
+
if (frameworkDetected) return frameworkDetected;
|
|
168
|
+
} else {
|
|
169
|
+
return { type: "node", framework: "npm (package.json error)", command: "npm test" };
|
|
104
170
|
}
|
|
105
171
|
}
|
|
106
172
|
if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "setup.py")) || existsSync(join(cwd, "pytest.ini"))) {
|
|
@@ -268,7 +334,17 @@ async function executeTests(args) {
|
|
|
268
334
|
} else if (detected.type === "go") {
|
|
269
335
|
command = `go test ./... -run "${filter}"`;
|
|
270
336
|
} else if (detected.type === "node") {
|
|
271
|
-
|
|
337
|
+
if (detected.framework === "vitest") {
|
|
338
|
+
command += ` -t "${filter}"`;
|
|
339
|
+
} else if (detected.framework === "jest") {
|
|
340
|
+
command += ` -t "${filter}"`;
|
|
341
|
+
} else if (detected.framework === "mocha") {
|
|
342
|
+
command += ` --grep "${filter}"`;
|
|
343
|
+
} else if (detected.framework === "playwright") {
|
|
344
|
+
command += ` --grep "${filter}"`;
|
|
345
|
+
} else {
|
|
346
|
+
command += ` -- --grep "${filter}"`;
|
|
347
|
+
}
|
|
272
348
|
}
|
|
273
349
|
}
|
|
274
350
|
}
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
30
30
|
VERSION,
|
|
31
31
|
runTestsTool
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-U4BEPHO5.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
35
|
import { program } from "commander";
|
|
@@ -4288,7 +4288,7 @@ ${hint}` : "")
|
|
|
4288
4288
|
description: "Run project tests and show structured report",
|
|
4289
4289
|
usage: "/test [command|filter]",
|
|
4290
4290
|
async execute(args, _ctx) {
|
|
4291
|
-
const { executeTests } = await import("./run-tests-
|
|
4291
|
+
const { executeTests } = await import("./run-tests-AWR6YAYZ.js");
|
|
4292
4292
|
const argStr = args.join(" ").trim();
|
|
4293
4293
|
let testArgs = {};
|
|
4294
4294
|
if (argStr) {
|
|
@@ -5234,21 +5234,19 @@ ${pdfText}`;
|
|
|
5234
5234
|
const dir = dirname3(normalizedPath);
|
|
5235
5235
|
const nameNoExt = basename2(normalizedPath, ext);
|
|
5236
5236
|
const textAlts = [".md", ".txt", ".html"].map((e) => resolve3(dir, nameNoExt + e)).filter(existsSync8);
|
|
5237
|
-
|
|
5238
|
-
PDF
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
\u8BF7\u4F7F\u7528 read_file \u8BFB\u53D6\u4E0A\u8FF0\u6587\u4EF6\u3002
|
|
5242
|
-
|
|
5243
|
-
|
|
5237
|
+
if (textAlts.length > 0) {
|
|
5238
|
+
return `[PDF file: ${filePath}]
|
|
5239
|
+
\u6B64 PDF \u6587\u4EF6\u5F53\u524D\u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u63D0\u53D6\u6587\u672C\uFF0C\u4F46\u627E\u5230\u53EF\u66FF\u4EE3\u7684\u6587\u672C\u7248\u672C\uFF1A
|
|
5240
|
+
` + textAlts.map((p) => ` \u2192 ${basename2(p)}`).join("\n") + `
|
|
5241
|
+
\u8BF7\u4F7F\u7528 read_file \u8BFB\u53D6\u4E0A\u8FF0\u6587\u4EF6\u3002`;
|
|
5242
|
+
}
|
|
5243
|
+
return `[PDF file: ${filePath}]
|
|
5244
|
+
\u6B64 PDF \u6587\u4EF6\u5F53\u524D\u73AF\u5883\u65E0\u6CD5\u81EA\u52A8\u63D0\u53D6\u6587\u672C\uFF08\u9700\u5B89\u88C5 pdftotext \u6216 pdfminer.six\uFF09\u3002
|
|
5245
|
+
\u5EFA\u8BAE\uFF1A\u76F4\u63A5\u53C2\u8003\u9879\u76EE\u4E2D\u5DF2\u6709\u7684\u6587\u672C\u7248\u672C\uFF08.md / .txt\uFF09\uFF0C\u6216\u7528 bash \u5B89\u88C5\u63D0\u53D6\u5DE5\u5177\u540E\u91CD\u8BD5\u3002`;
|
|
5244
5246
|
}
|
|
5245
5247
|
if (BINARY_EXTENSIONS.has(ext)) {
|
|
5246
|
-
return `[Binary file: ${filePath}]
|
|
5247
|
-
\u6B64\u6587\u4EF6\u4E3A\u4E8C\u8FDB\u5236\u683C\u5F0F\
|
|
5248
|
-
\u5982\u9700\u4F7F\u7528\u5176\u5185\u5BB9\uFF0C\u8BF7\u8003\u8651\uFF1A
|
|
5249
|
-
1. \u5C06\u6587\u4EF6\u8F6C\u6362\u4E3A\u6587\u672C\u683C\u5F0F\u540E\u518D\u8BFB\u53D6
|
|
5250
|
-
2. \u4F7F\u7528 bash \u5DE5\u5177\u8C03\u7528\u5916\u90E8\u8F6C\u6362\u7A0B\u5E8F\uFF08\u5982 pandoc \u7B49\uFF09
|
|
5251
|
-
3. \u82E5\u6709\u5BF9\u5E94\u7684\u7EAF\u6587\u672C\u7248\u672C\uFF08.md / .txt\uFF09\uFF0C\u8BF7\u76F4\u63A5\u8BFB\u53D6\u90A3\u4E2A\u6587\u4EF6`;
|
|
5248
|
+
return `[Binary file: ${filePath} (${ext})]
|
|
5249
|
+
\u6B64\u6587\u4EF6\u4E3A\u4E8C\u8FDB\u5236\u683C\u5F0F\uFF0C\u4E0D\u652F\u6301\u76F4\u63A5\u6587\u672C\u8BFB\u53D6\u3002\u5982\u9879\u76EE\u4E2D\u6709\u5BF9\u5E94\u7684\u6587\u672C\u7248\u672C\uFF08.md / .txt\uFF09\uFF0C\u8BF7\u76F4\u63A5\u8BFB\u53D6\u3002`;
|
|
5252
5250
|
}
|
|
5253
5251
|
const buf = readFileSync5(normalizedPath);
|
|
5254
5252
|
if (encoding === "base64") {
|