claude-coder 1.2.0 → 1.4.0

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/README.md CHANGED
@@ -53,9 +53,10 @@ claude-coder run "实现用户注册和登录功能"
53
53
  | `claude-coder run --max 1` | 单次执行 |
54
54
  | `claude-coder run --dry-run` | 预览模式 |
55
55
  | `claude-coder init` | 初始化项目环境 |
56
- | `claude-coder add "指令"` | 追加任务(默认用 opus 级模型推理) |
56
+ | `claude-coder add "指令"` | 追加任务 |
57
57
  | `claude-coder add -r [file]` | 从需求文件追加任务 |
58
58
  | `claude-coder add "..." --model M` | 指定模型追加任务 |
59
+ | `claude-coder auth [url]` | 导出 Playwright 登录状态 |
59
60
  | `claude-coder validate` | 手动校验 |
60
61
  | `claude-coder status` | 查看进度和成本 |
61
62
  | `claude-coder config sync` | 同步配置到 ~/.claude/ |
@@ -72,6 +73,8 @@ claude-coder run "实现用户注册和登录功能"
72
73
 
73
74
  **追加任务**:`claude-coder add "新增管理员后台"` 或 `claude-coder add -r requirements.md` — 仅追加到任务列表,下次 run 时执行。
74
75
 
76
+ **自动测试 + 凭证持久化**:`claude-coder auth http://localhost:3000` — 导出浏览器登录态(cookies + localStorage),Agent 测试时自动使用。缺 API Key 时 Agent 会自行记录到 `test.env` 并继续推进,不会停工。详见 [测试凭证持久化方案](docs/PLAYWRIGHT_CREDENTIALS.md)。
77
+
75
78
  ## 模型支持
76
79
 
77
80
  | 提供商 | 说明 |
@@ -94,6 +97,7 @@ your-project/
94
97
  progress.json # 会话历史 + 成本
95
98
  tests.json # 验证记录
96
99
  test.env # 测试凭证(API Key 等,可选)
100
+ playwright-auth.json # Playwright 登录状态(可选,auth 命令生成)
97
101
  .runtime/ # 临时文件(含日志)
98
102
  requirements.md # 需求文档(可选)
99
103
  ```
@@ -110,8 +114,8 @@ your-project/
110
114
 
111
115
  ## 文档
112
116
 
113
- - [技术架构](docs/ARCHITECTURE.md) — 模块职责、提示语注入架构、注意力机制、Hook 数据流、后续优化方向
114
- - [Playwright 凭证持久化](docs/PLAYWRIGHT_CREDENTIALS.md) — 测试 cookies 和 API Key 管理方案
117
+ - [技术架构](docs/ARCHITECTURE.md) — 核心设计规则、模块职责、提示语注入架构、注意力机制、Hook 数据流
118
+ - [测试凭证持久化方案](docs/PLAYWRIGHT_CREDENTIALS.md) — 自动测试的凭证管理:Playwright 登录态导出、API Key 持久化、Agent 缺凭证时的行为策略
115
119
 
116
120
  ## License
117
121
 
package/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ const COMMANDS = {
8
8
  setup: { desc: '交互式模型配置', usage: 'claude-coder setup' },
9
9
  init: { desc: '初始化项目环境', usage: 'claude-coder init' },
10
10
  add: { desc: '追加任务到 tasks.json', usage: 'claude-coder add "指令" [--model M] | add -r [file]' },
11
+ auth: { desc: '导出 Playwright 登录状态', usage: 'claude-coder auth [url]' },
11
12
  validate: { desc: '手动校验上次 session', usage: 'claude-coder validate' },
12
13
  status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
13
14
  config: { desc: '配置管理', usage: 'claude-coder config sync' },
@@ -29,6 +30,8 @@ function showHelp() {
29
30
  console.log(' claude-coder add "新增搜索功能" 追加任务');
30
31
  console.log(' claude-coder add -r 从 requirements.md 追加任务');
31
32
  console.log(' claude-coder add "..." --model opus-4 指定模型追加任务');
33
+ console.log(' claude-coder auth 导出 Playwright 登录状态');
34
+ console.log(' claude-coder auth http://localhost:8080 指定登录 URL');
32
35
  console.log(' claude-coder status 查看进度和成本');
33
36
  console.log(`\n前置条件: npm install -g @anthropic-ai/claude-agent-sdk`);
34
37
  }
@@ -109,14 +112,16 @@ async function main() {
109
112
  break;
110
113
  }
111
114
  case 'add': {
115
+ const fs = require('fs');
116
+ const nodePath = require('path');
112
117
  let instruction = positional[0] || '';
113
118
  if (opts.readFile) {
114
- const reqPath = require('path').resolve(opts.readFile);
115
- if (!require('fs').existsSync(reqPath)) {
119
+ const reqPath = nodePath.resolve(opts.readFile);
120
+ if (!fs.existsSync(reqPath)) {
116
121
  console.error(`文件不存在: ${reqPath}`);
117
122
  process.exit(1);
118
123
  }
119
- instruction = require('fs').readFileSync(reqPath, 'utf8');
124
+ instruction = fs.readFileSync(reqPath, 'utf8');
120
125
  console.log(`已读取需求文件: ${opts.readFile}`);
121
126
  }
122
127
  if (!instruction) {
@@ -127,6 +132,11 @@ async function main() {
127
132
  await runner.add(instruction, opts);
128
133
  break;
129
134
  }
135
+ case 'auth': {
136
+ const { auth } = require('../src/auth');
137
+ await auth(positional[0] || null);
138
+ break;
139
+ }
130
140
  case 'validate': {
131
141
  const validator = require('../src/validator');
132
142
  const result = await validator.validate();
@@ -10,6 +10,54 @@
10
10
 
11
11
  ---
12
12
 
13
+ ## 0. 核心设计规则(MUST READ)
14
+
15
+ > 以下规则按重要性排序(注意力 primacy zone),所有代码修改和架构决策必须遵循。
16
+
17
+ ### 规则 1:长 Session 不停工
18
+
19
+ Agent 在单次 session 中应最大化推进任务进度。**任何非致命问题都不应中断 session**。
20
+
21
+ - 缺少 API Key → 用 mock 或代码逻辑验证替代,记录到 `test.env`,继续推进
22
+ - 测试环境未就绪 → 跳过该测试步骤,完成其余可验证的步骤
23
+ - 服务启动失败 → 尝试排查修复,无法修复则记录问题后推进代码实现
24
+
25
+ **反面案例**:Agent 因 `OPENAI_API_KEY` 缺失直接标记任务 `failed` → 浪费整个 session
26
+
27
+ ### 规则 2:回滚即彻底回滚
28
+
29
+ `git reset --hard` 是全量回滚,不做部分文件保护。
30
+
31
+ - 凭证文件(`test.env`、`playwright-auth.json`)应通过 `.gitignore` 排除在 git 之外
32
+ - 如果回滚发生,说明 session 确实失败,代码应全部还原
33
+ - 不需要 backup/restore 机制 — 这是过度设计
34
+
35
+ ### 规则 3:分层校验(fatal / recoverable / pass)
36
+
37
+ 不是所有校验失败都需要回滚:
38
+
39
+ | 情况 | 有新 commit | 处理 |
40
+ |------|------------|------|
41
+ | session_result.json 格式异常 | 是 | **warn** — 代码已提交且可能正确,不回滚 |
42
+ | session_result.json 格式异常 | 否 | **fatal** — 无进展,回滚 |
43
+ | 代码结构性错误 | — | **fatal** — 回滚 |
44
+ | 全部通过 | — | **pass** — 推送 |
45
+
46
+ ### 规则 4:凭证与代码分离
47
+
48
+ | 文件 | git 状态 | 说明 |
49
+ |------|---------|------|
50
+ | `test.env` | .gitignore | Agent 可写入发现的 API Key、测试账号 |
51
+ | `playwright-auth.json` | .gitignore | 用户通过 `claude-coder auth` 生成 |
52
+ | `session_result.json` | git-tracked | Agent 每次 session 覆盖写入 |
53
+ | `tasks.json` | git-tracked | Agent 修改 status 字段 |
54
+
55
+ ### 规则 5:Harness 准备上下文,Agent 直接执行
56
+
57
+ Agent 不应浪费工具调用读取 harness 已知的数据。所有可预读的上下文通过 prompt hint 注入(见第 5 节 Prompt 注入架构)。
58
+
59
+ ---
60
+
13
61
  ## 1. 核心架构
14
62
 
15
63
  ```mermaid
@@ -42,7 +90,7 @@ flowchart TB
42
90
 
43
91
  query -->|Agent 工具调用| Files
44
92
  validate -->|读取| runtime
45
- validate -->|"pass → 下一 session<br/>fail → rollback"| coding
93
+ validate -->|"pass → 下一 session<br/>fatal → rollback<br/>recoverable + commit → warn"| coding
46
94
  ```
47
95
 
48
96
  **核心特征:**
@@ -80,7 +128,7 @@ flowchart LR
80
128
  pause_check -->|继续| session
81
129
  done_check -->|是| finish([完成])
82
130
 
83
- val -->|fail| rollback["git reset --hard"]
131
+ val -->|fatal| rollback["git reset --hard"]
84
132
  rollback --> retry_check{连续失败<br/>≥3次?}
85
133
  retry_check -->|否| session
86
134
  retry_check -->|是| mark_failed["标记 task failed"]
@@ -100,8 +148,9 @@ src/
100
148
  prompts.js 提示语构建:系统 prompt 组合 + 条件 hint + 任务分解指导
101
149
  init.js 环境初始化:读取 profile 执行依赖安装、服务启动、健康检查
102
150
  scanner.js 初始化扫描:调用 runScanSession + 重试
103
- validator.js 校验引擎:session_result 结构校验 + git 检查 + 测试覆盖
151
+ validator.js 校验引擎:分层校验(fatal/recoverable/pass)+ git 检查 + 测试覆盖
104
152
  tasks.js 任务管理:CRUD + 状态机 + 进度展示
153
+ auth.js Playwright 凭证:导出登录状态 + MCP 配置 + gitignore
105
154
  indicator.js 进度指示:终端 spinner + phase/step 文件写入
106
155
  setup.js 交互式配置:模型选择、API Key、MCP 工具
107
156
  templates/
@@ -125,8 +174,9 @@ templates/
125
174
  | `src/prompts.js` | 提示语构建(系统 prompt + 条件 hint + 任务分解指导) |
126
175
  | `src/init.js` | 环境初始化(依赖安装、服务启动) |
127
176
  | `src/scanner.js` | 项目初始化扫描 |
128
- | `src/validator.js` | 校验引擎 |
177
+ | `src/validator.js` | 校验引擎(分层校验) |
129
178
  | `src/tasks.js` | 任务 CRUD + 状态机 |
179
+ | `src/auth.js` | Playwright 凭证持久化 |
130
180
  | `src/indicator.js` | 终端进度指示器 |
131
181
  | `src/setup.js` | 交互式配置向导 |
132
182
  | `templates/CLAUDE.md` | Agent 协议 |
@@ -140,7 +190,8 @@ templates/
140
190
  | `project_profile.json` | 首次扫描 | 项目元数据 |
141
191
  | `tasks.json` | 首次扫描 | 任务列表 + 状态跟踪 |
142
192
  | `progress.json` | 每次 session 结束 | 结构化会话日志 + 成本记录 |
143
- | `session_result.json` | 每次 session 结束 | 当前 + 历史 session 结果 |
193
+ | `session_result.json` | 每次 session 结束 | 当前 session 结果(扁平格式,向后兼容旧 `current` 包装) |
194
+ | `playwright-auth.json` | `claude-coder auth` | Playwright 登录状态(cookies + localStorage) |
144
195
  | `tests.json` | 首次测试时 | 验证记录(防止反复测试) |
145
196
  | `.runtime/` | 运行时 | 临时文件(phase、step、activity.log、logs/) |
146
197
 
@@ -183,11 +234,11 @@ flowchart TB
183
234
 
184
235
  | Session 类型 | systemPrompt | user prompt | 触发条件 |
185
236
  |---|---|---|---|
186
- | **编码** | CLAUDE.md | `buildCodingPrompt()` + 10 个条件 hint | 主循环每次迭代 |
237
+ | **编码** | CLAUDE.md | `buildCodingPrompt()` + 11 个条件 hint | 主循环每次迭代 |
187
238
  | **扫描** | CLAUDE.md + SCAN_PROTOCOL.md | `buildScanPrompt()` + 任务分解指导 + profile 质量要求 | 首次运行 |
188
239
  | **追加** | CLAUDE.md | `buildAddPrompt()` + 任务分解指导 | `claude-coder add` |
189
240
 
190
- ### 编码 Session 的 10 个条件 Hint
241
+ ### 编码 Session 的 11 个条件 Hint
191
242
 
192
243
  | # | Hint | 触发条件 | 影响 |
193
244
  |---|---|---|---|
@@ -197,7 +248,8 @@ flowchart TB
197
248
  | 4 | `testHint` | tests.json 有记录 | Step 5:避免重复验证 |
198
249
  | 5 | `docsHint` | profile.existing_docs 非空或 profile 有缺陷 | Step 4:读文档后再编码;profile 缺陷时提示 Agent 在 Step 6 补全 services/docs |
199
250
  | 6 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 + 项目绝对路径 |
200
- | 6b | `testEnvHint` | .claude-coder/test.env 存在 | Step 5:提示 Agent 在测试前加载测试环境变量 |
251
+ | 6b | `testEnvHint` | 始终注入(内容因 test.env 是否存在而不同) | Step 5:存在时提示加载;不存在时告知可创建 |
252
+ | 6c | `playwrightAuthHint` | .claude-coder/playwright-auth.json 存在 | Step 5:提示 Agent 前端测试可使用已认证的浏览器状态 |
201
253
  | 7 | `memoryHint` | session_result.json 存在(扁平格式) | Step 1:跳过读取 session_result.json,harness 已注入上次会话摘要 |
202
254
  | 8 | `serviceHint` | 始终注入 | Step 6:单次模式停止服务,连续模式保持服务运行 |
203
255
  | 9 | `toolGuidance` | 始终注入 | 全局:工具使用规范(Grep/Glob/Read/LS/MultiEdit/Task 替代 bash 命令),非 Claude 模型必需 |
@@ -318,13 +370,14 @@ PreToolUse hook 中追踪每个文件的编辑次数。当同一文件被 Write/
318
370
 
319
371
  ### 文件权限模型
320
372
 
321
- | 文件 | 写入方 | Agent 权限 |
322
- |------|--------|-----------|
323
- | `progress.json` | Harness | 只读 |
324
- | `sync_state.json` | Harness | 只读 |
325
- | `session_result.json` | Agent `current`,Harness 归档到 `history` | `current` |
326
- | `tasks.json` | Agent(仅 `status` 字段) | 修改 `status` |
327
- | `project_profile.json` | Agent(仅扫描阶段) | 扫描时写入 |
373
+ | 文件 | 写入方 | Agent 权限 | git 状态 |
374
+ |------|--------|-----------|---------|
375
+ | `progress.json` | Harness | 只读 | tracked |
376
+ | `session_result.json` | Agent 每次 session 覆盖写入(扁平格式) | 写入 | tracked |
377
+ | `tasks.json` | Agent(仅 `status` 字段) | 修改 `status` | tracked |
378
+ | `project_profile.json` | Agent(仅扫描阶段) | 扫描时写入 | tracked |
379
+ | `test.env` | Agent + 用户 | 可追加写入 | .gitignore |
380
+ | `playwright-auth.json` | 用户(`claude-coder auth`) | 只读 | .gitignore |
328
381
 
329
382
  ---
330
383
 
@@ -416,16 +469,16 @@ query({
416
469
 
417
470
  ---
418
471
 
419
- ## 设计原则
472
+ ## 实现原则
473
+
474
+ > 核心设计规则见 Section 0(primacy zone),以下为实现层面的补充原则。
420
475
 
421
476
  1. **SDK 原生集成**:通过 `query()` 调用 Claude,内联 hooks,原生 cost tracking
422
477
  2. **零硬依赖**:Claude Agent SDK 作为 peerDependency
423
- 3. **Agent 自治**:Agent 通过 CLAUDE.md 协议自主决策,harness 只负责调度和校验
424
- 4. **幂等设计**:所有入口可重复执行,不产生副作用
425
- 5. **跨平台**:纯 Node.js + `child_process` 调用 git,无平台特定脚本
426
- 6. **运行时隔离**:每个项目的 `.claude-coder/` 独立,不同项目互不干扰
427
- 7. **Prompt 架构分离**:静态规则在 `templates/`,动态上下文在 `src/prompts.js`
428
- 8. **文档即上下文**:文档在 harness 中分两层角色——Blueprint(`project_profile.json`,给 harness 的结构化元数据)和 Context Docs(`docs/ARCHITECTURE.md` 等,给 Agent 的人类可读文档)。Harness 通过 Hint 6 动态提醒 Agent 读取相关文档,并在 profile 有缺陷时提示补全
478
+ 3. **幂等设计**:所有入口可重复执行,不产生副作用
479
+ 4. **跨平台**:纯 Node.js + `child_process` 调用 git,无平台特定脚本
480
+ 5. **运行时隔离**:每个项目的 `.claude-coder/` 独立,不同项目互不干扰
481
+ 6. **文档即上下文**:Blueprint(`project_profile.json`)给 harness,Context Docs 给 Agent。Hint 6 动态提醒 Agent 读取相关文档
429
482
 
430
483
  ### 文档架构的学术依据
431
484
 
@@ -1,131 +1,161 @@
1
- # Playwright MCP 凭证持久化方案
1
+ # 测试凭证持久化方案
2
2
 
3
- ## 背景
3
+ ## 设计思想
4
4
 
5
- 在使用 claude-coder 运行涉及前端测试的任务时,Playwright MCP 可能需要:
6
- 1. 已登录状态的 cookies(如后台管理页面)
7
- 2. API Key 等测试凭证(如 AI 生成功能需要真实 API 调用)
5
+ claude-coder 的核心目标是让 Agent **完全自主测试**,不因凭证缺失而中断。测试中涉及三类凭证:
8
6
 
9
- 本文档描述如何在 claude-coder 工作流中管理这些凭证。
7
+ | 类型 | 示例 | 特点 |
8
+ |------|------|------|
9
+ | 浏览器状态 | 登录 cookies、localStorage 中的用户配置 | 有过期时间,跨 session 需要持久化 |
10
+ | API Key | OPENAI_API_KEY、ZHIPU_API_KEY | 长期有效,需安全存储 |
11
+ | 测试账号 | 注册的测试用户名密码、生成的 token | 可能是 Agent 自己创建的,需跨 session 传递 |
12
+
13
+ **核心原则**:
14
+ 1. **Agent 可自行发现并持久化凭证** — 测试中发现需要的 API Key 或账号,直接写入 `test.env`
15
+ 2. **凭证不受回滚影响** — `git reset --hard` 不会摧毁已保存的凭证
16
+ 3. **零手动干预** — 除首次浏览器登录态外,其余由 Agent 自动处理
10
17
 
11
18
  ---
12
19
 
13
- ## 方案 1: Playwright --storage-state(推荐用于 cookies)
20
+ ## 持久化架构
14
21
 
15
- ### 原理
22
+ ```
23
+ .claude-coder/
24
+ .env ← 模型配置(ANTHROPIC_API_KEY 等) [用户配置]
25
+ test.env ← 测试凭证(API Key、测试账号等) [Agent 可写]
26
+ playwright-auth.json ← 浏览器状态(cookies + localStorage) [auth 命令生成]
27
+ ```
16
28
 
17
- `@playwright/mcp` 支持 `--storage-state=<path>` 参数,加载预存的浏览器状态(cookies、localStorage)。
29
+ ### 文件生命周期
18
30
 
19
- ### 步骤
31
+ | 文件 | 创建方 | 写入方 | 回滚保护 | 生命周期 |
32
+ |------|--------|--------|----------|----------|
33
+ | `.env` | `claude-coder setup` | 用户 | 是 | 长期 |
34
+ | `test.env` | Agent 或用户 | Agent + 用户 | 是 | 长期,按需更新 |
35
+ | `playwright-auth.json` | `claude-coder auth` | auth 命令 | 是 | 中期,cookies 过期后需刷新 |
20
36
 
21
- **1. 手动登录并导出状态**
37
+ ### 回滚保护机制
22
38
 
23
- ```bash
24
- # 启动 Playwright,手动登录后导出
25
- npx playwright codegen --save-storage=.claude-coder/playwright-auth.json http://localhost:3000
26
- ```
39
+ Harness 在 `git reset --hard` 前备份、后恢复以下文件:
40
+ - `session_result.json` — 会话结果
41
+ - `progress.json` — 历史记录
42
+ - `test.env` — 测试凭证
43
+ - `playwright-auth.json` — 浏览器状态
27
44
 
28
- 登录完成后关闭浏览器,状态自动保存到 `playwright-auth.json`。
45
+ 这确保无论回滚多少次,凭证始终保留。
29
46
 
30
- **2. 配置 MCP 使用保存的状态**
47
+ ---
48
+
49
+ ## 核心流程
31
50
 
32
- 在项目的 `.mcp.json`(Claude Code MCP 配置)中:
51
+ ### 流程 1:Agent 自动发现凭证
33
52
 
34
- ```json
35
- {
36
- "mcpServers": {
37
- "playwright": {
38
- "command": "npx",
39
- "args": [
40
- "@playwright/mcp@latest",
41
- "--storage-state=.claude-coder/playwright-auth.json"
42
- ]
43
- }
44
- }
45
- }
46
53
  ```
54
+ Agent 测试 → 发现需要 API Key → 写入 test.env → 下次 session 自动加载
55
+ ```
56
+
57
+ Agent 在 CLAUDE.md Step 5 中被指导:测试中发现的凭证追加到 `.claude-coder/test.env`。Harness 在每次 session 的 prompt 中注入 hint,告知 Agent `test.env` 的存在和用法。
47
58
 
48
- **3. 安全注意事项**
59
+ ### 流程 2:用户预配置浏览器登录态
49
60
 
50
- ```gitignore
51
- # .gitignore
52
- .claude-coder/playwright-auth.json
61
+ ```
62
+ 用户运行 claude-coder auth → 手动登录 → 状态自动保存 → Agent 测试时使用
53
63
  ```
54
64
 
55
- ### 注意
65
+ 适用于需要已登录状态才能测试的前端页面(如后台管理、需要 cookie 的 SPA)。
56
66
 
57
- - 状态文件包含敏感 cookies,必须加入 `.gitignore`
58
- - cookies 有过期时间,需要定期重新导出
59
- - `--storage-state` 与 `--isolated` 模式配合使用效果最佳
67
+ ### 流程 3:用户预配置 API Key
60
68
 
61
- ---
69
+ ```
70
+ 用户编辑 test.env → 填入 API Key → Agent 测试前 source 加载
71
+ ```
62
72
 
63
- ## 方案 2: test.env(推荐用于 API Key)
73
+ 适用于后端功能依赖真实 API 调用的场景。
64
74
 
65
- ### 原理
75
+ ---
66
76
 
67
- `.claude-coder/test.env` 中存放测试专用的环境变量(如 API Key)。claude-coder 会自动检测此文件存在,并通过 Hint 提示 Agent 在测试前加载它。
77
+ ## CLI 命令
68
78
 
69
- ### 步骤
79
+ ### `claude-coder auth [url]`
70
80
 
71
- **1. 创建 test.env**
81
+ 一键导出浏览器登录态:
72
82
 
73
83
  ```bash
74
- # .claude-coder/test.env
75
- OPENAI_API_KEY=sk-xxx
76
- ZHIPU_API_KEY=xxx.xxx
77
- TEST_USER_TOKEN=xxx
78
- ```
84
+ # 默认打开 http://localhost:3000
85
+ claude-coder auth
79
86
 
80
- **2. Agent 自动感知**
81
-
82
- 当 `.claude-coder/test.env` 存在时,harness 在编码 session 的 prompt 中注入提示:
87
+ # 指定 URL
88
+ claude-coder auth http://localhost:8080/admin
89
+ ```
83
90
 
84
- > 测试环境变量在 .claude-coder/test.env(含 API Key 等),测试前用 source .claude-coder/test.env 或 export 加载。
91
+ **自动完成**:
92
+ 1. 启动 Playwright 浏览器,用户手动登录后关闭
93
+ 2. 保存 cookies + localStorage 到 `.claude-coder/playwright-auth.json`
94
+ 3. 创建/更新 `.mcp.json`,配置 `--storage-state`
95
+ 4. 添加 `.gitignore` 条目
96
+ 5. 启用 `.claude-coder/.env` 中 `MCP_PLAYWRIGHT=true`
85
97
 
86
- Agent 在执行测试时会自动 `source` 该文件。
98
+ ### `claude-coder setup`(相关)
87
99
 
88
- **3. 安全注意事项**
100
+ 配置模型时可启用 Playwright MCP:
89
101
 
90
- ```gitignore
91
- # .gitignore
92
- .claude-coder/test.env
102
+ ```bash
103
+ claude-coder setup
104
+ # 选择启用 MCP_PLAYWRIGHT=true
93
105
  ```
94
106
 
95
107
  ---
96
108
 
97
- ## 方案 3: project_profile.json 中声明测试依赖
109
+ ## 场景示例
110
+
111
+ ### 场景 1:全栈项目首次测试
112
+
113
+ ```bash
114
+ # 1. 配置模型
115
+ claude-coder setup
116
+
117
+ # 2. 填入后端测试需要的 API Key
118
+ cat >> .claude-coder/test.env << 'EOF'
119
+ OPENAI_API_KEY=sk-xxx
120
+ ZHIPU_API_KEY=xxx.xxx
121
+ EOF
98
122
 
99
- 在扫描阶段或手动编辑 `project_profile.json`,声明哪些测试需要真实 API Key:
123
+ # 3. 导出前端登录态(可选,Agent 也能用 Playwright MCP 自动登录)
124
+ claude-coder auth http://localhost:3000
100
125
 
101
- ```json
102
- {
103
- "test_dependencies": {
104
- "real_api_key": true,
105
- "required_env_vars": ["OPENAI_API_KEY", "ZHIPU_API_KEY"],
106
- "env_file": ".claude-coder/test.env"
107
- }
108
- }
126
+ # 4. 开始自动编码和测试
127
+ claude-coder run
109
128
  ```
110
129
 
111
- Agent Step 5 测试时,如果检测到 `preconditions.real_api_key: true`,会先检查环境变量是否可用,不可用则跳过该测试并标记为 `skip`。
130
+ ### 场景 2:Agent 自主发现并处理凭证缺失
112
131
 
113
- ---
132
+ Agent 在测试 feat-005(AI 内容生成)时发现需要 `OPENAI_API_KEY`:
133
+
134
+ 1. Agent 尝试调用 API → 报错 "API key required"
135
+ 2. Agent **不中断任务**,改用替代验证方式(如 mock 响应、检查代码逻辑是否正确、验证接口可达性)
136
+ 3. Agent 将凭证需求写入 `test.env`:`echo 'OPENAI_API_KEY=需要配置' >> .claude-coder/test.env`
137
+ 4. Agent 在 `session_result.json` 的 notes 中记录:"AI 内容生成功能已实现,但需要真实 OPENAI_API_KEY 才能完整测试,已记录到 test.env"
138
+ 5. Agent 完成其他可验证的步骤后标记任务为 `done`(功能已实现)或 `testing`(等待凭证后完整验证)
139
+
140
+ **核心原则**:缺少凭证不等于任务失败。Agent 应最大化推进,将凭证问题记录为后续补充项,而非阻塞整个 session。
114
141
 
115
- ## 最佳实践
142
+ ### 场景 3:前端 localStorage 配置持久化
116
143
 
117
- | 场景 | 推荐方案 |
118
- |------|----------|
119
- | 需要已登录状态测试页面 | 方案 1 (--storage-state) |
120
- | 需要 API Key 测试后端功能 | 方案 2 (test.env) |
121
- | 需要区分 mock 测试和集成测试 | 方案 3 (profile 声明) |
122
- | 以上组合 | 方案 1 + 2 + 3 |
144
+ 项目的前端将 LLM 服务商配置存储在 localStorage 中:
123
145
 
124
- ### 工作流示例
146
+ ```bash
147
+ # 启动前后端服务
148
+ # 运行 auth,手动在页面中配置 LLM 设置
149
+ claude-coder auth http://localhost:3000
125
150
 
151
+ # playwright-auth.json 中已包含 localStorage 数据
152
+ # 后续 Agent 使用 Playwright MCP 测试时自动加载这些配置
126
153
  ```
127
- 1. claude-coder setup → 配置模型
128
- 2. 创建 .claude-coder/test.env → 填入 API Key
129
- 3. npx playwright codegen ... → 导出登录状态
130
- 4. claude-coder run → Agent 自动使用凭证测试
154
+
155
+ ### 场景 4:cookies 过期后刷新
156
+
157
+ ```bash
158
+ # 重新运行 auth 即可
159
+ claude-coder auth http://localhost:3000
160
+ # 新的 cookies 覆盖旧文件,立即生效
131
161
  ```
package/docs/README.en.md CHANGED
@@ -53,9 +53,10 @@ Each session, the agent autonomously follows 6 steps: restore context → env ch
53
53
  | `claude-coder run --max 1` | Single session (replaces old view mode) |
54
54
  | `claude-coder run --dry-run` | Preview mode |
55
55
  | `claude-coder init` | Initialize project environment |
56
- | `claude-coder add "instruction"` | Append tasks (defaults to opus-class model) |
56
+ | `claude-coder add "instruction"` | Append tasks |
57
57
  | `claude-coder add -r [file]` | Append tasks from requirements file |
58
58
  | `claude-coder add "..." --model M` | Append tasks with specific model |
59
+ | `claude-coder auth [url]` | Export Playwright login state |
59
60
  | `claude-coder validate` | Manually validate last session |
60
61
  | `claude-coder status` | View progress and costs |
61
62
  | `claude-coder config sync` | Sync config to ~/.claude/ |
@@ -84,6 +85,7 @@ your-project/
84
85
  progress.json # Session history + costs
85
86
  tests.json # Verification records
86
87
  test.env # Test credentials (API keys, optional)
88
+ playwright-auth.json # Playwright login state (optional, via auth command)
87
89
  .runtime/ # Temp files (logs)
88
90
  requirements.md # Requirements (optional)
89
91
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Claude Coder — Autonomous coding agent harness powered by Claude Code SDK. Scan, plan, code, validate, git-commit in a loop.",
5
5
  "bin": {
6
6
  "claude-coder": "bin/cli.js"
package/src/auth.js ADDED
@@ -0,0 +1,107 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const { paths, log, getProjectRoot, ensureLoopDir } = require('./config');
7
+
8
+ function updateGitignore(entry) {
9
+ const gitignorePath = path.join(getProjectRoot(), '.gitignore');
10
+ let content = '';
11
+ if (fs.existsSync(gitignorePath)) {
12
+ content = fs.readFileSync(gitignorePath, 'utf8');
13
+ }
14
+ if (content.includes(entry)) return;
15
+
16
+ const suffix = content.endsWith('\n') || content === '' ? '' : '\n';
17
+ fs.appendFileSync(gitignorePath, `${suffix}${entry}\n`, 'utf8');
18
+ log('ok', `.gitignore 已添加: ${entry}`);
19
+ }
20
+
21
+ function updateMcpConfig(authFilePath) {
22
+ const p = paths();
23
+ let mcpConfig = {};
24
+ if (fs.existsSync(p.mcpConfig)) {
25
+ try {
26
+ mcpConfig = JSON.parse(fs.readFileSync(p.mcpConfig, 'utf8'));
27
+ } catch {
28
+ log('warn', '.mcp.json 解析失败,将覆盖');
29
+ }
30
+ }
31
+
32
+ if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
33
+
34
+ const relAuthPath = path.relative(getProjectRoot(), authFilePath);
35
+ mcpConfig.mcpServers.playwright = {
36
+ command: 'npx',
37
+ args: [
38
+ '@playwright/mcp@latest',
39
+ `--storage-state=${relAuthPath}`,
40
+ ],
41
+ };
42
+
43
+ fs.writeFileSync(p.mcpConfig, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
44
+ log('ok', `.mcp.json 已配置 Playwright MCP (storage-state: ${relAuthPath})`);
45
+ }
46
+
47
+ function enableMcpPlaywrightEnv() {
48
+ const p = paths();
49
+ if (!fs.existsSync(p.envFile)) return;
50
+
51
+ let content = fs.readFileSync(p.envFile, 'utf8');
52
+ if (/^MCP_PLAYWRIGHT=/m.test(content)) {
53
+ content = content.replace(/^MCP_PLAYWRIGHT=.*/m, 'MCP_PLAYWRIGHT=true');
54
+ } else {
55
+ const suffix = content.endsWith('\n') ? '' : '\n';
56
+ content += `${suffix}MCP_PLAYWRIGHT=true\n`;
57
+ }
58
+ fs.writeFileSync(p.envFile, content, 'utf8');
59
+ log('ok', '.claude-coder/.env 已设置 MCP_PLAYWRIGHT=true');
60
+ }
61
+
62
+ async function auth(url) {
63
+ ensureLoopDir();
64
+ const p = paths();
65
+ const targetUrl = url || 'http://localhost:3000';
66
+
67
+ log('info', '启动 Playwright 浏览器,请手动登录...');
68
+ log('info', `目标 URL: ${targetUrl}`);
69
+ log('info', `登录状态将保存到: ${p.playwrightAuth}`);
70
+ console.log('');
71
+ console.log('操作步骤:');
72
+ console.log(' 1. 浏览器将自动打开,请手动完成登录');
73
+ console.log(' 2. 登录成功后关闭浏览器窗口');
74
+ console.log(' 3. 登录状态(cookies + localStorage)将自动保存');
75
+ console.log('');
76
+
77
+ try {
78
+ execSync(
79
+ `npx playwright codegen --save-storage="${p.playwrightAuth}" "${targetUrl}"`,
80
+ { stdio: 'inherit', cwd: getProjectRoot() }
81
+ );
82
+ } catch (err) {
83
+ if (!fs.existsSync(p.playwrightAuth)) {
84
+ log('error', `Playwright 登录状态导出失败: ${err.message}`);
85
+ log('info', '请确保已安装 playwright: npx playwright install');
86
+ return;
87
+ }
88
+ }
89
+
90
+ if (!fs.existsSync(p.playwrightAuth)) {
91
+ log('error', '未检测到导出的登录状态文件');
92
+ return;
93
+ }
94
+
95
+ log('ok', '登录状态已保存');
96
+
97
+ updateMcpConfig(p.playwrightAuth);
98
+ updateGitignore('.claude-coder/playwright-auth.json');
99
+ enableMcpPlaywrightEnv();
100
+
101
+ console.log('');
102
+ log('ok', 'Playwright 凭证配置完成!');
103
+ log('info', '后续运行 claude-coder run 时,Agent 的前端测试将自动使用已认证状态');
104
+ log('info', '注意: cookies 有过期时间,需要定期重新运行 claude-coder auth 更新');
105
+ }
106
+
107
+ module.exports = { auth };
package/src/config.js CHANGED
@@ -56,6 +56,8 @@ function paths() {
56
56
  profile: path.join(loopDir, 'project_profile.json'),
57
57
  testsFile: path.join(loopDir, 'tests.json'),
58
58
  testEnvFile: path.join(loopDir, 'test.env'),
59
+ playwrightAuth: path.join(loopDir, 'playwright-auth.json'),
60
+ mcpConfig: path.join(getProjectRoot(), '.mcp.json'),
59
61
  claudeMd: getTemplatePath('CLAUDE.md'),
60
62
  scanProtocol: getTemplatePath('SCAN_PROTOCOL.md'),
61
63
  runtime,
@@ -188,16 +190,6 @@ function syncToGlobal() {
188
190
  log('ok', `已同步配置到 ${settingsPath}`);
189
191
  }
190
192
 
191
- // --------------- Requirements hash ---------------
192
-
193
- function getRequirementsHash() {
194
- const crypto = require('crypto');
195
- const reqFile = path.join(getProjectRoot(), 'requirements.md');
196
- if (!fs.existsSync(reqFile)) return '';
197
- const content = fs.readFileSync(reqFile, 'utf8');
198
- return crypto.createHash('sha256').update(content).digest('hex');
199
- }
200
-
201
193
  module.exports = {
202
194
  COLOR,
203
195
  log,
@@ -211,5 +203,4 @@ module.exports = {
211
203
  buildEnvVars,
212
204
  getAllowedTools,
213
205
  syncToGlobal,
214
- getRequirementsHash,
215
206
  };
package/src/prompts.js CHANGED
@@ -72,9 +72,9 @@ function buildCodingPrompt(sessionNum, opts = {}) {
72
72
  }
73
73
 
74
74
  // Hint 6: Task context (harness pre-read, saves Agent 2-3 Read calls)
75
+ const projectRoot = getProjectRoot();
75
76
  let taskHint = '';
76
77
  try {
77
- const projectRoot = getProjectRoot();
78
78
  const taskData = loadTasks();
79
79
  if (taskData) {
80
80
  const next = findNextTask(taskData);
@@ -89,11 +89,18 @@ function buildCodingPrompt(sessionNum, opts = {}) {
89
89
  }
90
90
  } catch { /* ignore */ }
91
91
 
92
- // Hint 6b: Test environment variables
92
+ // Hint 6b: Test environment variables (readable + writable by Agent)
93
93
  let testEnvHint = '';
94
- const testEnvFile = paths().testEnvFile;
95
- if (testEnvFile && fs.existsSync(testEnvFile)) {
96
- testEnvHint = '测试环境变量在 .claude-coder/test.env(含 API Key 等),测试前用 source .claude-coder/test.env 或 export 加载。';
94
+ if (p.testEnvFile && fs.existsSync(p.testEnvFile)) {
95
+ testEnvHint = `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source ${projectRoot}/.claude-coder/test.env 加载。发现新凭证需求时可追加写入(KEY=value 格式)。`;
96
+ } else {
97
+ testEnvHint = `如需持久化测试凭证(API Key、测试账号密码等),写入 ${projectRoot}/.claude-coder/test.env(KEY=value 格式,每行一个)。后续 session 会自动感知。`;
98
+ }
99
+
100
+ // Hint 6c: Playwright authenticated state
101
+ let playwrightAuthHint = '';
102
+ if (p.playwrightAuth && fs.existsSync(p.playwrightAuth)) {
103
+ playwrightAuthHint = `已检测到 Playwright 登录状态(${projectRoot}/.claude-coder/playwright-auth.json),前端/全栈测试将使用已认证的浏览器会话(含 cookies 和 localStorage)。`;
97
104
  }
98
105
 
99
106
  // Hint 7: Session memory (read flat session_result.json)
@@ -136,6 +143,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
136
143
  envHint,
137
144
  taskHint,
138
145
  testEnvHint,
146
+ playwrightAuthHint,
139
147
  memoryHint,
140
148
  serviceHint,
141
149
  toolGuidance,
package/src/runner.js CHANGED
@@ -183,7 +183,7 @@ async function run(requirement, opts = {}) {
183
183
  ensureLoopDir();
184
184
 
185
185
  const maxSessions = opts.max || 50;
186
- const pauseEvery = opts.pause || 5;
186
+ const pauseEvery = opts.pause ?? 0;
187
187
  const dryRun = opts.dryRun || false;
188
188
 
189
189
  console.log('');
@@ -306,7 +306,11 @@ async function run(requirement, opts = {}) {
306
306
  const validateResult = await validate(headBefore);
307
307
 
308
308
  if (!validateResult.fatal) {
309
- log('ok', `Session ${session} 校验通过`);
309
+ if (validateResult.hasWarnings) {
310
+ log('warn', `Session ${session} 校验通过 (有自动修复或警告)`);
311
+ } else {
312
+ log('ok', `Session ${session} 校验通过`);
313
+ }
310
314
  tryPush();
311
315
  consecutiveFailures = 0;
312
316
 
@@ -369,8 +373,8 @@ async function add(instruction, opts = {}) {
369
373
  if (!opts.model) {
370
374
  if (config.defaultOpus) {
371
375
  opts.model = config.defaultOpus;
372
- } else if (config.provider === 'claude' || !config.baseUrl) {
373
- opts.model = 'claude-sonnet-4-20250514';
376
+ } else if (config.model) {
377
+ opts.model = config.model;
374
378
  }
375
379
  }
376
380
 
package/src/validator.js CHANGED
@@ -9,7 +9,7 @@ function validateSessionResult() {
9
9
 
10
10
  if (!fs.existsSync(p.sessionResult)) {
11
11
  log('error', 'Agent 未生成 session_result.json');
12
- return { valid: false, fatal: true, reason: 'session_result.json 不存在' };
12
+ return { valid: false, fatal: true, recoverable: false, reason: 'session_result.json 不存在' };
13
13
  }
14
14
 
15
15
  let data;
@@ -17,25 +17,30 @@ function validateSessionResult() {
17
17
  data = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
18
18
  } catch (err) {
19
19
  log('error', `session_result.json 解析失败: ${err.message}`);
20
- return { valid: false, fatal: true, reason: `JSON 解析失败: ${err.message}` };
20
+ return { valid: false, fatal: true, recoverable: false, reason: `JSON 解析失败: ${err.message}` };
21
+ }
22
+
23
+ // Backward compat: unwrap legacy { current: {...} } format
24
+ if (data.current && typeof data.current === 'object') {
25
+ data = data.current;
21
26
  }
22
27
 
23
28
  const required = ['session_result', 'status_after'];
24
29
  const missing = required.filter(k => !(k in data));
25
30
  if (missing.length > 0) {
26
- log('error', `session_result.json 缺少字段: ${missing.join(', ')}`);
27
- return { valid: false, fatal: true, reason: `缺少字段: ${missing.join(', ')}` };
31
+ log('warn', `session_result.json 缺少字段: ${missing.join(', ')}`);
32
+ return { valid: false, fatal: false, recoverable: true, reason: `缺少字段: ${missing.join(', ')}` };
28
33
  }
29
34
 
30
35
  if (!['success', 'failed'].includes(data.session_result)) {
31
36
  log('error', `session_result 必须是 success 或 failed,实际是: ${data.session_result}`);
32
- return { valid: false, fatal: true, reason: `无效 session_result: ${data.session_result}` };
37
+ return { valid: false, fatal: true, recoverable: false, reason: `无效 session_result: ${data.session_result}` };
33
38
  }
34
39
 
35
40
  const validStatuses = ['pending', 'in_progress', 'testing', 'done', 'failed'];
36
41
  if (!validStatuses.includes(data.status_after)) {
37
42
  log('error', `status_after 不合法: ${data.status_after}`);
38
- return { valid: false, fatal: true, reason: `无效 status_after: ${data.status_after}` };
43
+ return { valid: false, fatal: true, recoverable: false, reason: `无效 status_after: ${data.status_after}` };
39
44
  }
40
45
 
41
46
  if (!data.task_id) {
@@ -48,7 +53,7 @@ function validateSessionResult() {
48
53
  log('warn', 'session_result.json 合法,但 Agent 报告失败 (failed)');
49
54
  }
50
55
 
51
- return { valid: true, fatal: false, data };
56
+ return { valid: true, fatal: false, recoverable: false, data };
52
57
  }
53
58
 
54
59
  function checkGitProgress(headBefore) {
@@ -107,12 +112,21 @@ function checkTestCoverage() {
107
112
  async function validate(headBefore) {
108
113
  log('info', '========== 开始校验 ==========');
109
114
 
110
- const srResult = validateSessionResult();
115
+ let srResult = validateSessionResult();
111
116
  const gitResult = checkGitProgress(headBefore);
117
+
118
+ // Tiered: has commit + session_result issue → warn, don't rollback good code
119
+ if (srResult.recoverable && gitResult.hasCommit) {
120
+ log('warn', 'session_result.json 格式异常,但有新提交,降级为警告(不回滚代码)');
121
+ } else if (srResult.recoverable && !gitResult.hasCommit) {
122
+ log('error', '无新提交且 session_result.json 格式错误,视为致命');
123
+ srResult.fatal = true;
124
+ }
125
+
112
126
  checkTestCoverage();
113
127
 
114
128
  const fatal = srResult.fatal;
115
- const hasWarnings = gitResult.warning;
129
+ const hasWarnings = gitResult.warning || srResult.recoverable;
116
130
 
117
131
  if (fatal) {
118
132
  log('error', '========== 校验失败 (致命) ==========');
@@ -50,6 +50,8 @@
50
50
  | `.claude-coder/progress.json` | 跨会话记忆日志(外部循环自动维护) | 只读 |
51
51
  | `.claude-coder/session_result.json` | 本次会话的结构化输出 | 每次会话结束时覆盖写入 |
52
52
  | `.claude-coder/tests.json` | 功能验证记录(轻量) | 可新增和更新;仅当功能涉及 API 或核心逻辑时记录 |
53
+ | `.claude-coder/test.env` | 测试凭证(API Key、测试账号等) | **可追加写入**;发现测试需要的凭证时持久化到此文件 |
54
+ | `.claude-coder/playwright-auth.json` | 浏览器登录状态(cookies + localStorage) | 只读;由用户通过 `claude-coder auth` 预配置 |
53
55
 
54
56
  ### requirements.md 处理原则
55
57
 
@@ -232,6 +234,8 @@ pending ──→ in_progress ──→ testing ──→ done
232
234
 
233
235
  6. **记录验证命令**:如果本功能涉及 API 或核心逻辑,在 `tests.json` 中追加一条记录(含 `last_run_session` 为当前 session 编号)。纯配置 / 纯样式 / 改动 < 100 行的任务无需记录
234
236
 
237
+ 7. **凭证持久化**:测试中发现需要的凭证(API Key、测试账号密码等),追加写入 `.claude-coder/test.env`,格式为 `KEY=value`(每行一个)。后续 session 会自动感知该文件。确保 `test.env` 已在 `.gitignore` 中(不被 git 追踪)
238
+
235
239
  **禁止**:
236
240
  - 后端任务启动浏览器测试
237
241
  - 创建独立测试文件(`test-*.js` / `test-*.html`)