claude-coder 1.1.0 → 1.2.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,8 +53,9 @@ 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 "指令"` | 追加任务 |
56
+ | `claude-coder add "指令"` | 追加任务(默认用 opus 级模型推理) |
57
57
  | `claude-coder add -r [file]` | 从需求文件追加任务 |
58
+ | `claude-coder add "..." --model M` | 指定模型追加任务 |
58
59
  | `claude-coder validate` | 手动校验 |
59
60
  | `claude-coder status` | 查看进度和成本 |
60
61
  | `claude-coder config sync` | 同步配置到 ~/.claude/ |
@@ -92,7 +93,8 @@ your-project/
92
93
  session_result.json # 上次 session 结果(扁平)
93
94
  progress.json # 会话历史 + 成本
94
95
  tests.json # 验证记录
95
- .runtime/ # 临时文件
96
+ test.env # 测试凭证(API Key 等,可选)
97
+ .runtime/ # 临时文件(含日志)
96
98
  requirements.md # 需求文档(可选)
97
99
  ```
98
100
 
@@ -109,6 +111,7 @@ your-project/
109
111
  ## 文档
110
112
 
111
113
  - [技术架构](docs/ARCHITECTURE.md) — 模块职责、提示语注入架构、注意力机制、Hook 数据流、后续优化方向
114
+ - [Playwright 凭证持久化](docs/PLAYWRIGHT_CREDENTIALS.md) — 测试 cookies 和 API Key 管理方案
112
115
 
113
116
  ## License
114
117
 
package/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ const COMMANDS = {
7
7
  run: { desc: '自动编码循环', usage: 'claude-coder run [需求] [--max N] [--pause N] [--dry-run]' },
8
8
  setup: { desc: '交互式模型配置', usage: 'claude-coder setup' },
9
9
  init: { desc: '初始化项目环境', usage: 'claude-coder init' },
10
- add: { desc: '追加任务到 tasks.json', usage: 'claude-coder add "指令" | add -r [file]' },
10
+ add: { desc: '追加任务到 tasks.json', usage: 'claude-coder add "指令" [--model M] | add -r [file]' },
11
11
  validate: { desc: '手动校验上次 session', usage: 'claude-coder validate' },
12
12
  status: { desc: '查看任务进度和成本', usage: 'claude-coder status' },
13
13
  config: { desc: '配置管理', usage: 'claude-coder config sync' },
@@ -28,6 +28,7 @@ function showHelp() {
28
28
  console.log(' claude-coder run --dry-run 预览模式');
29
29
  console.log(' claude-coder add "新增搜索功能" 追加任务');
30
30
  console.log(' claude-coder add -r 从 requirements.md 追加任务');
31
+ console.log(' claude-coder add "..." --model opus-4 指定模型追加任务');
31
32
  console.log(' claude-coder status 查看进度和成本');
32
33
  console.log(`\n前置条件: npm install -g @anthropic-ai/claude-agent-sdk`);
33
34
  }
@@ -35,7 +36,7 @@ function showHelp() {
35
36
  function parseArgs(argv) {
36
37
  const args = argv.slice(2);
37
38
  const command = args[0];
38
- const opts = { max: 50, pause: 0, dryRun: false, readFile: null };
39
+ const opts = { max: 50, pause: 0, dryRun: false, readFile: null, model: null };
39
40
  const positional = [];
40
41
 
41
42
  for (let i = 1; i < args.length; i++) {
@@ -49,6 +50,9 @@ function parseArgs(argv) {
49
50
  case '--dry-run':
50
51
  opts.dryRun = true;
51
52
  break;
53
+ case '--model':
54
+ opts.model = args[++i] || null;
55
+ break;
52
56
  case '-r': {
53
57
  const next = args[i + 1];
54
58
  if (next && !next.startsWith('-')) {
@@ -183,11 +183,11 @@ flowchart TB
183
183
 
184
184
  | Session 类型 | systemPrompt | user prompt | 触发条件 |
185
185
  |---|---|---|---|
186
- | **编码** | CLAUDE.md | `buildCodingPrompt()` + 9 个条件 hint | 主循环每次迭代 |
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 的 9 个条件 Hint
190
+ ### 编码 Session 的 10 个条件 Hint
191
191
 
192
192
  | # | Hint | 触发条件 | 影响 |
193
193
  |---|---|---|---|
@@ -196,7 +196,8 @@ flowchart TB
196
196
  | 3 | `envHint` | 连续成功且 session>1 | Step 2:跳过 init |
197
197
  | 4 | `testHint` | tests.json 有记录 | Step 5:避免重复验证 |
198
198
  | 5 | `docsHint` | profile.existing_docs 非空或 profile 有缺陷 | Step 4:读文档后再编码;profile 缺陷时提示 Agent 在 Step 6 补全 services/docs |
199
- | 6 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 + .claude-coder/ 路径提示 |
199
+ | 6 | `taskHint` | tasks.json 存在且有待办任务 | Step 1:跳过读取 tasks.json,harness 已注入当前任务上下文 + 项目绝对路径 |
200
+ | 6b | `testEnvHint` | .claude-coder/test.env 存在 | Step 5:提示 Agent 在测试前加载测试环境变量 |
200
201
  | 7 | `memoryHint` | session_result.json 存在(扁平格式) | Step 1:跳过读取 session_result.json,harness 已注入上次会话摘要 |
201
202
  | 8 | `serviceHint` | 始终注入 | Step 6:单次模式停止服务,连续模式保持服务运行 |
202
203
  | 9 | `toolGuidance` | 始终注入 | 全局:工具使用规范(Grep/Glob/Read/LS/MultiEdit/Task 替代 bash 命令),非 Claude 模型必需 |
@@ -267,7 +268,7 @@ sequenceDiagram
267
268
  | 维度 | 评分 | 说明 |
268
269
  |------|------|------|
269
270
  | **CLAUDE.md 系统提示** | 8/10 | U 型注意力设计;铁律清晰;状态机和 6 步流程是核心竞争力 |
270
- | **动态 prompt** | 9/10 | 9 个条件 hint 精准注入,含 task/memory 上下文注入 + 服务管理 + 工具使用指导,减少 Agent 冗余操作 |
271
+ | **动态 prompt** | 9/10 | 10 个条件 hint 精准注入,含 task/memory 上下文注入 + cwd 路径 + test.env + 服务管理 + 工具使用指导,减少 Agent 冗余操作 |
271
272
  | **SCAN_PROTOCOL.md** | 8.5/10 | 新旧项目分支完整,profile 格式全面 |
272
273
  | **tests.json 设计** | 7.5/10 | 精简字段,核心目的(防反复测试)明确 |
273
274
  | **注入时机** | 9/10 | 静态规则 vs 动态上下文分离干净 |
@@ -0,0 +1,131 @@
1
+ # Playwright MCP 凭证持久化方案
2
+
3
+ ## 背景
4
+
5
+ 在使用 claude-coder 运行涉及前端测试的任务时,Playwright MCP 可能需要:
6
+ 1. 已登录状态的 cookies(如后台管理页面)
7
+ 2. API Key 等测试凭证(如 AI 生成功能需要真实 API 调用)
8
+
9
+ 本文档描述如何在 claude-coder 工作流中管理这些凭证。
10
+
11
+ ---
12
+
13
+ ## 方案 1: Playwright --storage-state(推荐用于 cookies)
14
+
15
+ ### 原理
16
+
17
+ `@playwright/mcp` 支持 `--storage-state=<path>` 参数,加载预存的浏览器状态(cookies、localStorage)。
18
+
19
+ ### 步骤
20
+
21
+ **1. 手动登录并导出状态**
22
+
23
+ ```bash
24
+ # 启动 Playwright,手动登录后导出
25
+ npx playwright codegen --save-storage=.claude-coder/playwright-auth.json http://localhost:3000
26
+ ```
27
+
28
+ 登录完成后关闭浏览器,状态自动保存到 `playwright-auth.json`。
29
+
30
+ **2. 配置 MCP 使用保存的状态**
31
+
32
+ 在项目的 `.mcp.json`(Claude Code MCP 配置)中:
33
+
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
+ ```
47
+
48
+ **3. 安全注意事项**
49
+
50
+ ```gitignore
51
+ # .gitignore
52
+ .claude-coder/playwright-auth.json
53
+ ```
54
+
55
+ ### 注意
56
+
57
+ - 状态文件包含敏感 cookies,必须加入 `.gitignore`
58
+ - cookies 有过期时间,需要定期重新导出
59
+ - `--storage-state` 与 `--isolated` 模式配合使用效果最佳
60
+
61
+ ---
62
+
63
+ ## 方案 2: test.env(推荐用于 API Key)
64
+
65
+ ### 原理
66
+
67
+ 在 `.claude-coder/test.env` 中存放测试专用的环境变量(如 API Key)。claude-coder 会自动检测此文件存在,并通过 Hint 提示 Agent 在测试前加载它。
68
+
69
+ ### 步骤
70
+
71
+ **1. 创建 test.env**
72
+
73
+ ```bash
74
+ # .claude-coder/test.env
75
+ OPENAI_API_KEY=sk-xxx
76
+ ZHIPU_API_KEY=xxx.xxx
77
+ TEST_USER_TOKEN=xxx
78
+ ```
79
+
80
+ **2. Agent 自动感知**
81
+
82
+ 当 `.claude-coder/test.env` 存在时,harness 在编码 session 的 prompt 中注入提示:
83
+
84
+ > 测试环境变量在 .claude-coder/test.env(含 API Key 等),测试前用 source .claude-coder/test.env 或 export 加载。
85
+
86
+ Agent 在执行测试时会自动 `source` 该文件。
87
+
88
+ **3. 安全注意事项**
89
+
90
+ ```gitignore
91
+ # .gitignore
92
+ .claude-coder/test.env
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 方案 3: project_profile.json 中声明测试依赖
98
+
99
+ 在扫描阶段或手动编辑 `project_profile.json`,声明哪些测试需要真实 API Key:
100
+
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
+ }
109
+ ```
110
+
111
+ Agent 在 Step 5 测试时,如果检测到 `preconditions.real_api_key: true`,会先检查环境变量是否可用,不可用则跳过该测试并标记为 `skip`。
112
+
113
+ ---
114
+
115
+ ## 最佳实践
116
+
117
+ | 场景 | 推荐方案 |
118
+ |------|----------|
119
+ | 需要已登录状态测试页面 | 方案 1 (--storage-state) |
120
+ | 需要 API Key 测试后端功能 | 方案 2 (test.env) |
121
+ | 需要区分 mock 测试和集成测试 | 方案 3 (profile 声明) |
122
+ | 以上组合 | 方案 1 + 2 + 3 |
123
+
124
+ ### 工作流示例
125
+
126
+ ```
127
+ 1. claude-coder setup → 配置模型
128
+ 2. 创建 .claude-coder/test.env → 填入 API Key
129
+ 3. npx playwright codegen ... → 导出登录状态
130
+ 4. claude-coder run → Agent 自动使用凭证测试
131
+ ```
package/docs/README.en.md CHANGED
@@ -53,8 +53,9 @@ 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 |
56
+ | `claude-coder add "instruction"` | Append tasks (defaults to opus-class model) |
57
57
  | `claude-coder add -r [file]` | Append tasks from requirements file |
58
+ | `claude-coder add "..." --model M` | Append tasks with specific model |
58
59
  | `claude-coder validate` | Manually validate last session |
59
60
  | `claude-coder status` | View progress and costs |
60
61
  | `claude-coder config sync` | Sync config to ~/.claude/ |
@@ -82,13 +83,15 @@ your-project/
82
83
  session_result.json # Last session result (flat)
83
84
  progress.json # Session history + costs
84
85
  tests.json # Verification records
85
- .runtime/ # Temp files
86
+ test.env # Test credentials (API keys, optional)
87
+ .runtime/ # Temp files (logs)
86
88
  requirements.md # Requirements (optional)
87
89
  ```
88
90
 
89
91
  ## Documentation
90
92
 
91
93
  - [Architecture](ARCHITECTURE.md) — Module responsibilities, prompt injection architecture, attention mechanism, hook data flow, future roadmap
94
+ - [Playwright Credentials](PLAYWRIGHT_CREDENTIALS.md) — Test cookies and API key management
92
95
 
93
96
  ## License
94
97
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.1.0",
3
+ "version": "1.2.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/config.js CHANGED
@@ -55,6 +55,7 @@ function paths() {
55
55
  sessionResult: path.join(loopDir, 'session_result.json'),
56
56
  profile: path.join(loopDir, 'project_profile.json'),
57
57
  testsFile: path.join(loopDir, 'tests.json'),
58
+ testEnvFile: path.join(loopDir, 'test.env'),
58
59
  claudeMd: getTemplatePath('CLAUDE.md'),
59
60
  scanProtocol: getTemplatePath('SCAN_PROTOCOL.md'),
60
61
  runtime,
package/src/prompts.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
- const { paths, loadConfig } = require('./config');
4
+ const { paths, loadConfig, getProjectRoot } = require('./config');
5
5
  const { loadTasks, findNextTask, getStats } = require('./tasks');
6
6
 
7
7
  /**
@@ -74,6 +74,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
74
74
  // Hint 6: Task context (harness pre-read, saves Agent 2-3 Read calls)
75
75
  let taskHint = '';
76
76
  try {
77
+ const projectRoot = getProjectRoot();
77
78
  const taskData = loadTasks();
78
79
  if (taskData) {
79
80
  const next = findNextTask(taskData);
@@ -82,12 +83,19 @@ function buildCodingPrompt(sessionNum, opts = {}) {
82
83
  taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
83
84
  `category=${next.category}, steps=${next.steps.length}步。` +
84
85
  `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
85
- `运行时目录: .claude-coder/(隐藏目录,ls -a 可见,所有 tasks.json/profile 等文件均在此目录下)。` +
86
+ `项目绝对路径: ${projectRoot}。运行时目录: ${projectRoot}/.claude-coder/(隐藏目录)。` +
86
87
  `第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
87
88
  }
88
89
  }
89
90
  } catch { /* ignore */ }
90
91
 
92
+ // Hint 6b: Test environment variables
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 加载。';
97
+ }
98
+
91
99
  // Hint 7: Session memory (read flat session_result.json)
92
100
  let memoryHint = '';
93
101
  if (fs.existsSync(p.sessionResult)) {
@@ -127,6 +135,7 @@ function buildCodingPrompt(sessionNum, opts = {}) {
127
135
  docsHint,
128
136
  envHint,
129
137
  taskHint,
138
+ testEnvHint,
130
139
  memoryHint,
131
140
  serviceHint,
132
141
  toolGuidance,
package/src/runner.js CHANGED
@@ -77,17 +77,38 @@ function killServicesByProfile() {
77
77
  } catch { /* ignore profile read errors */ }
78
78
  }
79
79
 
80
+ function sleepSync(ms) {
81
+ const end = Date.now() + ms;
82
+ while (Date.now() < end) { /* busy wait */ }
83
+ }
84
+
80
85
  function rollback(headBefore, reason) {
81
86
  if (!headBefore || headBefore === 'none') return;
82
87
 
83
88
  killServicesByProfile();
84
89
 
90
+ if (process.platform === 'win32') sleepSync(1500);
91
+
92
+ const cwd = getProjectRoot();
93
+ const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
94
+
85
95
  log('warn', `回滚到 ${headBefore} ...`);
86
- try {
87
- execSync(`git reset --hard ${headBefore}`, { cwd: getProjectRoot(), stdio: 'pipe' });
88
- log('ok', '回滚完成');
89
- } catch (err) {
90
- log('error', `回滚失败: ${err.message}`);
96
+
97
+ let success = false;
98
+ for (let attempt = 1; attempt <= 2; attempt++) {
99
+ try {
100
+ execSync(`git reset --hard ${headBefore}`, { cwd, stdio: 'pipe', env: gitEnv });
101
+ log('ok', '回滚完成');
102
+ success = true;
103
+ break;
104
+ } catch (err) {
105
+ if (attempt === 1) {
106
+ log('warn', `回滚首次失败,等待后重试: ${err.message}`);
107
+ sleepSync(2000);
108
+ } else {
109
+ log('error', `回滚失败: ${err.message}`);
110
+ }
111
+ }
91
112
  }
92
113
 
93
114
  appendProgress({
@@ -95,6 +116,7 @@ function rollback(headBefore, reason) {
95
116
  timestamp: new Date().toISOString(),
96
117
  reason: reason || 'harness 校验失败',
97
118
  rollbackTo: headBefore,
119
+ success,
98
120
  });
99
121
  }
100
122
 
@@ -267,10 +289,13 @@ async function run(requirement, opts = {}) {
267
289
  }
268
290
 
269
291
  const headBefore = getHead();
292
+ const nextTask = findNextTask(taskData);
293
+ const taskId = nextTask?.id || 'unknown';
270
294
 
271
295
  // Run coding session
272
296
  const sessionResult = await runCodingSession(session, {
273
297
  projectRoot,
298
+ taskId,
274
299
  consecutiveFailures,
275
300
  maxSessions,
276
301
  lastValidateLog: consecutiveFailures > 0 ? '上次校验失败' : '',
@@ -340,10 +365,18 @@ async function add(instruction, opts = {}) {
340
365
  ensureLoopDir();
341
366
 
342
367
  const config = loadConfig();
343
- if (config.provider !== 'claude' && config.baseUrl) {
344
- log('ok', `模型配置已加载: ${config.provider}${config.model ? ` (${config.model})` : ''}`);
368
+
369
+ if (!opts.model) {
370
+ if (config.defaultOpus) {
371
+ opts.model = config.defaultOpus;
372
+ } else if (config.provider === 'claude' || !config.baseUrl) {
373
+ opts.model = 'claude-sonnet-4-20250514';
374
+ }
345
375
  }
346
376
 
377
+ const displayModel = opts.model || config.model || '(default)';
378
+ log('ok', `模型配置已加载: ${config.provider || 'claude'} (add 使用: ${displayModel})`);
379
+
347
380
  if (!fs.existsSync(p.profile) || !fs.existsSync(p.tasksFile)) {
348
381
  log('error', 'add 需要先完成初始化(至少运行一次 claude-coder run)');
349
382
  process.exit(1);
package/src/session.js CHANGED
@@ -51,7 +51,8 @@ function buildQueryOptions(config, opts = {}) {
51
51
  env: buildEnvVars(config),
52
52
  settingSources: ['project'],
53
53
  };
54
- if (config.model) base.model = config.model;
54
+ if (opts.model) base.model = opts.model;
55
+ else if (config.model) base.model = config.model;
55
56
  return base;
56
57
  }
57
58
 
@@ -62,6 +63,10 @@ function extractResult(messages) {
62
63
  return null;
63
64
  }
64
65
 
66
+ function stripAnsi(str) {
67
+ return str.replace(/\x1b\[[0-9;]*m/g, '');
68
+ }
69
+
65
70
  function logMessage(message, logStream, indicator) {
66
71
  if (message.type === 'assistant' && message.message?.content) {
67
72
  for (const block of message.message.content) {
@@ -70,6 +75,9 @@ function logMessage(message, logStream, indicator) {
70
75
  const statusLine = indicator.getStatusLine();
71
76
  process.stderr.write('\r\x1b[K');
72
77
  if (statusLine) process.stderr.write(statusLine + '\n');
78
+ if (logStream && statusLine) {
79
+ logStream.write('\n' + stripAnsi(statusLine) + '\n');
80
+ }
73
81
  }
74
82
  process.stdout.write(block.text);
75
83
  if (logStream) logStream.write(block.text);
@@ -88,7 +96,9 @@ async function runCodingSession(sessionNum, opts = {}) {
88
96
  const systemPrompt = buildSystemPrompt(false);
89
97
 
90
98
  const p = paths();
91
- const logFile = path.join(p.logsDir, `session_${sessionNum}_${Date.now()}.log`);
99
+ const taskId = opts.taskId || 'unknown';
100
+ const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
101
+ const logFile = path.join(p.logsDir, `${taskId}_session_${sessionNum}_${dateStr}.log`);
92
102
  const logStream = fs.createWriteStream(logFile, { flags: 'a' });
93
103
 
94
104
  indicator.start(sessionNum);
@@ -164,7 +174,7 @@ async function runScanSession(requirement, opts = {}) {
164
174
  const systemPrompt = buildSystemPrompt(true);
165
175
 
166
176
  const p = paths();
167
- const logFile = path.join(p.logsDir, `scan_${Date.now()}.log`);
177
+ const logFile = path.join(p.logsDir, `scan_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}.log`);
168
178
  const logStream = fs.createWriteStream(logFile, { flags: 'a' });
169
179
 
170
180
  indicator.start(0);
@@ -218,7 +228,7 @@ async function runAddSession(instruction, opts = {}) {
218
228
  const prompt = buildAddPrompt(instruction);
219
229
 
220
230
  const p = paths();
221
- const logFile = path.join(p.logsDir, `add_tasks_${Date.now()}.log`);
231
+ const logFile = path.join(p.logsDir, `add_tasks_${new Date().toISOString().slice(0, 10).replace(/-/g, '')}.log`);
222
232
  const logStream = fs.createWriteStream(logFile, { flags: 'a' });
223
233
 
224
234
  indicator.start(0);