claude-coder 1.0.3 → 1.0.5
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 +68 -14
- package/package.json +1 -1
- package/src/prompts.js +42 -0
- package/src/runner.js +8 -2
- package/src/session.js +15 -0
- package/src/tasks.js +9 -5
- package/src/validator.js +11 -5
- package/templates/CLAUDE.md +7 -4
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -188,21 +188,23 @@ flowchart TB
|
|
|
188
188
|
|
|
189
189
|
| Session 类型 | systemPrompt | user prompt | 触发条件 |
|
|
190
190
|
|---|---|---|---|
|
|
191
|
-
| **编码** | CLAUDE.md | `buildCodingPrompt()` +
|
|
191
|
+
| **编码** | CLAUDE.md | `buildCodingPrompt()` + 8 个条件 hint | 主循环每次迭代 |
|
|
192
192
|
| **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 | 首次运行 |
|
|
193
193
|
| **观测** | CLAUDE.md (± SCAN_PROTOCOL.md) | `buildViewPrompt()` | `claude-coder view` |
|
|
194
194
|
| **追加** | CLAUDE.md | `buildAddPrompt()` + 任务分解指导 | `claude-coder add` |
|
|
195
195
|
|
|
196
|
-
### 编码 Session 的
|
|
196
|
+
### 编码 Session 的 8 个条件 Hint
|
|
197
197
|
|
|
198
|
-
| Hint | 触发条件 | 影响 |
|
|
199
|
-
|
|
200
|
-
| `reqSyncHint` | 需求 hash 变化 | Step 1:追加新任务 |
|
|
201
|
-
| `mcpHint` | MCP_PLAYWRIGHT=true | Step 5:可用 Playwright |
|
|
202
|
-
| `testHint` | tests.json 有记录 | Step 5:避免重复验证 |
|
|
203
|
-
| `docsHint` | profile.existing_docs 非空 | Step 4:读文档后再编码,完成后更新文档 |
|
|
204
|
-
| `envHint` | 连续成功且 session>1 | Step 2:跳过 init |
|
|
205
|
-
| `retryContext` | 上次校验失败 | 全局:避免同样错误 |
|
|
198
|
+
| # | Hint | 触发条件 | 影响 |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| 1 | `reqSyncHint` | 需求 hash 变化 | Step 1:追加新任务 |
|
|
201
|
+
| 2 | `mcpHint` | MCP_PLAYWRIGHT=true | Step 5:可用 Playwright |
|
|
202
|
+
| 3 | `testHint` | tests.json 有记录 | Step 5:避免重复验证 |
|
|
203
|
+
| 4 | `docsHint` | profile.existing_docs 非空 | Step 4:读文档后再编码,完成后更新文档 |
|
|
204
|
+
| 5 | `envHint` | 连续成功且 session>1 | Step 2:跳过 init |
|
|
205
|
+
| 6 | `retryContext` | 上次校验失败 | 全局:避免同样错误 |
|
|
206
|
+
| 7 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 |
|
|
207
|
+
| 8 | `memoryHint` | session_result.json 存在且有历史记录 | Step 1:跳过读取 session_result.json,harness 已注入上次会话摘要 |
|
|
206
208
|
|
|
207
209
|
---
|
|
208
210
|
|
|
@@ -270,7 +272,7 @@ sequenceDiagram
|
|
|
270
272
|
| 维度 | 评分 | 说明 |
|
|
271
273
|
|------|------|------|
|
|
272
274
|
| **CLAUDE.md 系统提示** | 8/10 | U 型注意力设计;铁律清晰;状态机和 6 步流程是核心竞争力 |
|
|
273
|
-
| **动态 prompt** | 8/10 |
|
|
275
|
+
| **动态 prompt** | 8.5/10 | 8 个条件 hint 精准注入,含 task/memory 上下文注入,减少 Agent 冗余 Read 调用 |
|
|
274
276
|
| **SCAN_PROTOCOL.md** | 8.5/10 | 新旧项目分支完整,profile 格式全面 |
|
|
275
277
|
| **tests.json 设计** | 7.5/10 | 精简字段,核心目的(防反复测试)明确 |
|
|
276
278
|
| **注入时机** | 9/10 | 静态规则 vs 动态上下文分离干净 |
|
|
@@ -278,7 +280,59 @@ sequenceDiagram
|
|
|
278
280
|
|
|
279
281
|
---
|
|
280
282
|
|
|
281
|
-
## 9.
|
|
283
|
+
## 9. Context Injection 架构(v1.0.4+)
|
|
284
|
+
|
|
285
|
+
### 设计原则
|
|
286
|
+
|
|
287
|
+
**Harness 准备上下文,Agent 直接执行。** Agent 不应浪费工具调用读取 harness 已知的数据。
|
|
288
|
+
|
|
289
|
+
### 优化前后对比
|
|
290
|
+
|
|
291
|
+
```mermaid
|
|
292
|
+
flowchart TD
|
|
293
|
+
subgraph before ["优化前:Agent 自行读取"]
|
|
294
|
+
A1[Agent starts] --> A2["Read tasks.json"]
|
|
295
|
+
A2 --> A3["Read profile.json"]
|
|
296
|
+
A3 --> A4["Read session_result.json"]
|
|
297
|
+
A4 --> A5["Read requirements.md"]
|
|
298
|
+
A5 --> A6["Read tests.json"]
|
|
299
|
+
A6 --> A7["开始编码(5+ Read 调用浪费)"]
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
subgraph after ["优化后:Harness 注入上下文"]
|
|
303
|
+
B1["Harness 预读文件"] --> B2["注入 Hint 7: 任务上下文"]
|
|
304
|
+
B1 --> B3["注入 Hint 8: 会话记忆"]
|
|
305
|
+
B2 --> B4["Agent prompt 就绪"]
|
|
306
|
+
B3 --> B4
|
|
307
|
+
B4 --> B5["Agent 直接开始编码"]
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Hint 7: 任务上下文注入
|
|
312
|
+
|
|
313
|
+
Harness 在 `buildCodingPrompt()` 中预读 `tasks.json`,将下一个待办任务的 id、description、category、steps 数量和整体进度注入 user prompt。Agent 无需自行读取 `tasks.json`。
|
|
314
|
+
|
|
315
|
+
### Hint 8: 会话记忆注入
|
|
316
|
+
|
|
317
|
+
Harness 在 `buildCodingPrompt()` 中预读 `session_result.json`,将上次会话的 task_id、结果和 notes 摘要注入 user prompt。Agent 无需自行读取历史 session 数据。
|
|
318
|
+
|
|
319
|
+
### Loop Detection(编辑死循环检测)
|
|
320
|
+
|
|
321
|
+
PreToolUse hook 中追踪每个文件的编辑次数。当同一文件被 Write/Edit 超过 5 次时,hook 返回 `decision: "block"` 阻止操作并提示 Agent 重新审视方案。
|
|
322
|
+
|
|
323
|
+
### 文件权限模型
|
|
324
|
+
|
|
325
|
+
| 文件 | 写入方 | Agent 权限 |
|
|
326
|
+
|------|--------|-----------|
|
|
327
|
+
| `progress.json` | Harness | 只读 |
|
|
328
|
+
| `sync_state.json` | Harness | 只读 |
|
|
329
|
+
| `session_result.json` | Agent 写 `current`,Harness 归档到 `history` | 写 `current` |
|
|
330
|
+
| `tasks.json` | Agent(仅 `status` 字段) | 修改 `status` |
|
|
331
|
+
| `project_profile.json` | Agent(仅扫描阶段) | 扫描时写入 |
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## 10. Claude Agent SDK V1/V2 对比与迁移计划
|
|
282
336
|
|
|
283
337
|
当前使用 **V1 稳定 API**(`query()`),V2 为 preview 状态(`unstable_` 前缀)。
|
|
284
338
|
|
|
@@ -336,14 +390,13 @@ query({
|
|
|
336
390
|
|
|
337
391
|
---
|
|
338
392
|
|
|
339
|
-
##
|
|
393
|
+
## 11. 后续优化方向
|
|
340
394
|
|
|
341
395
|
### P0 — 近期
|
|
342
396
|
|
|
343
397
|
| 方向 | 说明 |
|
|
344
398
|
|------|------|
|
|
345
399
|
| **文件保护 Deny-list** | PreToolUse hook 拦截对保护文件的写入(比文字规则更硬性) |
|
|
346
|
-
| **TUI 终端监控** | 基于 ANSI 的全屏界面,替代单行 spinner |
|
|
347
400
|
| **成本预算控制** | `.env` 新增 `MAX_COST_USD`,超预算自动停止 |
|
|
348
401
|
|
|
349
402
|
### P1 — 中期
|
|
@@ -359,6 +412,7 @@ query({
|
|
|
359
412
|
|
|
360
413
|
| 方向 | 说明 |
|
|
361
414
|
|------|------|
|
|
415
|
+
| **TUI 终端监控** | 基于 ANSI 的全屏界面,替代单行 spinner |
|
|
362
416
|
| **Web UI 监控** | 可选插件包 `@claude-coder/web-ui` |
|
|
363
417
|
| **PR/CI 集成** | Session 完成后自动创建 PR、监控 CI |
|
|
364
418
|
| **Prompt A/B 测试** | 多版本 CLAUDE.md 并行对比效果 |
|
package/package.json
CHANGED
package/src/prompts.js
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { paths, loadConfig, getRequirementsHash } = require('./config');
|
|
5
|
+
const { loadTasks, findNextTask, getStats } = require('./tasks');
|
|
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
|
+
}
|
|
5
16
|
|
|
6
17
|
/**
|
|
7
18
|
* Build system prompt by combining template files.
|
|
@@ -79,6 +90,35 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
79
90
|
} catch { /* ignore */ }
|
|
80
91
|
}
|
|
81
92
|
|
|
93
|
+
// Hint 7: Task context (harness pre-read, saves Agent 2-3 Read calls)
|
|
94
|
+
let taskHint = '';
|
|
95
|
+
try {
|
|
96
|
+
const taskData = loadTasks();
|
|
97
|
+
if (taskData) {
|
|
98
|
+
const next = findNextTask(taskData);
|
|
99
|
+
const stats = getStats(taskData);
|
|
100
|
+
if (next) {
|
|
101
|
+
taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
|
|
102
|
+
`category=${next.category}, steps=${next.steps.length}步。` +
|
|
103
|
+
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
|
|
104
|
+
`第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch { /* ignore */ }
|
|
108
|
+
|
|
109
|
+
// Hint 8: Session memory (last session summary, recency zone for attention)
|
|
110
|
+
let memoryHint = '';
|
|
111
|
+
if (fs.existsSync(p.sessionResult)) {
|
|
112
|
+
try {
|
|
113
|
+
const sr = safeJsonParse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
114
|
+
const last = sr.current || (sr.history?.length ? sr.history[sr.history.length - 1] : null);
|
|
115
|
+
if (last?.task_id) {
|
|
116
|
+
memoryHint = `上次会话: ${last.task_id} → ${last.status_after || last.session_result}` +
|
|
117
|
+
(last.notes ? `, 要点: ${last.notes.slice(0, 100)}` : '') + '。';
|
|
118
|
+
}
|
|
119
|
+
} catch { /* ignore */ }
|
|
120
|
+
}
|
|
121
|
+
|
|
82
122
|
return [
|
|
83
123
|
`Session ${sessionNum}。执行 6 步流程。`,
|
|
84
124
|
'效率要求:先规划后编码,完成全部编码后再统一测试,禁止编码-测试反复跳转。后端任务用 curl 验证,不启动浏览器。',
|
|
@@ -87,6 +127,8 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
87
127
|
testHint,
|
|
88
128
|
docsHint,
|
|
89
129
|
envHint,
|
|
130
|
+
taskHint,
|
|
131
|
+
memoryHint,
|
|
90
132
|
`完成后写入 session_result.json。${retryContext}`,
|
|
91
133
|
].filter(Boolean).join('\n');
|
|
92
134
|
}
|
package/src/runner.js
CHANGED
|
@@ -100,7 +100,10 @@ function appendProgress(entry) {
|
|
|
100
100
|
const p = paths();
|
|
101
101
|
let progress = { sessions: [] };
|
|
102
102
|
if (fs.existsSync(p.progressFile)) {
|
|
103
|
-
try {
|
|
103
|
+
try {
|
|
104
|
+
const text = fs.readFileSync(p.progressFile, 'utf8');
|
|
105
|
+
try { progress = JSON.parse(text); } catch { progress = JSON.parse(text.replace(/[\u201c\u201d]/g, '"')); }
|
|
106
|
+
} catch { /* reset */ }
|
|
104
107
|
}
|
|
105
108
|
if (!Array.isArray(progress.sessions)) progress.sessions = [];
|
|
106
109
|
progress.sessions.push(entry);
|
|
@@ -111,7 +114,10 @@ function updateSessionHistory(sessionData, sessionNum) {
|
|
|
111
114
|
const p = paths();
|
|
112
115
|
let sr = { current: null, history: [] };
|
|
113
116
|
if (fs.existsSync(p.sessionResult)) {
|
|
114
|
-
try {
|
|
117
|
+
try {
|
|
118
|
+
const text = fs.readFileSync(p.sessionResult, 'utf8');
|
|
119
|
+
try { sr = JSON.parse(text); } catch { sr = JSON.parse(text.replace(/[\u201c\u201d]/g, '"')); }
|
|
120
|
+
} catch { /* reset */ }
|
|
115
121
|
if (!sr.history && sr.session_result) {
|
|
116
122
|
sr = { current: sr, history: [] };
|
|
117
123
|
}
|
package/src/session.js
CHANGED
|
@@ -89,6 +89,9 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
89
89
|
|
|
90
90
|
indicator.start(sessionNum);
|
|
91
91
|
|
|
92
|
+
const editCounts = {};
|
|
93
|
+
const EDIT_THRESHOLD = 5;
|
|
94
|
+
|
|
92
95
|
try {
|
|
93
96
|
const queryOpts = buildQueryOptions(config, opts);
|
|
94
97
|
queryOpts.systemPrompt = systemPrompt;
|
|
@@ -97,6 +100,18 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
97
100
|
matcher: '*',
|
|
98
101
|
hooks: [async (input) => {
|
|
99
102
|
inferPhaseStep(indicator, input.tool_name, input.tool_input);
|
|
103
|
+
|
|
104
|
+
const filePath = input.tool_input?.file_path || input.tool_input?.path || '';
|
|
105
|
+
if (['Write', 'Edit', 'MultiEdit'].includes(input.tool_name) && filePath) {
|
|
106
|
+
editCounts[filePath] = (editCounts[filePath] || 0) + 1;
|
|
107
|
+
if (editCounts[filePath] > EDIT_THRESHOLD) {
|
|
108
|
+
return {
|
|
109
|
+
decision: 'block',
|
|
110
|
+
message: `已对 ${filePath} 编辑 ${editCounts[filePath]} 次,疑似死循环。请重新审视方案后再继续。`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
100
115
|
return {};
|
|
101
116
|
}]
|
|
102
117
|
}]
|
package/src/tasks.js
CHANGED
|
@@ -13,16 +13,20 @@ const TRANSITIONS = {
|
|
|
13
13
|
done: [],
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
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
|
+
}
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
function loadTasks() {
|
|
23
27
|
const p = paths();
|
|
24
28
|
if (!fs.existsSync(p.tasksFile)) return null;
|
|
25
|
-
return
|
|
29
|
+
return safeJsonParse(fs.readFileSync(p.tasksFile, 'utf8'));
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
function saveTasks(data) {
|
package/src/validator.js
CHANGED
|
@@ -4,8 +4,14 @@ const fs = require('fs');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const { paths, log, getProjectRoot } = require('./config');
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
|
|
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
|
+
}
|
|
9
15
|
}
|
|
10
16
|
|
|
11
17
|
function validateSessionResult() {
|
|
@@ -18,7 +24,7 @@ function validateSessionResult() {
|
|
|
18
24
|
|
|
19
25
|
let data;
|
|
20
26
|
try {
|
|
21
|
-
data =
|
|
27
|
+
data = safeJsonParse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
22
28
|
} catch {
|
|
23
29
|
log('error', 'session_result.json JSON 格式错误');
|
|
24
30
|
return { valid: false, fatal: true, reason: 'JSON 格式错误' };
|
|
@@ -90,9 +96,9 @@ function checkTestCoverage() {
|
|
|
90
96
|
if (!fs.existsSync(p.testsFile) || !fs.existsSync(p.sessionResult)) return;
|
|
91
97
|
|
|
92
98
|
try {
|
|
93
|
-
const sr =
|
|
99
|
+
const sr = safeJsonParse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
94
100
|
const current = sr.current || sr;
|
|
95
|
-
const tests =
|
|
101
|
+
const tests = safeJsonParse(fs.readFileSync(p.testsFile, 'utf8'));
|
|
96
102
|
|
|
97
103
|
const taskId = current.task_id || '';
|
|
98
104
|
const testCases = tests.test_cases || [];
|
package/templates/CLAUDE.md
CHANGED
|
@@ -175,10 +175,13 @@ pending ──→ in_progress ──→ testing ──→ done
|
|
|
175
175
|
|
|
176
176
|
### 第一步:恢复上下文
|
|
177
177
|
|
|
178
|
-
1.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
1. **检查 prompt 注入的上下文**:
|
|
179
|
+
- 如果 prompt 中包含"任务上下文"(Hint 7),说明 harness 已注入当前任务信息,**跳过读取 tasks.json**,直接确认任务后进入第二步
|
|
180
|
+
- 如果 prompt 中包含"上次会话"(Hint 8),说明 harness 已注入上次会话摘要,**跳过读取 session_result.json 历史**
|
|
181
|
+
2. 批量读取以下文件(一次工具调用,跳过已注入的):`.claude-coder/project_profile.json`、`.claude-coder/tasks.json`(仅当无 Hint 7 时)、`.claude-coder/session_result.json`(仅当无 Hint 8 时)
|
|
182
|
+
3. 如果 `session_result.json` 不存在或 history 为空且无 Hint 8,运行 `git log --oneline -20` 补充上下文
|
|
183
|
+
4. 如果项目根目录存在 `requirements.md`,读取用户的详细需求和偏好(技术约束、样式要求等),作为本次会话的参考依据
|
|
184
|
+
5. **需求同步(条件触发)**:如果 prompt 中提示"需求已变更",读取 `requirements.md`,对比 `tasks.json`,将新增需求追加为 `pending` 任务。未提示则跳过
|
|
182
185
|
|
|
183
186
|
### 第二步:环境与健康检查
|
|
184
187
|
|