claude-coder 1.0.2 → 1.0.4
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/bin/cli.js +8 -1
- package/docs/ARCHITECTURE.md +125 -13
- package/package.json +1 -1
- package/src/prompts.js +36 -0
- package/src/runner.js +2 -2
- package/src/session.js +19 -3
- package/src/tasks.js +7 -1
- package/src/validator.js +7 -3
- package/templates/CLAUDE.md +7 -4
package/bin/cli.js
CHANGED
|
@@ -46,6 +46,9 @@ function parseArgs(argv) {
|
|
|
46
46
|
case '--dry-run':
|
|
47
47
|
opts.dryRun = true;
|
|
48
48
|
break;
|
|
49
|
+
case '--view':
|
|
50
|
+
opts.viewMode = true;
|
|
51
|
+
break;
|
|
49
52
|
case '--help':
|
|
50
53
|
case '-h':
|
|
51
54
|
showHelp();
|
|
@@ -78,7 +81,11 @@ async function main() {
|
|
|
78
81
|
switch (command) {
|
|
79
82
|
case 'run': {
|
|
80
83
|
const runner = require('../src/runner');
|
|
81
|
-
|
|
84
|
+
if (opts.viewMode) {
|
|
85
|
+
await runner.view(positional[0] || null, opts);
|
|
86
|
+
} else {
|
|
87
|
+
await runner.run(positional[0] || null, opts);
|
|
88
|
+
}
|
|
82
89
|
break;
|
|
83
90
|
}
|
|
84
91
|
case 'setup': {
|
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,14 +280,123 @@ 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 对比与迁移计划
|
|
336
|
+
|
|
337
|
+
当前使用 **V1 稳定 API**(`query()`),V2 为 preview 状态(`unstable_` 前缀)。
|
|
338
|
+
|
|
339
|
+
### V1 vs V2 API 对比
|
|
340
|
+
|
|
341
|
+
| 维度 | V1 `query()` | V2 `send()/stream()` |
|
|
342
|
+
|------|-------------|---------------------|
|
|
343
|
+
| **状态** | 稳定,生产可用 | `unstable_` 前缀,preview |
|
|
344
|
+
| **入口函数** | `query({ prompt, options })` | `unstable_v2_createSession(opts)` / `unstable_v2_prompt()` |
|
|
345
|
+
| **多轮会话** | 需手动管理 AsyncGenerator | `session.send()` + `session.stream()`,更简洁 |
|
|
346
|
+
| **会话恢复** | `options.resume: sessionId` | `unstable_v2_resumeSession(id)` |
|
|
347
|
+
| **Hooks** | `options.hooks: { PreToolUse, PostToolUse, ... }` | 未支持 |
|
|
348
|
+
| **Subagents** | `options.agents: { name: AgentDefinition }` | 未支持 |
|
|
349
|
+
| **Session Fork** | `options.forkSession: true` | 未支持 |
|
|
350
|
+
| **Plugins** | `options.plugins: [{ type, path }]` | 未支持 |
|
|
351
|
+
| **结构化输出** | `options.outputFormat: { type: 'json_schema', schema }` | 支持 |
|
|
352
|
+
| **文件检查点** | `options.enableFileCheckpointing + rewindFiles()` | 未明确 |
|
|
353
|
+
| **Cost Tracking** | `SDKResultMessage.total_cost_usd` | `SDKResultMessage.total_cost_usd` |
|
|
354
|
+
| **权限控制** | `canUseTool`, `permissionMode`, `allowedTools`, `disallowedTools` | 继承 |
|
|
355
|
+
|
|
356
|
+
### 当前实现使用的 V1 特性
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
query({
|
|
360
|
+
prompt,
|
|
361
|
+
options: {
|
|
362
|
+
systemPrompt, // 注入 CLAUDE.md
|
|
363
|
+
allowedTools, // 工具白名单
|
|
364
|
+
permissionMode: 'bypassPermissions',
|
|
365
|
+
allowDangerouslySkipPermissions: true,
|
|
366
|
+
model, // 从 .env 传入
|
|
367
|
+
env, // 环境变量透传
|
|
368
|
+
settingSources: ['project'], // 加载项目 CLAUDE.md
|
|
369
|
+
hooks: { PreToolUse: [...] }, // 实时 spinner 监控
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### V2 迁移条件(等待稳定后)
|
|
375
|
+
|
|
376
|
+
1. V2 去掉 `unstable_` 前缀,正式发布
|
|
377
|
+
2. V2 支持 Hooks(当前项目依赖 PreToolUse 做 spinner 和 activity log)
|
|
378
|
+
3. V2 支持 Subagents(未来可能用于扫描 Agent / 编码 Agent 分离)
|
|
379
|
+
|
|
380
|
+
### 可利用但尚未使用的 V1 特性
|
|
381
|
+
|
|
382
|
+
| 特性 | 说明 | 优先级 |
|
|
383
|
+
|------|------|--------|
|
|
384
|
+
| `maxBudgetUsd` | SDK 内置成本上限,替代自研追踪 | P0 |
|
|
385
|
+
| `effort` | 控制思考深度(`low`/`medium`/`high`/`max`) | P1 |
|
|
386
|
+
| `enableFileCheckpointing` | 文件操作检查点,比 git reset 更精细 | P1 |
|
|
387
|
+
| `outputFormat` | 结构化输出,让 Agent 直接输出 JSON 格式 | P1 |
|
|
388
|
+
| `agents` | 定义子 Agent,不同模型/工具集 | P2 |
|
|
389
|
+
| `betas` | 扩展上下文窗口 | P2 |
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## 11. 后续优化方向
|
|
282
394
|
|
|
283
395
|
### P0 — 近期
|
|
284
396
|
|
|
285
397
|
| 方向 | 说明 |
|
|
286
398
|
|------|------|
|
|
287
399
|
| **文件保护 Deny-list** | PreToolUse hook 拦截对保护文件的写入(比文字规则更硬性) |
|
|
288
|
-
| **TUI 终端监控** | 基于 ANSI 的全屏界面,替代单行 spinner |
|
|
289
400
|
| **成本预算控制** | `.env` 新增 `MAX_COST_USD`,超预算自动停止 |
|
|
290
401
|
|
|
291
402
|
### P1 — 中期
|
|
@@ -301,6 +412,7 @@ sequenceDiagram
|
|
|
301
412
|
|
|
302
413
|
| 方向 | 说明 |
|
|
303
414
|
|------|------|
|
|
415
|
+
| **TUI 终端监控** | 基于 ANSI 的全屏界面,替代单行 spinner |
|
|
304
416
|
| **Web UI 监控** | 可选插件包 `@claude-coder/web-ui` |
|
|
305
417
|
| **PR/CI 集成** | Session 完成后自动创建 PR、监控 CI |
|
|
306
418
|
| **Prompt A/B 测试** | 多版本 CLAUDE.md 并行对比效果 |
|
package/package.json
CHANGED
package/src/prompts.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
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 normalizeJson(text) {
|
|
8
|
+
return text.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'");
|
|
9
|
+
}
|
|
5
10
|
|
|
6
11
|
/**
|
|
7
12
|
* Build system prompt by combining template files.
|
|
@@ -79,6 +84,35 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
79
84
|
} catch { /* ignore */ }
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
// Hint 7: Task context (harness pre-read, saves Agent 2-3 Read calls)
|
|
88
|
+
let taskHint = '';
|
|
89
|
+
try {
|
|
90
|
+
const taskData = loadTasks();
|
|
91
|
+
if (taskData) {
|
|
92
|
+
const next = findNextTask(taskData);
|
|
93
|
+
const stats = getStats(taskData);
|
|
94
|
+
if (next) {
|
|
95
|
+
taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
|
|
96
|
+
`category=${next.category}, steps=${next.steps.length}步。` +
|
|
97
|
+
`进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
|
|
98
|
+
`第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch { /* ignore */ }
|
|
102
|
+
|
|
103
|
+
// Hint 8: Session memory (last session summary, recency zone for attention)
|
|
104
|
+
let memoryHint = '';
|
|
105
|
+
if (fs.existsSync(p.sessionResult)) {
|
|
106
|
+
try {
|
|
107
|
+
const sr = JSON.parse(normalizeJson(fs.readFileSync(p.sessionResult, 'utf8')));
|
|
108
|
+
const last = sr.current || (sr.history?.length ? sr.history[sr.history.length - 1] : null);
|
|
109
|
+
if (last?.task_id) {
|
|
110
|
+
memoryHint = `上次会话: ${last.task_id} → ${last.status_after || last.session_result}` +
|
|
111
|
+
(last.notes ? `, 要点: ${last.notes.slice(0, 100)}` : '') + '。';
|
|
112
|
+
}
|
|
113
|
+
} catch { /* ignore */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
82
116
|
return [
|
|
83
117
|
`Session ${sessionNum}。执行 6 步流程。`,
|
|
84
118
|
'效率要求:先规划后编码,完成全部编码后再统一测试,禁止编码-测试反复跳转。后端任务用 curl 验证,不启动浏览器。',
|
|
@@ -87,6 +121,8 @@ function buildCodingPrompt(sessionNum, opts = {}) {
|
|
|
87
121
|
testHint,
|
|
88
122
|
docsHint,
|
|
89
123
|
envHint,
|
|
124
|
+
taskHint,
|
|
125
|
+
memoryHint,
|
|
90
126
|
`完成后写入 session_result.json。${retryContext}`,
|
|
91
127
|
].filter(Boolean).join('\n');
|
|
92
128
|
}
|
package/src/runner.js
CHANGED
|
@@ -100,7 +100,7 @@ function appendProgress(entry) {
|
|
|
100
100
|
const p = paths();
|
|
101
101
|
let progress = { sessions: [] };
|
|
102
102
|
if (fs.existsSync(p.progressFile)) {
|
|
103
|
-
try { progress = JSON.parse(fs.readFileSync(p.progressFile, 'utf8')); } catch { /* reset */ }
|
|
103
|
+
try { progress = JSON.parse(fs.readFileSync(p.progressFile, 'utf8').replace(/[\u201c\u201d]/g, '"')); } catch { /* reset */ }
|
|
104
104
|
}
|
|
105
105
|
if (!Array.isArray(progress.sessions)) progress.sessions = [];
|
|
106
106
|
progress.sessions.push(entry);
|
|
@@ -111,7 +111,7 @@ function updateSessionHistory(sessionData, sessionNum) {
|
|
|
111
111
|
const p = paths();
|
|
112
112
|
let sr = { current: null, history: [] };
|
|
113
113
|
if (fs.existsSync(p.sessionResult)) {
|
|
114
|
-
try { sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8')); } catch { /* reset */ }
|
|
114
|
+
try { sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8').replace(/[\u201c\u201d]/g, '"')); } catch { /* reset */ }
|
|
115
115
|
if (!sr.history && sr.session_result) {
|
|
116
116
|
sr = { current: sr, history: [] };
|
|
117
117
|
}
|
package/src/session.js
CHANGED
|
@@ -62,10 +62,11 @@ function extractResult(messages) {
|
|
|
62
62
|
return null;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
function logMessage(message, logStream) {
|
|
65
|
+
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) process.stderr.write('\r\x1b[K');
|
|
69
70
|
process.stdout.write(block.text);
|
|
70
71
|
if (logStream) logStream.write(block.text);
|
|
71
72
|
}
|
|
@@ -88,6 +89,9 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
88
89
|
|
|
89
90
|
indicator.start(sessionNum);
|
|
90
91
|
|
|
92
|
+
const editCounts = {};
|
|
93
|
+
const EDIT_THRESHOLD = 5;
|
|
94
|
+
|
|
91
95
|
try {
|
|
92
96
|
const queryOpts = buildQueryOptions(config, opts);
|
|
93
97
|
queryOpts.systemPrompt = systemPrompt;
|
|
@@ -96,6 +100,18 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
96
100
|
matcher: '*',
|
|
97
101
|
hooks: [async (input) => {
|
|
98
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
|
+
|
|
99
115
|
return {};
|
|
100
116
|
}]
|
|
101
117
|
}]
|
|
@@ -106,7 +122,7 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
106
122
|
const collected = [];
|
|
107
123
|
for await (const message of session) {
|
|
108
124
|
collected.push(message);
|
|
109
|
-
logMessage(message, logStream);
|
|
125
|
+
logMessage(message, logStream, indicator);
|
|
110
126
|
}
|
|
111
127
|
|
|
112
128
|
logStream.end();
|
|
@@ -168,7 +184,7 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
168
184
|
const collected = [];
|
|
169
185
|
for await (const message of session) {
|
|
170
186
|
collected.push(message);
|
|
171
|
-
logMessage(message, logStream);
|
|
187
|
+
logMessage(message, logStream, indicator);
|
|
172
188
|
}
|
|
173
189
|
|
|
174
190
|
logStream.end();
|
package/src/tasks.js
CHANGED
|
@@ -13,10 +13,16 @@ const TRANSITIONS = {
|
|
|
13
13
|
done: [],
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
function normalizeJson(text) {
|
|
17
|
+
return text
|
|
18
|
+
.replace(/[\u201c\u201d]/g, '"')
|
|
19
|
+
.replace(/[\u2018\u2019]/g, "'");
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
function loadTasks() {
|
|
17
23
|
const p = paths();
|
|
18
24
|
if (!fs.existsSync(p.tasksFile)) return null;
|
|
19
|
-
return JSON.parse(fs.readFileSync(p.tasksFile, 'utf8'));
|
|
25
|
+
return JSON.parse(normalizeJson(fs.readFileSync(p.tasksFile, 'utf8')));
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
function saveTasks(data) {
|
package/src/validator.js
CHANGED
|
@@ -4,6 +4,10 @@ const fs = require('fs');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
const { paths, log, getProjectRoot } = require('./config');
|
|
6
6
|
|
|
7
|
+
function normalizeJson(text) {
|
|
8
|
+
return text.replace(/[\u201c\u201d]/g, '"').replace(/[\u2018\u2019]/g, "'");
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
function validateSessionResult() {
|
|
8
12
|
const p = paths();
|
|
9
13
|
|
|
@@ -14,7 +18,7 @@ function validateSessionResult() {
|
|
|
14
18
|
|
|
15
19
|
let data;
|
|
16
20
|
try {
|
|
17
|
-
data = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
21
|
+
data = JSON.parse(normalizeJson(fs.readFileSync(p.sessionResult, 'utf8')));
|
|
18
22
|
} catch {
|
|
19
23
|
log('error', 'session_result.json JSON 格式错误');
|
|
20
24
|
return { valid: false, fatal: true, reason: 'JSON 格式错误' };
|
|
@@ -86,9 +90,9 @@ function checkTestCoverage() {
|
|
|
86
90
|
if (!fs.existsSync(p.testsFile) || !fs.existsSync(p.sessionResult)) return;
|
|
87
91
|
|
|
88
92
|
try {
|
|
89
|
-
const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
|
|
93
|
+
const sr = JSON.parse(normalizeJson(fs.readFileSync(p.sessionResult, 'utf8')));
|
|
90
94
|
const current = sr.current || sr;
|
|
91
|
-
const tests = JSON.parse(fs.readFileSync(p.testsFile, 'utf8'));
|
|
95
|
+
const tests = JSON.parse(normalizeJson(fs.readFileSync(p.testsFile, 'utf8')));
|
|
92
96
|
|
|
93
97
|
const taskId = current.task_id || '';
|
|
94
98
|
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
|
|