claude-coder 1.0.1 → 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 +8 -1
- package/docs/ARCHITECTURE.md +59 -1
- package/package.json +1 -1
- package/src/runner.js +27 -13
- package/src/session.js +115 -100
- package/src/tasks.js +7 -1
- package/src/validator.js +7 -3
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
|
@@ -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
package/src/runner.js
CHANGED
|
@@ -12,14 +12,28 @@ const { runCodingSession, runViewSession, runAddSession } = require('./session')
|
|
|
12
12
|
|
|
13
13
|
const MAX_RETRY = 3;
|
|
14
14
|
|
|
15
|
-
function requireSdk() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
async function requireSdk() {
|
|
16
|
+
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
17
|
+
const attempts = [
|
|
18
|
+
() => { require.resolve(pkgName); return true; },
|
|
19
|
+
() => {
|
|
20
|
+
const { createRequire } = require('module');
|
|
21
|
+
createRequire(__filename).resolve(pkgName);
|
|
22
|
+
return true;
|
|
23
|
+
},
|
|
24
|
+
() => {
|
|
25
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
|
|
26
|
+
const sdkPath = path.join(prefix, 'lib', 'node_modules', pkgName);
|
|
27
|
+
if (fs.existsSync(sdkPath)) return true;
|
|
28
|
+
throw new Error('not found');
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
for (const attempt of attempts) {
|
|
32
|
+
try { if (attempt()) return; } catch { /* try next */ }
|
|
22
33
|
}
|
|
34
|
+
console.error(`错误:未找到 ${pkgName}`);
|
|
35
|
+
console.error(`请先安装:npm install -g ${pkgName}`);
|
|
36
|
+
process.exit(1);
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
function getHead() {
|
|
@@ -86,7 +100,7 @@ function appendProgress(entry) {
|
|
|
86
100
|
const p = paths();
|
|
87
101
|
let progress = { sessions: [] };
|
|
88
102
|
if (fs.existsSync(p.progressFile)) {
|
|
89
|
-
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 */ }
|
|
90
104
|
}
|
|
91
105
|
if (!Array.isArray(progress.sessions)) progress.sessions = [];
|
|
92
106
|
progress.sessions.push(entry);
|
|
@@ -97,7 +111,7 @@ function updateSessionHistory(sessionData, sessionNum) {
|
|
|
97
111
|
const p = paths();
|
|
98
112
|
let sr = { current: null, history: [] };
|
|
99
113
|
if (fs.existsSync(p.sessionResult)) {
|
|
100
|
-
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 */ }
|
|
101
115
|
if (!sr.history && sr.session_result) {
|
|
102
116
|
sr = { current: sr, history: [] };
|
|
103
117
|
}
|
|
@@ -199,7 +213,7 @@ async function run(requirement, opts = {}) {
|
|
|
199
213
|
return;
|
|
200
214
|
}
|
|
201
215
|
|
|
202
|
-
requireSdk();
|
|
216
|
+
await requireSdk();
|
|
203
217
|
const scanResult = await scan(requirement, { projectRoot });
|
|
204
218
|
if (!scanResult.success) {
|
|
205
219
|
console.log('');
|
|
@@ -215,7 +229,7 @@ async function run(requirement, opts = {}) {
|
|
|
215
229
|
}
|
|
216
230
|
|
|
217
231
|
// Coding loop
|
|
218
|
-
if (!dryRun) requireSdk();
|
|
232
|
+
if (!dryRun) await requireSdk();
|
|
219
233
|
log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
|
|
220
234
|
console.log('');
|
|
221
235
|
|
|
@@ -319,7 +333,7 @@ async function run(requirement, opts = {}) {
|
|
|
319
333
|
}
|
|
320
334
|
|
|
321
335
|
async function view(requirement, opts = {}) {
|
|
322
|
-
requireSdk();
|
|
336
|
+
await requireSdk();
|
|
323
337
|
const projectRoot = getProjectRoot();
|
|
324
338
|
ensureLoopDir();
|
|
325
339
|
|
|
@@ -331,7 +345,7 @@ async function view(requirement, opts = {}) {
|
|
|
331
345
|
}
|
|
332
346
|
|
|
333
347
|
async function add(instruction, opts = {}) {
|
|
334
|
-
requireSdk();
|
|
348
|
+
await requireSdk();
|
|
335
349
|
const p = paths();
|
|
336
350
|
const projectRoot = getProjectRoot();
|
|
337
351
|
ensureLoopDir();
|
package/src/session.js
CHANGED
|
@@ -6,12 +6,76 @@ const { paths, loadConfig, buildEnvVars, getAllowedTools, log } = require('./con
|
|
|
6
6
|
const { Indicator, inferPhaseStep } = require('./indicator');
|
|
7
7
|
const { buildSystemPrompt, buildCodingPrompt, buildScanPrompt, buildViewPrompt, buildAddPrompt } = require('./prompts');
|
|
8
8
|
|
|
9
|
+
let _sdkModule = null;
|
|
10
|
+
async function loadSDK() {
|
|
11
|
+
if (_sdkModule) return _sdkModule;
|
|
12
|
+
|
|
13
|
+
const pkgName = '@anthropic-ai/claude-agent-sdk';
|
|
14
|
+
const attempts = [
|
|
15
|
+
() => import(pkgName),
|
|
16
|
+
() => {
|
|
17
|
+
const { createRequire } = require('module');
|
|
18
|
+
const resolved = createRequire(__filename).resolve(pkgName);
|
|
19
|
+
return import(resolved);
|
|
20
|
+
},
|
|
21
|
+
() => {
|
|
22
|
+
const { execSync } = require('child_process');
|
|
23
|
+
const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim();
|
|
24
|
+
const sdkPath = path.join(prefix, 'lib', 'node_modules', pkgName, 'sdk.mjs');
|
|
25
|
+
return import(sdkPath);
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const attempt of attempts) {
|
|
30
|
+
try {
|
|
31
|
+
_sdkModule = await attempt();
|
|
32
|
+
return _sdkModule;
|
|
33
|
+
} catch { /* try next */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log('error', `未找到 ${pkgName}`);
|
|
37
|
+
log('error', `请先安装:npm install -g ${pkgName}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
9
41
|
function applyEnvConfig(config) {
|
|
10
42
|
Object.assign(process.env, buildEnvVars(config));
|
|
11
43
|
}
|
|
12
44
|
|
|
45
|
+
function buildQueryOptions(config, opts = {}) {
|
|
46
|
+
const base = {
|
|
47
|
+
allowedTools: getAllowedTools(config),
|
|
48
|
+
permissionMode: 'bypassPermissions',
|
|
49
|
+
allowDangerouslySkipPermissions: true,
|
|
50
|
+
cwd: opts.projectRoot || process.cwd(),
|
|
51
|
+
env: buildEnvVars(config),
|
|
52
|
+
settingSources: ['project'],
|
|
53
|
+
};
|
|
54
|
+
if (config.model) base.model = config.model;
|
|
55
|
+
return base;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractResult(messages) {
|
|
59
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
60
|
+
if (messages[i].type === 'result') return messages[i];
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function logMessage(message, logStream, indicator) {
|
|
66
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
67
|
+
for (const block of message.message.content) {
|
|
68
|
+
if (block.type === 'text' && block.text) {
|
|
69
|
+
if (indicator) process.stderr.write('\r\x1b[K');
|
|
70
|
+
process.stdout.write(block.text);
|
|
71
|
+
if (logStream) logStream.write(block.text);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
13
77
|
async function runCodingSession(sessionNum, opts = {}) {
|
|
14
|
-
const
|
|
78
|
+
const sdk = await loadSDK();
|
|
15
79
|
const config = loadConfig();
|
|
16
80
|
applyEnvConfig(config);
|
|
17
81
|
const indicator = new Indicator();
|
|
@@ -26,46 +90,34 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
26
90
|
indicator.start(sessionNum);
|
|
27
91
|
|
|
28
92
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
inferPhaseStep(indicator, event.tool_name, event.tool_input);
|
|
43
|
-
return { decision: 'allow' };
|
|
44
|
-
}
|
|
45
|
-
}]
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
93
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
94
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
95
|
+
queryOpts.hooks = {
|
|
96
|
+
PreToolUse: [{
|
|
97
|
+
matcher: '*',
|
|
98
|
+
hooks: [async (input) => {
|
|
99
|
+
inferPhaseStep(indicator, input.tool_name, input.tool_input);
|
|
100
|
+
return {};
|
|
101
|
+
}]
|
|
102
|
+
}]
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
49
106
|
|
|
50
|
-
|
|
107
|
+
const collected = [];
|
|
51
108
|
for await (const message of session) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? message.content
|
|
55
|
-
: JSON.stringify(message.content);
|
|
56
|
-
process.stdout.write(text);
|
|
57
|
-
logStream.write(text);
|
|
58
|
-
}
|
|
59
|
-
result = message;
|
|
109
|
+
collected.push(message);
|
|
110
|
+
logMessage(message, logStream, indicator);
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
logStream.end();
|
|
63
114
|
indicator.stop();
|
|
64
115
|
|
|
116
|
+
const result = extractResult(collected);
|
|
65
117
|
return {
|
|
66
118
|
exitCode: 0,
|
|
67
|
-
cost: result?.total_cost_usd
|
|
68
|
-
tokenUsage: result?.
|
|
119
|
+
cost: result?.total_cost_usd ?? null,
|
|
120
|
+
tokenUsage: result?.usage ?? null,
|
|
69
121
|
logFile,
|
|
70
122
|
};
|
|
71
123
|
} catch (err) {
|
|
@@ -83,7 +135,7 @@ async function runCodingSession(sessionNum, opts = {}) {
|
|
|
83
135
|
}
|
|
84
136
|
|
|
85
137
|
async function runScanSession(requirement, opts = {}) {
|
|
86
|
-
const
|
|
138
|
+
const sdk = await loadSDK();
|
|
87
139
|
const config = loadConfig();
|
|
88
140
|
applyEnvConfig(config);
|
|
89
141
|
const indicator = new Indicator();
|
|
@@ -100,45 +152,33 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
100
152
|
log('info', `正在调用 Claude Code 执行项目扫描(${projectType}项目)...`);
|
|
101
153
|
|
|
102
154
|
try {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
inferPhaseStep(indicator, event.tool_name, event.tool_input);
|
|
117
|
-
return { decision: 'allow' };
|
|
118
|
-
}
|
|
119
|
-
}]
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
155
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
156
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
157
|
+
queryOpts.hooks = {
|
|
158
|
+
PreToolUse: [{
|
|
159
|
+
matcher: '*',
|
|
160
|
+
hooks: [async (input) => {
|
|
161
|
+
inferPhaseStep(indicator, input.tool_name, input.tool_input);
|
|
162
|
+
return {};
|
|
163
|
+
}]
|
|
164
|
+
}]
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
123
168
|
|
|
124
|
-
|
|
169
|
+
const collected = [];
|
|
125
170
|
for await (const message of session) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? message.content
|
|
129
|
-
: JSON.stringify(message.content);
|
|
130
|
-
process.stdout.write(text);
|
|
131
|
-
logStream.write(text);
|
|
132
|
-
}
|
|
133
|
-
result = message;
|
|
171
|
+
collected.push(message);
|
|
172
|
+
logMessage(message, logStream, indicator);
|
|
134
173
|
}
|
|
135
174
|
|
|
136
175
|
logStream.end();
|
|
137
176
|
indicator.stop();
|
|
138
177
|
|
|
178
|
+
const result = extractResult(collected);
|
|
139
179
|
return {
|
|
140
180
|
exitCode: 0,
|
|
141
|
-
cost: result?.total_cost_usd
|
|
181
|
+
cost: result?.total_cost_usd ?? null,
|
|
142
182
|
logFile,
|
|
143
183
|
};
|
|
144
184
|
} catch (err) {
|
|
@@ -150,7 +190,7 @@ async function runScanSession(requirement, opts = {}) {
|
|
|
150
190
|
}
|
|
151
191
|
|
|
152
192
|
async function runViewSession(requirement, opts = {}) {
|
|
153
|
-
const
|
|
193
|
+
const sdk = await loadSDK();
|
|
154
194
|
const p = paths();
|
|
155
195
|
const config = loadConfig();
|
|
156
196
|
applyEnvConfig(config);
|
|
@@ -172,25 +212,13 @@ async function runViewSession(requirement, opts = {}) {
|
|
|
172
212
|
}
|
|
173
213
|
|
|
174
214
|
try {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
allowedTools: getAllowedTools(config),
|
|
180
|
-
permissionMode: 'bypassPermissions',
|
|
181
|
-
verbose: true,
|
|
182
|
-
cwd: opts.projectRoot || process.cwd(),
|
|
183
|
-
timeout_ms: config.timeoutMs,
|
|
184
|
-
}
|
|
185
|
-
});
|
|
215
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
216
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
217
|
+
|
|
218
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
186
219
|
|
|
187
220
|
for await (const message of session) {
|
|
188
|
-
|
|
189
|
-
const text = typeof message.content === 'string'
|
|
190
|
-
? message.content
|
|
191
|
-
: JSON.stringify(message.content);
|
|
192
|
-
process.stdout.write(text);
|
|
193
|
-
}
|
|
221
|
+
logMessage(message, null);
|
|
194
222
|
}
|
|
195
223
|
} catch (err) {
|
|
196
224
|
log('error', `观测模式错误: ${err.message}`);
|
|
@@ -198,7 +226,7 @@ async function runViewSession(requirement, opts = {}) {
|
|
|
198
226
|
}
|
|
199
227
|
|
|
200
228
|
async function runAddSession(instruction, opts = {}) {
|
|
201
|
-
const
|
|
229
|
+
const sdk = await loadSDK();
|
|
202
230
|
const config = loadConfig();
|
|
203
231
|
applyEnvConfig(config);
|
|
204
232
|
|
|
@@ -210,26 +238,13 @@ async function runAddSession(instruction, opts = {}) {
|
|
|
210
238
|
const logStream = fs.createWriteStream(logFile, { flags: 'a' });
|
|
211
239
|
|
|
212
240
|
try {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
allowedTools: getAllowedTools(config),
|
|
218
|
-
permissionMode: 'bypassPermissions',
|
|
219
|
-
verbose: true,
|
|
220
|
-
cwd: opts.projectRoot || process.cwd(),
|
|
221
|
-
timeout_ms: config.timeoutMs,
|
|
222
|
-
}
|
|
223
|
-
});
|
|
241
|
+
const queryOpts = buildQueryOptions(config, opts);
|
|
242
|
+
queryOpts.systemPrompt = systemPrompt;
|
|
243
|
+
|
|
244
|
+
const session = sdk.query({ prompt, options: queryOpts });
|
|
224
245
|
|
|
225
246
|
for await (const message of session) {
|
|
226
|
-
|
|
227
|
-
const text = typeof message.content === 'string'
|
|
228
|
-
? message.content
|
|
229
|
-
: JSON.stringify(message.content);
|
|
230
|
-
process.stdout.write(text);
|
|
231
|
-
logStream.write(text);
|
|
232
|
-
}
|
|
247
|
+
logMessage(message, logStream);
|
|
233
248
|
}
|
|
234
249
|
|
|
235
250
|
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 || [];
|