claude-coder 1.0.2 → 1.0.3

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 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
- await runner.run(positional[0] || null, opts);
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': {
@@ -278,7 +278,65 @@ sequenceDiagram
278
278
 
279
279
  ---
280
280
 
281
- ## 9. 后续优化方向
281
+ ## 9. Claude Agent SDK V1/V2 对比与迁移计划
282
+
283
+ 当前使用 **V1 稳定 API**(`query()`),V2 为 preview 状态(`unstable_` 前缀)。
284
+
285
+ ### V1 vs V2 API 对比
286
+
287
+ | 维度 | V1 `query()` | V2 `send()/stream()` |
288
+ |------|-------------|---------------------|
289
+ | **状态** | 稳定,生产可用 | `unstable_` 前缀,preview |
290
+ | **入口函数** | `query({ prompt, options })` | `unstable_v2_createSession(opts)` / `unstable_v2_prompt()` |
291
+ | **多轮会话** | 需手动管理 AsyncGenerator | `session.send()` + `session.stream()`,更简洁 |
292
+ | **会话恢复** | `options.resume: sessionId` | `unstable_v2_resumeSession(id)` |
293
+ | **Hooks** | `options.hooks: { PreToolUse, PostToolUse, ... }` | 未支持 |
294
+ | **Subagents** | `options.agents: { name: AgentDefinition }` | 未支持 |
295
+ | **Session Fork** | `options.forkSession: true` | 未支持 |
296
+ | **Plugins** | `options.plugins: [{ type, path }]` | 未支持 |
297
+ | **结构化输出** | `options.outputFormat: { type: 'json_schema', schema }` | 支持 |
298
+ | **文件检查点** | `options.enableFileCheckpointing + rewindFiles()` | 未明确 |
299
+ | **Cost Tracking** | `SDKResultMessage.total_cost_usd` | `SDKResultMessage.total_cost_usd` |
300
+ | **权限控制** | `canUseTool`, `permissionMode`, `allowedTools`, `disallowedTools` | 继承 |
301
+
302
+ ### 当前实现使用的 V1 特性
303
+
304
+ ```javascript
305
+ query({
306
+ prompt,
307
+ options: {
308
+ systemPrompt, // 注入 CLAUDE.md
309
+ allowedTools, // 工具白名单
310
+ permissionMode: 'bypassPermissions',
311
+ allowDangerouslySkipPermissions: true,
312
+ model, // 从 .env 传入
313
+ env, // 环境变量透传
314
+ settingSources: ['project'], // 加载项目 CLAUDE.md
315
+ hooks: { PreToolUse: [...] }, // 实时 spinner 监控
316
+ }
317
+ })
318
+ ```
319
+
320
+ ### V2 迁移条件(等待稳定后)
321
+
322
+ 1. V2 去掉 `unstable_` 前缀,正式发布
323
+ 2. V2 支持 Hooks(当前项目依赖 PreToolUse 做 spinner 和 activity log)
324
+ 3. V2 支持 Subagents(未来可能用于扫描 Agent / 编码 Agent 分离)
325
+
326
+ ### 可利用但尚未使用的 V1 特性
327
+
328
+ | 特性 | 说明 | 优先级 |
329
+ |------|------|--------|
330
+ | `maxBudgetUsd` | SDK 内置成本上限,替代自研追踪 | P0 |
331
+ | `effort` | 控制思考深度(`low`/`medium`/`high`/`max`) | P1 |
332
+ | `enableFileCheckpointing` | 文件操作检查点,比 git reset 更精细 | P1 |
333
+ | `outputFormat` | 结构化输出,让 Agent 直接输出 JSON 格式 | P1 |
334
+ | `agents` | 定义子 Agent,不同模型/工具集 | P2 |
335
+ | `betas` | 扩展上下文窗口 | P2 |
336
+
337
+ ---
338
+
339
+ ## 10. 后续优化方向
282
340
 
283
341
  ### P0 — 近期
284
342
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-coder",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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/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
  }
@@ -106,7 +107,7 @@ async function runCodingSession(sessionNum, opts = {}) {
106
107
  const collected = [];
107
108
  for await (const message of session) {
108
109
  collected.push(message);
109
- logMessage(message, logStream);
110
+ logMessage(message, logStream, indicator);
110
111
  }
111
112
 
112
113
  logStream.end();
@@ -168,7 +169,7 @@ async function runScanSession(requirement, opts = {}) {
168
169
  const collected = [];
169
170
  for await (const message of session) {
170
171
  collected.push(message);
171
- logMessage(message, logStream);
172
+ logMessage(message, logStream, indicator);
172
173
  }
173
174
 
174
175
  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 || [];