claude-coder 1.0.6 → 1.0.9
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/docs/ARCHITECTURE.md +6 -4
- package/docs/PHASE_INJECTION_RESEARCH.md +325 -0
- package/docs/README.en.md +1 -1
- package/package.json +1 -1
- package/src/config.js +6 -1
- package/src/indicator.js +18 -7
- package/src/prompts.js +23 -11
- package/src/runner.js +9 -2
- package/src/session.js +5 -1
- package/src/tasks.js +6 -11
- package/src/validator.js +6 -16
- package/templates/CLAUDE.md +3 -2
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -183,11 +183,11 @@ flowchart TB
|
|
|
183
183
|
|
|
184
184
|
| Session 类型 | systemPrompt | user prompt | 触发条件 |
|
|
185
185
|
|---|---|---|---|
|
|
186
|
-
| **编码** | CLAUDE.md | `buildCodingPrompt()` +
|
|
186
|
+
| **编码** | CLAUDE.md | `buildCodingPrompt()` + 10 个条件 hint | 主循环每次迭代 |
|
|
187
187
|
| **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 + profile 质量要求 | 首次运行 |
|
|
188
188
|
| **追加** | CLAUDE.md | `buildAddPrompt()` + 任务分解指导 | `claude-coder add` |
|
|
189
189
|
|
|
190
|
-
### 编码 Session 的
|
|
190
|
+
### 编码 Session 的 10 个条件 Hint
|
|
191
191
|
|
|
192
192
|
| # | Hint | 触发条件 | 影响 |
|
|
193
193
|
|---|---|---|---|
|
|
@@ -197,8 +197,10 @@ flowchart TB
|
|
|
197
197
|
| 4 | `docsHint` | profile.existing_docs 非空或 profile 有缺陷 | Step 4:读文档后再编码;profile 缺陷时提示 Agent 在 Step 6 补全 services/docs |
|
|
198
198
|
| 5 | `envHint` | 连续成功且 session>1 | Step 2:跳过 init |
|
|
199
199
|
| 6 | `retryContext` | 上次校验失败 | 全局:避免同样错误 |
|
|
200
|
-
| 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 |
|
|
200
|
+
| 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 + .claude-coder/ 路径提示 |
|
|
201
201
|
| 8 | `memoryHint` | session_result.json 存在且有历史记录 | Step 1:跳过读取 session_result.json,harness 已注入上次会话摘要 |
|
|
202
|
+
| 9 | `serviceHint` | 始终注入 | Step 6:单次模式停止服务,连续模式保持服务运行 |
|
|
203
|
+
| 10 | `toolGuidance` | 始终注入 | 全局:工具使用规范(Grep/Glob/Read/LS/MultiEdit/Task 替代 bash 命令),非 Claude 模型必需 |
|
|
202
204
|
|
|
203
205
|
---
|
|
204
206
|
|
|
@@ -266,7 +268,7 @@ sequenceDiagram
|
|
|
266
268
|
| 维度 | 评分 | 说明 |
|
|
267
269
|
|------|------|------|
|
|
268
270
|
| **CLAUDE.md 系统提示** | 8/10 | U 型注意力设计;铁律清晰;状态机和 6 步流程是核心竞争力 |
|
|
269
|
-
| **动态 prompt** |
|
|
271
|
+
| **动态 prompt** | 9/10 | 10 个条件 hint 精准注入,含 task/memory 上下文注入 + 服务管理 + 工具使用指导,减少 Agent 冗余操作 |
|
|
270
272
|
| **SCAN_PROTOCOL.md** | 8.5/10 | 新旧项目分支完整,profile 格式全面 |
|
|
271
273
|
| **tests.json 设计** | 7.5/10 | 精简字段,核心目的(防反复测试)明确 |
|
|
272
274
|
| **注入时机** | 9/10 | 静态规则 vs 动态上下文分离干净 |
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# 分阶段提示语注入 — 技术调研与方向探讨
|
|
2
|
+
|
|
3
|
+
> 状态:调研阶段,仅探讨,未实现
|
|
4
|
+
> 日期:2026-03-04
|
|
5
|
+
> 背景:当前所有 10 个 Hint 在 session 开始前一次性注入 user prompt。本文探讨利用 Hook 的 `additionalContext` 能力,将提示语拆分到不同阶段按需注入。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 当前架构
|
|
10
|
+
|
|
11
|
+
### 提示语注入时机
|
|
12
|
+
|
|
13
|
+
```mermaid
|
|
14
|
+
sequenceDiagram
|
|
15
|
+
participant H as Harness
|
|
16
|
+
participant SDK as Claude Agent SDK
|
|
17
|
+
participant Agent as Agent (Model)
|
|
18
|
+
|
|
19
|
+
H->>H: buildSystemPrompt()<br/>CLAUDE.md (~260行)
|
|
20
|
+
H->>H: buildCodingPrompt()<br/>10 个 Hint 全部拼接
|
|
21
|
+
H->>SDK: query({ prompt, options })
|
|
22
|
+
Note over SDK,Agent: 所有提示语一次性加载
|
|
23
|
+
|
|
24
|
+
loop Agent 自主运行
|
|
25
|
+
Agent->>SDK: 工具调用 (Read/Edit/Bash...)
|
|
26
|
+
SDK->>H: PreToolUse hook 回调
|
|
27
|
+
H->>H: inferPhaseStep() 更新 spinner
|
|
28
|
+
H->>H: 检查编辑循环
|
|
29
|
+
H-->>SDK: return {} (放行)
|
|
30
|
+
SDK->>Agent: 工具结果
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 问题
|
|
35
|
+
|
|
36
|
+
| 问题 | 说明 |
|
|
37
|
+
|------|------|
|
|
38
|
+
| **Token 浪费** | 10 个 Hint 全部注入 user prompt,但大部分 Hint 仅在特定阶段有用(如 testHint 仅 Step 5 需要) |
|
|
39
|
+
| **注意力稀释** | 一次性注入大量指令,模型在真正需要某条指令时可能已"忘记"(context rot) |
|
|
40
|
+
| **时机错位** | 工具使用指导(Hint 10)在 Agent 还没开始读文件时就注入了,但 Agent 在 Step 4 编码阶段才真正需要这些规则 |
|
|
41
|
+
| **无法纠正** | 当前 Hook 仅用于监控和死循环拦截,无法在 Agent 做出低效工具选择时即时纠正 |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 2. Hook 能力盘点
|
|
46
|
+
|
|
47
|
+
### SDK 内联 Hook(当前使用方式)
|
|
48
|
+
|
|
49
|
+
通过 `query()` 的 `options.hooks` 定义,进程内回调:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
sdk.query({
|
|
53
|
+
prompt,
|
|
54
|
+
options: {
|
|
55
|
+
hooks: {
|
|
56
|
+
PreToolUse: [{ matcher: '*', hooks: [async (input) => { ... }] }],
|
|
57
|
+
PostToolUse: [{ matcher: '*', hooks: [async (input) => { ... }] }],
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Hook 事件 | SDK 内联支持 | 能力 |
|
|
64
|
+
|-----------|-------------|------|
|
|
65
|
+
| `PreToolUse` | 是 | `permissionDecision` (allow/deny/ask), `message`, **`additionalContext`** (v2.1.9+), `updatedInput` |
|
|
66
|
+
| `PostToolUse` | 是 | `decision` (block), `reason`, **`additionalContext`** |
|
|
67
|
+
| `UserPromptSubmit` | 是 | `decision` (block), `reason`, `additionalContext` |
|
|
68
|
+
| `Stop` | 是 | `decision` (block), `reason` |
|
|
69
|
+
| `SessionStart` | **否** (仅 CLI 声明式) | 不适用 |
|
|
70
|
+
| `SessionEnd` | **否** (仅 CLI 声明式) | 不适用 |
|
|
71
|
+
|
|
72
|
+
### `additionalContext` 关键特性
|
|
73
|
+
|
|
74
|
+
- **作用**: 将文本注入 Agent 的 context window,Agent 在后续推理中可以看到并遵循
|
|
75
|
+
- **注入位置**: 作为工具调用的附加上下文出现,紧邻工具结果
|
|
76
|
+
- **注意力**: 因为紧跟当前工具调用,处于模型注意力的高峰区域(recency zone)
|
|
77
|
+
- **限制**: 2026年1月新增,可能存在边缘 bug
|
|
78
|
+
|
|
79
|
+
### `decision: 'block'` + `message`(当前已在用)
|
|
80
|
+
|
|
81
|
+
- **作用**: 阻止工具调用,`message` 作为错误反馈传回模型
|
|
82
|
+
- **注意力**: 模型会将其视为"操作失败"信息,遵循率高
|
|
83
|
+
- **适用场景**: 拦截不当操作并引导替代方案
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 3. 提议架构:分阶段注入
|
|
88
|
+
|
|
89
|
+
### 核心思想
|
|
90
|
+
|
|
91
|
+
**按 Agent 的工作阶段,在 Hook 中按需注入对应阶段的提示语。** 初始 prompt 仅包含最核心的内容(身份、任务、约束),其余指导在 Agent 进入相应阶段时即时注入。
|
|
92
|
+
|
|
93
|
+
```mermaid
|
|
94
|
+
sequenceDiagram
|
|
95
|
+
participant H as Harness
|
|
96
|
+
participant SDK as Claude Agent SDK
|
|
97
|
+
participant Agent as Agent
|
|
98
|
+
|
|
99
|
+
H->>SDK: query({ prompt: 精简版 })
|
|
100
|
+
Note over H: 仅注入: 身份 + 任务上下文 + 约束
|
|
101
|
+
|
|
102
|
+
rect rgb(200, 230, 255)
|
|
103
|
+
Note over Agent: Phase 1: 恢复上下文
|
|
104
|
+
Agent->>SDK: Read(.claude-coder/profile.json)
|
|
105
|
+
SDK->>H: PreToolUse
|
|
106
|
+
H-->>SDK: additionalContext: 路径提示 + 文档指引
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
rect rgb(200, 255, 200)
|
|
110
|
+
Note over Agent: Phase 2: 编码阶段
|
|
111
|
+
Agent->>SDK: Edit(src/app.ts)
|
|
112
|
+
SDK->>H: PreToolUse
|
|
113
|
+
H-->>SDK: additionalContext: 工具使用规范 + MultiEdit提示
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
rect rgb(255, 230, 200)
|
|
117
|
+
Note over Agent: Phase 3: 测试阶段
|
|
118
|
+
Agent->>SDK: Bash(curl ...)
|
|
119
|
+
SDK->>H: PreToolUse
|
|
120
|
+
H-->>SDK: additionalContext: 测试效率规则
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
rect rgb(230, 200, 255)
|
|
124
|
+
Note over Agent: Phase 4: 收尾阶段
|
|
125
|
+
Agent->>SDK: Bash(git commit)
|
|
126
|
+
SDK->>H: PreToolUse
|
|
127
|
+
H-->>SDK: additionalContext: 服务管理 + session_result 格式
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Hint 拆分方案
|
|
132
|
+
|
|
133
|
+
| # | Hint | 当前位置 | 建议注入时机 | 注入方式 |
|
|
134
|
+
|---|------|----------|-------------|----------|
|
|
135
|
+
| 1 | `reqSyncHint` | user prompt | **保留在 user prompt** | 需求变更需要在 Step 1 就知道 |
|
|
136
|
+
| 7 | `taskHint` | user prompt | **保留在 user prompt** | 任务上下文是 Agent 开始工作的前提 |
|
|
137
|
+
| 8 | `memoryHint` | user prompt | **保留在 user prompt** | 上次会话记忆需要一开始就有 |
|
|
138
|
+
| 5 | `envHint` | user prompt | **保留在 user prompt** | Step 2 环境检查需要一开始就知道 |
|
|
139
|
+
| 2 | `mcpHint` | user prompt | PreToolUse (Bash: curl/test) | 测试时才需要知道 Playwright 可用 |
|
|
140
|
+
| 3 | `testHint` | user prompt | PreToolUse (Bash: curl/test) | 测试时才需要避免重复验证 |
|
|
141
|
+
| 4 | `docsHint` | user prompt | PreToolUse (Read: 首次读文件) | 读文件时提醒先读文档 |
|
|
142
|
+
| 6 | `retryContext` | user prompt | **保留在 user prompt** | 重试上下文需要一开始就有 |
|
|
143
|
+
| 9 | `serviceHint` | user prompt | PreToolUse (Bash: git) | 收尾时才需要知道是否停服务 |
|
|
144
|
+
| 10 | `toolGuidance` | user prompt | PreToolUse (首次工具调用) | 开始使用工具时注入 |
|
|
145
|
+
|
|
146
|
+
**结论**: 10 个 Hint 中,5 个适合保留在初始 prompt(1, 5, 6, 7, 8),5 个适合延迟注入到 Hook(2, 3, 4, 9, 10)。
|
|
147
|
+
|
|
148
|
+
### 实现草案
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
// session.js - PreToolUse hook 增强版(概念代码,仅供讨论)
|
|
152
|
+
const injected = new Set(); // 跟踪已注入的 Hint,每个仅注入一次
|
|
153
|
+
|
|
154
|
+
hooks: {
|
|
155
|
+
PreToolUse: [{
|
|
156
|
+
matcher: '*',
|
|
157
|
+
hooks: [async (input) => {
|
|
158
|
+
const name = input.tool_name;
|
|
159
|
+
const toolInput = input.tool_input || {};
|
|
160
|
+
let additionalContext = '';
|
|
161
|
+
|
|
162
|
+
// --- Phase: 读取文件 → 注入文档指引 ---
|
|
163
|
+
if (['Read', 'Glob', 'Grep', 'LS'].includes(name) && !injected.has('docs')) {
|
|
164
|
+
additionalContext += docsHint; // Hint 4
|
|
165
|
+
injected.add('docs');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- Phase: 首次工具调用 → 注入工具使用规范 ---
|
|
169
|
+
if (!injected.has('toolGuide')) {
|
|
170
|
+
additionalContext += '\n' + toolGuidance; // Hint 10
|
|
171
|
+
injected.add('toolGuide');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// --- Phase: 测试阶段 → 注入测试规则 ---
|
|
175
|
+
if (name === 'Bash') {
|
|
176
|
+
const cmd = toolInput.command || '';
|
|
177
|
+
if ((cmd.includes('curl') || cmd.includes('test') || cmd.includes('pytest'))
|
|
178
|
+
&& !injected.has('test')) {
|
|
179
|
+
additionalContext += '\n' + testHint; // Hint 3
|
|
180
|
+
additionalContext += '\n' + mcpHint; // Hint 2
|
|
181
|
+
injected.add('test');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- Phase: Git 操作 → 注入收尾提示 ---
|
|
185
|
+
if (cmd.includes('git ') && !injected.has('service')) {
|
|
186
|
+
additionalContext += '\n' + serviceHint; // Hint 9
|
|
187
|
+
injected.add('service');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- Bash 命令纠正(进阶) ---
|
|
192
|
+
if (name === 'Bash') {
|
|
193
|
+
const cmd = toolInput.command || '';
|
|
194
|
+
if (/\bgrep\b/.test(cmd) && !cmd.includes('rg ')) {
|
|
195
|
+
return {
|
|
196
|
+
permissionDecision: 'deny',
|
|
197
|
+
permissionDecisionReason: '请使用 Grep 工具替代 bash grep,效率更高且结果格式化更好。',
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (/\bfind\b/.test(cmd)) {
|
|
201
|
+
return {
|
|
202
|
+
permissionDecision: 'deny',
|
|
203
|
+
permissionDecisionReason: '请使用 Glob 工具替代 bash find。',
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (/\bcat\b/.test(cmd) && !cmd.includes('<<')) {
|
|
207
|
+
return {
|
|
208
|
+
permissionDecision: 'deny',
|
|
209
|
+
permissionDecisionReason: '请使用 Read 工具替代 bash cat。',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- 编辑循环检测(已有功能) ---
|
|
215
|
+
// ... existing loop detection code ...
|
|
216
|
+
|
|
217
|
+
// 注入上下文
|
|
218
|
+
if (additionalContext.trim()) {
|
|
219
|
+
return { additionalContext: additionalContext.trim() };
|
|
220
|
+
}
|
|
221
|
+
return {};
|
|
222
|
+
}]
|
|
223
|
+
}]
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 4. Bash 命令拦截:工具纠正的最短路径
|
|
230
|
+
|
|
231
|
+
在完整的分阶段注入之前,有一个**低成本高收益**的中间步骤:在 PreToolUse hook 中拦截 Agent 使用 Bash 执行低效命令(grep/find/cat/ls/head/tail),引导其使用专用工具。
|
|
232
|
+
|
|
233
|
+
### 行为矩阵
|
|
234
|
+
|
|
235
|
+
| Agent 执行 | Hook 行为 | 反馈给 Agent |
|
|
236
|
+
|------------|----------|-------------|
|
|
237
|
+
| `Bash: grep -r "pattern" .` | **deny** | "请使用 Grep 工具替代 bash grep" |
|
|
238
|
+
| `Bash: find . -name "*.ts"` | **deny** | "请使用 Glob 工具替代 bash find" |
|
|
239
|
+
| `Bash: cat src/app.ts` | **deny** | "请使用 Read 工具替代 bash cat" |
|
|
240
|
+
| `Bash: ls -la` | **deny** | "请使用 LS 工具替代 bash ls" |
|
|
241
|
+
| `Bash: head -20 file.txt` | **deny** | "请使用 Read 工具(支持 offset/limit)替代 bash head" |
|
|
242
|
+
| `Bash: npm test` | allow | -- |
|
|
243
|
+
| `Bash: git commit` | allow + additionalContext | 注入收尾提示 |
|
|
244
|
+
|
|
245
|
+
### 优势
|
|
246
|
+
|
|
247
|
+
- **确定性**: Hook 拦截是确定性的,不依赖模型是否"记住"了 prompt 中的工具规则
|
|
248
|
+
- **即时纠正**: 在 Agent 犯错的那一刻就纠正,而不是等它浪费完 context
|
|
249
|
+
- **渐进式**: 可以先实现拦截(deny + message),后续再加 additionalContext
|
|
250
|
+
- **非 Claude 模型必需**: qwen/deepseek 等模型对 prompt 的遵循率不如 Claude,但 deny 是硬性拦截,模型无法绕过
|
|
251
|
+
|
|
252
|
+
### 风险
|
|
253
|
+
|
|
254
|
+
| 风险 | 缓解方案 |
|
|
255
|
+
|------|----------|
|
|
256
|
+
| 误拦截合法 Bash 命令(如 `cat <<EOF` heredoc) | 正则匹配需要排除 heredoc、管道等场景 |
|
|
257
|
+
| 某些 grep 用法没有 Grep 工具替代(如 `grep -c`) | 只拦截简单模式,复杂 grep 放行 |
|
|
258
|
+
| 过度拦截导致 Agent 陷入循环 | 每种拦截最多触发 2 次,第 3 次放行 |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## 5. 与现有方案的对比
|
|
263
|
+
|
|
264
|
+
| 维度 | 当前方案 | 分阶段注入 | Bash 拦截纠正 |
|
|
265
|
+
|------|---------|-----------|-------------|
|
|
266
|
+
| 实现复杂度 | 低 | 高 | 中 |
|
|
267
|
+
| Token 效率 | 低(全量注入) | 高(按需注入) | 不变(不影响初始 prompt) |
|
|
268
|
+
| 注意力效果 | 中(U型优化) | 高(时机精准) | 高(即时纠正,deny 不可忽略) |
|
|
269
|
+
| 非 Claude 模型支持 | 中(靠 prompt) | 高(时机 + prompt) | **最高(硬性拦截)** |
|
|
270
|
+
| 风险 | 低 | 中(additionalContext 较新) | 低(deny 已验证) |
|
|
271
|
+
| 依赖 SDK 版本 | 无 | v2.1.9+(additionalContext) | 无(deny + message 已有) |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 6. 建议路线图
|
|
276
|
+
|
|
277
|
+
### P0 — 立即可做(不依赖新 SDK 特性)
|
|
278
|
+
|
|
279
|
+
**Bash 命令拦截纠正**
|
|
280
|
+
|
|
281
|
+
在现有 PreToolUse hook 中增加 bash 命令检测,对 `grep`/`find`/`cat`/`ls`/`head`/`tail` 返回 `deny + message` 引导使用专用工具。这是最短路径、最高确定性的优化。
|
|
282
|
+
|
|
283
|
+
### P1 — 短期(需要验证 additionalContext)
|
|
284
|
+
|
|
285
|
+
**工具使用指导延迟注入**
|
|
286
|
+
|
|
287
|
+
将 Hint 10(toolGuidance)从初始 prompt 移到 PreToolUse hook 的 `additionalContext`,在 Agent 首次使用工具时注入。验证 `additionalContext` 在非 Claude 模型上的效果。
|
|
288
|
+
|
|
289
|
+
### P2 — 中期
|
|
290
|
+
|
|
291
|
+
**测试/收尾阶段指导延迟注入**
|
|
292
|
+
|
|
293
|
+
将 Hint 2/3/9 移到 PreToolUse hook,按阶段(test/git)触发注入。
|
|
294
|
+
|
|
295
|
+
### P3 — 远期
|
|
296
|
+
|
|
297
|
+
**完整分阶段注入**
|
|
298
|
+
|
|
299
|
+
所有可延迟的 Hint 通过 Hook 按需注入。初始 prompt 仅保留身份、任务、约束。配合 `additionalContext` 的 PostToolUse 版本,实现"编码后注入代码审查提示"等高级场景。
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 7. 学术/行业参考
|
|
304
|
+
|
|
305
|
+
| 来源 | 核心概念 | 与本方案的关联 |
|
|
306
|
+
|------|----------|---------------|
|
|
307
|
+
| Anthropic Context Engineering (2025) | Context 是有限资源,需精心管理 | 按需注入减少 context 浪费 |
|
|
308
|
+
| Claude Code System Prompt (gist) | 每个工具都有 "when to use / when NOT to use" 指导 | Hint 10 和 Bash 拦截复现这一设计 |
|
|
309
|
+
| SWE-Agent (2024) ACI | Agent-Computer Interface 设计应优化工具发现和使用 | Hook 即时纠正是 ACI 的运行时优化 |
|
|
310
|
+
| Anthropic "Writing effective tools for agents" (2025) | 工具设计影响 Agent 行为,工具在 context 中很显眼 | 扩展 allowedTools 让工具自然出现在模型视野 |
|
|
311
|
+
| ContextBench (2025) | 复杂脚手架边际收益递减 | 不过度设计分阶段注入,先做确定性拦截 |
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 8. 结论
|
|
316
|
+
|
|
317
|
+
当前 harness 的提示语架构已经相当成熟(U型注意力 + 10个条件Hint + recency zone 注入)。下一步优化的核心方向是**从"一次性全量注入"向"按需分阶段注入"演进**,但需要渐进式推进:
|
|
318
|
+
|
|
319
|
+
1. **先做 Bash 命令拦截**(P0)— 零风险,最高确定性,不依赖新 SDK 特性
|
|
320
|
+
2. **验证 `additionalContext`**(P1)— 确认非 Claude 模型是否能看到并遵循
|
|
321
|
+
3. **逐步迁移 Hint**(P2-P3)— 每次迁移一个 Hint,A/B 测试效果
|
|
322
|
+
|
|
323
|
+
**核心原则:确定性拦截(Hook deny)> 即时注入(additionalContext)> 初始 prompt 指导(Hint)> 系统 prompt 规则(CLAUDE.md)**
|
|
324
|
+
|
|
325
|
+
这个优先级排序体现了一个关键洞察:**越靠近行为发生的时刻,指导的遵循率越高**。
|
package/docs/README.en.md
CHANGED
|
@@ -50,9 +50,9 @@ Each session, the agent autonomously follows 6 steps: restore context → env ch
|
|
|
50
50
|
|---------|-------------|
|
|
51
51
|
| `claude-coder setup` | Interactive model configuration |
|
|
52
52
|
| `claude-coder run [requirement]` | Auto-coding loop |
|
|
53
|
+
| `claude-coder run --max 1` | Single session (replaces old view mode) |
|
|
53
54
|
| `claude-coder run --dry-run` | Preview mode |
|
|
54
55
|
| `claude-coder init` | Initialize project environment |
|
|
55
|
-
| `claude-coder view [requirement]` | Observation mode (single session) |
|
|
56
56
|
| `claude-coder add "instruction"` | Append tasks |
|
|
57
57
|
| `claude-coder validate` | Manually validate last session |
|
|
58
58
|
| `claude-coder status` | View progress and costs |
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -148,7 +148,12 @@ function buildEnvVars(config) {
|
|
|
148
148
|
// --------------- Allowed tools ---------------
|
|
149
149
|
|
|
150
150
|
function getAllowedTools(config) {
|
|
151
|
-
const tools = [
|
|
151
|
+
const tools = [
|
|
152
|
+
'Read', 'Edit', 'MultiEdit', 'Write',
|
|
153
|
+
'Bash', 'Glob', 'Grep', 'LS',
|
|
154
|
+
'Task',
|
|
155
|
+
'WebSearch', 'WebFetch',
|
|
156
|
+
];
|
|
152
157
|
if (config.mcpPlaywright) tools.push('mcp__playwright__*');
|
|
153
158
|
return tools;
|
|
154
159
|
}
|
package/src/indicator.js
CHANGED
|
@@ -58,24 +58,29 @@ class Indicator {
|
|
|
58
58
|
try { fs.writeFileSync(paths().stepFile, this.step, 'utf8'); } catch { /* ignore */ }
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
getStatusLine() {
|
|
62
62
|
const elapsed = Math.floor((Date.now() - this.startTime) / 1000);
|
|
63
63
|
const mm = String(Math.floor(elapsed / 60)).padStart(2, '0');
|
|
64
64
|
const ss = String(elapsed % 60).padStart(2, '0');
|
|
65
65
|
const spinner = SPINNERS[this.spinnerIndex % SPINNERS.length];
|
|
66
|
-
this.spinnerIndex++;
|
|
67
66
|
|
|
68
67
|
const phaseLabel = this.phase === 'thinking'
|
|
69
68
|
? `${COLOR.yellow}思考中${COLOR.reset}`
|
|
70
69
|
: `${COLOR.green}编码中${COLOR.reset}`;
|
|
71
70
|
|
|
72
|
-
let line =
|
|
71
|
+
let line = `${spinner} [Session ${this.sessionNum}] ${phaseLabel} ${mm}:${ss}`;
|
|
73
72
|
if (this.step) line += ` | ${this.step}`;
|
|
73
|
+
return line;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
_render() {
|
|
77
|
+
this.spinnerIndex++;
|
|
78
|
+
const line = this.getStatusLine();
|
|
74
79
|
|
|
75
80
|
const maxWidth = process.stderr.columns || 80;
|
|
76
|
-
|
|
81
|
+
const truncated = line.length > maxWidth + 20 ? line.slice(0, maxWidth + 20) : line;
|
|
77
82
|
|
|
78
|
-
process.stderr.write(`\r\x1b[K${
|
|
83
|
+
process.stderr.write(`\r\x1b[K${truncated}`);
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -83,7 +88,7 @@ class Indicator {
|
|
|
83
88
|
function inferPhaseStep(indicator, toolName, toolInput) {
|
|
84
89
|
const name = (toolName || '').toLowerCase();
|
|
85
90
|
|
|
86
|
-
if (name === 'write' || name === 'edit' || name === 'str_replace_editor' || name === 'strreplace') {
|
|
91
|
+
if (name === 'write' || name === 'edit' || name === 'multiedit' || name === 'str_replace_editor' || name === 'strreplace') {
|
|
87
92
|
indicator.updatePhase('coding');
|
|
88
93
|
} else if (name === 'bash' || name === 'shell') {
|
|
89
94
|
const cmd = typeof toolInput === 'object' ? (toolInput.command || '') : String(toolInput || '');
|
|
@@ -97,9 +102,15 @@ function inferPhaseStep(indicator, toolName, toolInput) {
|
|
|
97
102
|
} else {
|
|
98
103
|
indicator.updatePhase('coding');
|
|
99
104
|
}
|
|
100
|
-
} else if (name === 'read' || name === 'glob' || name === 'grep') {
|
|
105
|
+
} else if (name === 'read' || name === 'glob' || name === 'grep' || name === 'ls') {
|
|
101
106
|
indicator.updatePhase('thinking');
|
|
102
107
|
indicator.updateStep('读取文件');
|
|
108
|
+
} else if (name === 'task') {
|
|
109
|
+
indicator.updatePhase('thinking');
|
|
110
|
+
indicator.updateStep('子 Agent 搜索');
|
|
111
|
+
} else if (name === 'websearch' || name === 'webfetch') {
|
|
112
|
+
indicator.updatePhase('thinking');
|
|
113
|
+
indicator.updateStep('查阅文档');
|
|
103
114
|
}
|
|
104
115
|
|
|
105
116
|
const summary = typeof toolInput === 'object'
|
package/src/prompts.js
CHANGED
|
@@ -4,16 +4,6 @@ const fs = require('fs');
|
|
|
4
4
|
const { paths, loadConfig, getRequirementsHash } = require('./config');
|
|
5
5
|
const { loadTasks, findNextTask, getStats } = require('./tasks');
|
|
6
6
|
|
|
7
|
-
function safeJsonParse(text) {
|
|
8
|
-
try {
|
|
9
|
-
return JSON.parse(text);
|
|
10
|
-
} catch {
|
|
11
|
-
return JSON.parse(
|
|
12
|
-
text.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'")
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
7
|
/**
|
|
18
8
|
* Build system prompt by combining template files.
|
|
19
9
|
* @param {boolean} includeScanProtocol - Whether to append SCAN_PROTOCOL.md
|
|
@@ -108,6 +98,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
108
98
|
taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
|
|
109
99
|
`category=${next.category}, steps=${next.steps.length}步。` +
|
|
110
100
|
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
|
|
101
|
+
`运行时目录: .claude-coder/(隐藏目录,ls -a 可见,所有 tasks.json/profile 等文件均在此目录下)。` +
|
|
111
102
|
`第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
|
|
112
103
|
}
|
|
113
104
|
}
|
|
@@ -117,7 +108,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
117
108
|
let memoryHint = '';
|
|
118
109
|
if (fs.existsSync(p.sessionResult)) {
|
|
119
110
|
try {
|
|
120
|
-
const sr =
|
|
111
|
+
const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
121
112
|
const last = sr.current || (sr.history?.length ? sr.history[sr.history.length - 1] : null);
|
|
122
113
|
if (last?.task_id) {
|
|
123
114
|
memoryHint = `上次会话: ${last.task_id} → ${last.status_after || last.session_result}` +
|
|
@@ -126,6 +117,25 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
126
117
|
} catch { /* ignore */ }
|
|
127
118
|
}
|
|
128
119
|
|
|
120
|
+
// Hint 9: Service management (continuous vs single-shot mode)
|
|
121
|
+
const maxSessions = opts.maxSessions || 50;
|
|
122
|
+
const serviceHint = maxSessions === 1
|
|
123
|
+
? '单次模式:收尾时停止所有后台服务。'
|
|
124
|
+
: '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
|
|
125
|
+
|
|
126
|
+
// Hint 10: Tool usage guidance (critical for non-Claude models)
|
|
127
|
+
const toolGuidance = [
|
|
128
|
+
'可用工具与使用规范(严格遵守):',
|
|
129
|
+
'- 搜索文件名: Glob(如 **/*.ts),禁止 bash find',
|
|
130
|
+
'- 搜索文件内容: Grep(正则,基于 ripgrep),禁止 bash grep',
|
|
131
|
+
'- 读文件: Read(支持批量多文件同时读取),禁止 bash cat/head/tail',
|
|
132
|
+
'- 列目录: LS,禁止 bash ls',
|
|
133
|
+
'- 编辑文件: 同一文件多处修改用 MultiEdit(一次原子调用),单处用 Edit',
|
|
134
|
+
'- 复杂搜索: Task(启动子 Agent 并行搜索,不消耗主 context),适合开放式探索',
|
|
135
|
+
'- 查文档/API: WebSearch + WebFetch',
|
|
136
|
+
'- 效率: 多个 Read/Glob/Grep 尽量合并为一次批量调用,减少工具轮次',
|
|
137
|
+
].join('\n');
|
|
138
|
+
|
|
129
139
|
return [
|
|
130
140
|
`Session ${sessionNum}。执行 6 步流程。`,
|
|
131
141
|
'效率要求:先规划后编码,完成全部编码后再统一测试,禁止编码-测试反复跳转。后端任务用 curl 验证,不启动浏览器。',
|
|
@@ -136,6 +146,8 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
136
146
|
envHint,
|
|
137
147
|
taskHint,
|
|
138
148
|
memoryHint,
|
|
149
|
+
serviceHint,
|
|
150
|
+
toolGuidance,
|
|
139
151
|
`完成后写入 session_result.json。${retryContext}`,
|
|
140
152
|
].filter(Boolean).join('\n');
|
|
141
153
|
}
|
package/src/runner.js
CHANGED
|
@@ -102,7 +102,7 @@ function appendProgress(entry) {
|
|
|
102
102
|
if (fs.existsSync(p.progressFile)) {
|
|
103
103
|
try {
|
|
104
104
|
const text = fs.readFileSync(p.progressFile, 'utf8');
|
|
105
|
-
|
|
105
|
+
progress = JSON.parse(text);
|
|
106
106
|
} catch { /* reset */ }
|
|
107
107
|
}
|
|
108
108
|
if (!Array.isArray(progress.sessions)) progress.sessions = [];
|
|
@@ -116,7 +116,7 @@ function updateSessionHistory(sessionData, sessionNum) {
|
|
|
116
116
|
if (fs.existsSync(p.sessionResult)) {
|
|
117
117
|
try {
|
|
118
118
|
const text = fs.readFileSync(p.sessionResult, 'utf8');
|
|
119
|
-
|
|
119
|
+
sr = JSON.parse(text);
|
|
120
120
|
} catch { /* reset */ }
|
|
121
121
|
if (!sr.history && sr.session_result) {
|
|
122
122
|
sr = { current: sr, history: [] };
|
|
@@ -247,6 +247,12 @@ async function run(requirement, opts = {}) {
|
|
|
247
247
|
log('info', `Session ${session} / ${maxSessions}`);
|
|
248
248
|
console.log('--------------------------------------------');
|
|
249
249
|
|
|
250
|
+
const taskData = loadTasks();
|
|
251
|
+
if (!taskData) {
|
|
252
|
+
log('error', 'tasks.json 无法读取,终止循环');
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
250
256
|
if (allTasksDone()) {
|
|
251
257
|
console.log('');
|
|
252
258
|
log('ok', '所有任务已完成!');
|
|
@@ -269,6 +275,7 @@ async function run(requirement, opts = {}) {
|
|
|
269
275
|
const sessionResult = await runCodingSession(session, {
|
|
270
276
|
projectRoot,
|
|
271
277
|
consecutiveFailures,
|
|
278
|
+
maxSessions,
|
|
272
279
|
lastValidateLog: consecutiveFailures > 0 ? '上次校验失败' : '',
|
|
273
280
|
});
|
|
274
281
|
|
package/src/session.js
CHANGED
|
@@ -66,7 +66,11 @@ function logMessage(message, logStream, indicator) {
|
|
|
66
66
|
if (message.type === 'assistant' && message.message?.content) {
|
|
67
67
|
for (const block of message.message.content) {
|
|
68
68
|
if (block.type === 'text' && block.text) {
|
|
69
|
-
if (indicator)
|
|
69
|
+
if (indicator) {
|
|
70
|
+
const statusLine = indicator.getStatusLine();
|
|
71
|
+
process.stderr.write('\r\x1b[K');
|
|
72
|
+
if (statusLine) process.stderr.write(statusLine + '\n');
|
|
73
|
+
}
|
|
70
74
|
process.stdout.write(block.text);
|
|
71
75
|
if (logStream) logStream.write(block.text);
|
|
72
76
|
}
|
package/src/tasks.js
CHANGED
|
@@ -13,20 +13,15 @@ const TRANSITIONS = {
|
|
|
13
13
|
done: [],
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
function safeJsonParse(text) {
|
|
17
|
-
try {
|
|
18
|
-
return JSON.parse(text);
|
|
19
|
-
} catch {
|
|
20
|
-
return JSON.parse(
|
|
21
|
-
text.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'")
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
function loadTasks() {
|
|
27
17
|
const p = paths();
|
|
28
18
|
if (!fs.existsSync(p.tasksFile)) return null;
|
|
29
|
-
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(p.tasksFile, 'utf8'));
|
|
21
|
+
} catch (err) {
|
|
22
|
+
log('error', `tasks.json 解析失败: ${err.message}`);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
function saveTasks(data) {
|
package/src/validator.js
CHANGED
|
@@ -4,16 +4,6 @@ const fs = require('fs');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const { paths, log, getProjectRoot } = require('./config');
|
|
6
6
|
|
|
7
|
-
function safeJsonParse(text) {
|
|
8
|
-
try {
|
|
9
|
-
return JSON.parse(text);
|
|
10
|
-
} catch {
|
|
11
|
-
return JSON.parse(
|
|
12
|
-
text.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'")
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
7
|
function validateSessionResult() {
|
|
18
8
|
const p = paths();
|
|
19
9
|
|
|
@@ -24,10 +14,10 @@ function validateSessionResult() {
|
|
|
24
14
|
|
|
25
15
|
let data;
|
|
26
16
|
try {
|
|
27
|
-
data =
|
|
28
|
-
} catch {
|
|
29
|
-
log('error',
|
|
30
|
-
return { valid: false, fatal: true, reason:
|
|
17
|
+
data = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
18
|
+
} catch (err) {
|
|
19
|
+
log('error', `session_result.json 解析失败: ${err.message}`);
|
|
20
|
+
return { valid: false, fatal: true, reason: `JSON 解析失败: ${err.message}` };
|
|
31
21
|
}
|
|
32
22
|
|
|
33
23
|
const sr = data.current || data;
|
|
@@ -96,9 +86,9 @@ function checkTestCoverage() {
|
|
|
96
86
|
if (!fs.existsSync(p.testsFile) || !fs.existsSync(p.sessionResult)) return;
|
|
97
87
|
|
|
98
88
|
try {
|
|
99
|
-
const sr =
|
|
89
|
+
const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
100
90
|
const current = sr.current || sr;
|
|
101
|
-
const tests =
|
|
91
|
+
const tests = JSON.parse(fs.readFileSync(p.testsFile, 'utf8'));
|
|
102
92
|
|
|
103
93
|
const taskId = current.task_id || '';
|
|
104
94
|
const testCases = tests.test_cases || [];
|
package/templates/CLAUDE.md
CHANGED
|
@@ -210,7 +210,8 @@ pending ──→ in_progress ──→ testing ──→ done
|
|
|
210
210
|
- 确认方案完整后,**一次性**完成所有编码
|
|
211
211
|
- **禁止边写边试**:完成全部编码后再进入第五步统一测试
|
|
212
212
|
4. **高效执行**:禁止碎片化操作(读一个文件、思考、再读一个),批量读取、批量修改、减少工具调用轮次
|
|
213
|
-
5.
|
|
213
|
+
5. **工具优先**:用 Grep/Glob 替代 bash grep/find,用 Read/LS 替代 bash cat/ls,同一文件多处修改用 MultiEdit
|
|
214
|
+
6. **跳过已完成的步骤**:文件已存在且内容正确的步骤直接跳过
|
|
214
215
|
|
|
215
216
|
### 第五步:测试验证
|
|
216
217
|
|
|
@@ -241,7 +242,7 @@ pending ──→ in_progress ──→ testing ──→ done
|
|
|
241
242
|
|
|
242
243
|
### 第六步:收尾(每次会话必须执行)
|
|
243
244
|
|
|
244
|
-
1.
|
|
245
|
+
1. **后台服务管理**:根据 prompt 提示决定——单次模式(`--max 1`)时停止所有后台服务(`lsof -ti :端口 | xargs kill`);连续模式时保持服务运行,下个 session 继续使用
|
|
245
246
|
2. **按需更新文档和 profile**:
|
|
246
247
|
- **README / 用户文档**:仅当对外行为变化(新增功能、API 变更、使用方式变化)时更新
|
|
247
248
|
- **架构 / API 文档**:如果本次新增了模块、改变了模块职责或新增了 API 端点,更新 `existing_docs` 中对应的架构或 API 文档。同时更新 `project_profile.json` 的 `existing_docs` 列表(若新增了文档文件)
|