Skip to content

Commit a8d1e83

Browse files
authored
Merge branch 'claude-code-best:main' into main
2 parents 75c230d + 3d7b32f commit a8d1e83

48 files changed

Lines changed: 1125 additions & 4015 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
name: Bug 报告
3+
description: 报告一个可复现的 bug
4+
title: "bug: "
5+
labels: ["bug"]
6+
assignees: []
7+
---
8+
9+
## 发帖前必读
10+
11+
- [ ] 我已经搜索过 [现有 Issues](https://github.com/claude-code-best/claude-code/issues),没有找到重复。
12+
- [ ] 我使用的是 **最新版本**`bun run build` 或最新 release)。
13+
- [ ] 我已经阅读过 [README](https://github.com/claude-code-best/claude-code) 和相关文档。
14+
15+
**未完成以上检查的 Issue 将被直接关闭。**
16+
17+
---
18+
19+
## 运行环境
20+
21+
| 项目||
22+
|---|---|
23+
| 操作系统| 例如 macOS 15.4、Ubuntu 24.04|
24+
| Bun 版本| 例如 `bun --version` 的输出|
25+
| Claude Code 版本| 例如 `2.4.3` 或 commit hash|
26+
| 安装方式| `bun run build` / npm / 其他|
27+
| 模型| 例如 claude-sonnet-4-6、claude-opus-4-7|
28+
29+
## 复现步骤
30+
31+
1.
32+
2.
33+
3.
34+
35+
## 期望行为
36+
37+
<!-- 应该发生什么? -->
38+
39+
## 实际行为
40+
41+
<!-- 实际发生了什么?如有必要可附截图。 -->
42+
43+
## 相关日志
44+
45+
<!-- 粘贴终端输出或错误信息,请使用 triple backticks 代码块。 -->
46+
47+
```text
48+
```
49+
50+
## 补充信息
51+
52+
<!-- 其他上下文 — 配置、环境变量、尝试过的 workaround 等。 -->

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: 💬 讨论区
4+
url: https://github.com/claude-code-best/claude-code/discussions
5+
about: 使用问题、功能建议和一般讨论 — 请使用 Discussions 而非 Issues。
6+
- name: 📖 项目文档
7+
url: https://github.com/claude-code-best/claude-code
8+
about: 提交 issue 前,请先阅读 README 和相关文档,你的问题可能已经有答案了。
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
name: 功能建议
3+
description: 提出新功能或改进建议
4+
title: "feat: "
5+
labels: ["enhancement"]
6+
assignees: []
7+
---
8+
9+
## 发帖前必读
10+
11+
- [ ] 我已经搜索过 [现有 Issues](https://github.com/claude-code-best/claude-code/issues),没有找到重复。
12+
- [ ] 这是功能建议,不是 Bug 报告或使用问题。
13+
- [ ] 使用问题请前往 [Discussions](https://github.com/claude-code-best/claude-code/discussions)
14+
15+
---
16+
17+
## 要解决的问题
18+
19+
<!-- 这个功能解决什么问题?为什么需要它? -->
20+
21+
## 建议方案
22+
23+
<!-- 描述你建议的实现方式,尽量简洁具体。 -->
24+
25+
## 考虑过的替代方案
26+
27+
<!-- 还有没有想到的其他实现思路? -->
28+
29+
## 补充信息
30+
31+
<!-- 截图、草图、参考资料,或其他有助于说明需求的内容。 -->

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ jobs:
4242
run: |
4343
# Tolerate pre-existing flaky tests (Bun mock pollution / order-dependent state).
4444
# We still require lcov.info to be generated and contain real coverage data.
45-
bun test --coverage --coverage-reporter lcov --coverage-dir coverage 2>&1 | grep -vE '^\s*(\(pass\)|\(skip\))' | sed '/^.*\/__tests__\/.*:$/d' | cat -s || true
45+
set -o pipefail
46+
bun test --coverage --coverage-reporter lcov --coverage-dir coverage 2>&1 | grep -vE '^\s*(\(pass\)|\(skip\))' | sed '/^.*\/__tests__\/.*:$/d' | cat -s
4647
test -s coverage/lcov.info
4748
grep -q '^SF:' coverage/lcov.info
4849

CLAUDE.md

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,6 @@ bun run docs:dev
119119
- **7 providers**: `firstParty` (Anthropic direct), `bedrock` (AWS), `vertex` (Google Cloud), `foundry`, `openai`, `gemini`, `grok` (xAI)。
120120
- Provider selection in `src/utils/model/providers.ts`。优先级:modelType 参数 > 环境变量 > 默认 firstParty。
121121

122-
### Encoding Detection
123-
124-
- **`src/utils/encoding.ts`** — 文件编码检测的唯一入口。提供 `detectEncoding`(三层检测:BOM → UTF-8 fatal → ICU 回退链)和 `decodeBuffer`/`encodeString` 函数。检测基于文件头部 4KB,零外部依赖,仅使用 TextDecoder API。ISO-8859-1 作为最终兜底编码(单字节编码永远成功)。`FileEncoding` 类型扩展了 `BufferEncoding`,覆盖 gbk/gb18030/shift_jis/euc-kr/euc-jp/big5/iso-8859-1。
125-
- `fs.readFileSync(path, { encoding })``encoding` 选项只接受 `BufferEncoding`,不支持 `gbk`/`shift_jis` 等 ICU 编码名。读取非 UTF-8 文件时必须先 `fs.readFileSync(path)` 读 Buffer,再用 `TextDecoder` 解码。项目中所有文件读取路径(fileRead.ts、fileReadCache.ts、file.ts)已统一使用 `decodeBuffer` 函数处理此逻辑。
126-
127122
### Tool System
128123

129124
- **`src/Tool.ts`** — Tool interface definition (`Tool` type) and utilities (`findToolByName`, `toolMatchesName`).
@@ -319,6 +314,48 @@ mock.module("src/utils/debug.ts", debugMock);
319314

320315
路径规则:统一用 `.ts` 扩展名 + `src/*` 别名路径,禁止双重 mock 同一模块。
321316

317+
#### 跨文件 mock 污染(process-global `mock.module`
318+
319+
**Bun 的 `mock.module` 是进程全局的(last-write-wins),不是 per-file 隔离的。** 一个测试文件的 `mock.module` 会污染同一进程中所有其他测试文件的 `require`/`import`
320+
321+
**关键事实(Bun 1.x 实测验证):**
322+
- 测试文件执行顺序**不是严格字母序**,不要假设文件 A 一定在文件 B 之前执行。
323+
- `mock.module``beforeAll` 内部调用时**不会被提升**(hoist),但仍会污染后续加载的文件。
324+
- `require()``import()` 共享同一模块注册表,`mock.module` 对两者都生效。
325+
- 一个模块一旦被某个文件的 `mock.module` 替换,同一进程中所有后续 `require`/`import` 都会返回 mock 值,即使调用方使用不同的 specifier 路径。
326+
327+
**核心规则:不要 mock 被测模块的上层业务模块。**
328+
329+
错误做法(会污染同目录的 `api.test.ts`):
330+
```ts
331+
// launchSchedule.test.ts — 直接 mock 源 API 模块 ❌
332+
mock.module('src/commands/schedule/triggersApi.js', () => ({
333+
listTriggers: listTriggersMock,
334+
// ...
335+
}))
336+
```
337+
338+
正确做法(mock 底层 HTTP 层,不污染业务模块):参考 `launchSkillStore.test.ts``launchVault.test.ts` 的模式。
339+
```ts
340+
// launchSchedule.test.ts — mock axios 而非 triggersApi ✅
341+
import { setupAxiosMock } from '../../../../tests/mocks/axios.js'
342+
343+
const axiosHandle = setupAxiosMock()
344+
axiosHandle.stubs.get = axiosGetMock
345+
axiosHandle.stubs.post = axiosPostMock
346+
347+
beforeAll(() => { axiosHandle.useStubs = true })
348+
afterAll(() => { axiosHandle.useStubs = false })
349+
```
350+
351+
**判断标准:** 如果目录下同时有 `launch*.test.ts`(集成测试)和 `api.test.ts`(回归测试),`launch*.test.ts` 必须 mock axios 而非源 API 模块。`api.test.ts` 需要测试真实 API 模块的 HTTP 方法/URL/错误处理逻辑,被 mock 后就无法测试。
352+
353+
**排查 mock 污染的方法:**
354+
1. 单独运行可疑文件确认其通过:`bun test path/to/suspect.test.ts`
355+
2. 与同目录其他文件一起运行定位污染源:`bun test path/to/__tests__/`
356+
3. 在两个文件中各加 `console.error('[file] milestone')` 追踪实际执行顺序
357+
4. 检查 `mock.module` 的 specifier 是否与同目录其他测试的 `require`/`import` 路径解析到同一模块
358+
322359
### 类型检查
323360

324361
项目使用 TypeScript strict 模式,**tsc 必须零错误**。每次修改后运行:

contributors.svg

Lines changed: 41 additions & 31 deletions
Loading

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-best",
3-
"version": "2.4.0",
3+
"version": "2.4.3",
44
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
55
"type": "module",
66
"author": "claude-code-best <claude-code-best@proton.me>",

packages/builtin-tools/src/tools/BashTool/BashTool.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import { extractClaudeCodeHints } from 'src/utils/claudeCodeHints.js';
2929
import { detectCodeIndexingFromCommand } from 'src/utils/codeIndexing.js';
3030
import { isEnvTruthy } from 'src/utils/envUtils.js';
3131
import { isENOENT, ShellError } from 'src/utils/errors.js';
32-
import { decodeBuffer } from 'src/utils/encoding.js';
3332
import { detectFileEncoding, detectLineEndings, getFileModificationTime, writeTextContent } from 'src/utils/file.js';
3433
import { fileHistoryEnabled, fileHistoryTrackEdit } from 'src/utils/fileHistory.js';
3534
import { truncate } from 'src/utils/format.js';
@@ -512,8 +511,7 @@ async function applySedEdit(
512511
const encoding = detectFileEncoding(absoluteFilePath);
513512
let originalContent: string;
514513
try {
515-
const rawBuffer = await fs.readFileBytes(absoluteFilePath);
516-
originalContent = decodeBuffer(rawBuffer, encoding);
514+
originalContent = await fs.readFile(absoluteFilePath, { encoding });
517515
} catch (e) {
518516
if (isENOENT(e)) {
519517
return {

packages/builtin-tools/src/tools/ExecuteTool/ExecuteTool.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ import {
1010
} from 'src/Tool.js'
1111
import { lazySchema } from 'src/utils/lazySchema.js'
1212
import { createUserMessage } from 'src/utils/messages.js'
13+
import {
14+
extractDiscoveredToolNames,
15+
isSearchExtraToolsEnabledOptimistic,
16+
isSearchExtraToolsToolAvailable,
17+
} from 'src/utils/searchExtraTools.js'
1318
import { DESCRIPTION, getPrompt } from './prompt.js'
1419
import { EXECUTE_TOOL_NAME } from './constants.js'
20+
import { isDeferredTool } from '../SearchExtraToolsTool/prompt.js'
1521

1622
export const inputSchema = lazySchema(() =>
1723
z.object({
@@ -74,6 +80,32 @@ export const ExecuteTool = buildTool({
7480
}
7581
}
7682

83+
// Guard: block execution of undiscovered deferred tools.
84+
// When tool search is active, deferred tools must be discovered via
85+
// SearchExtraTools first so the model has seen their schemas and knows
86+
// the correct parameters. Executing an undiscovered tool almost always
87+
// fails with parameter validation errors.
88+
if (
89+
isSearchExtraToolsEnabledOptimistic() &&
90+
isSearchExtraToolsToolAvailable(tools) &&
91+
isDeferredTool(targetTool)
92+
) {
93+
const discovered = extractDiscoveredToolNames(context.messages)
94+
if (!discovered.has(input.tool_name)) {
95+
return {
96+
data: {
97+
result: null,
98+
tool_name: input.tool_name,
99+
},
100+
newMessages: [
101+
createUserMessage({
102+
content: `Tool "${input.tool_name}" has not been discovered yet. You must first use SearchExtraTools to discover this tool before executing it.\n\nUsage: SearchExtraTools("select:${input.tool_name}")`,
103+
}),
104+
],
105+
}
106+
}
107+
}
108+
77109
// Check if the target tool is currently enabled
78110
if (!targetTool.isEnabled()) {
79111
return {

packages/builtin-tools/src/tools/ExecuteTool/__tests__/ExecuteTool.runner.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ mock.module('src/utils/searchExtraTools.js', () => ({
3333
isSearchExtraToolsEnabledOptimistic: () => true,
3434
getAutoSearchExtraToolsCharThreshold: () => 100,
3535
getSearchExtraToolsMode: () => 'tst' as const,
36-
isSearchExtraToolsToolAvailable: async () => true,
36+
isSearchExtraToolsToolAvailable: () => true,
3737
isSearchExtraToolsEnabled: async () => true,
3838
isToolReferenceBlock: () => false,
39-
extractDiscoveredToolNames: () => new Set(),
39+
extractDiscoveredToolNames: () => new Set(['TestTool', 'SecretTool']),
4040
isDeferredToolsDeltaEnabled: () => false,
4141
getDeferredToolsDelta: () => null,
4242
}))
@@ -154,6 +154,26 @@ describe('ExecuteTool', () => {
154154
expect(result.newMessages).toBeDefined()
155155
})
156156

157+
test('returns error when deferred tool has not been discovered via SearchExtraTools', async () => {
158+
const mockTarget = makeMockTool('UndiscoveredTool', 'result')
159+
const ctx = makeContext([mockTarget])
160+
161+
const result = await ExecuteTool.call(
162+
{ tool_name: 'UndiscoveredTool', params: {} },
163+
ctx,
164+
async () => ({ behavior: 'allow' }),
165+
{ type: 'assistant', content: [], uuid: 'msg1' } as never,
166+
undefined,
167+
)
168+
169+
expect(result.data).toEqual({
170+
result: null,
171+
tool_name: 'UndiscoveredTool',
172+
})
173+
expect(result.newMessages).toBeDefined()
174+
expect(result.newMessages![0].content).toContain('has not been discovered')
175+
})
176+
157177
test('has correct name', () => {
158178
expect(ExecuteTool.name).toBe(EXECUTE_TOOL_NAME)
159179
})

0 commit comments

Comments
 (0)