claude-coder 1.6.2 → 1.7.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 +125 -127
- package/bin/cli.js +161 -197
- package/package.json +47 -44
- package/prompts/ADD_GUIDE.md +98 -0
- package/{templates → prompts}/CLAUDE.md +199 -238
- package/{templates → prompts}/SCAN_PROTOCOL.md +118 -123
- package/prompts/add_user.md +24 -0
- package/prompts/coding_user.md +23 -0
- package/prompts/scan_user.md +17 -0
- package/src/auth.js +245 -245
- package/src/config.js +201 -223
- package/src/hooks.js +160 -96
- package/src/indicator.js +217 -160
- package/src/init.js +144 -144
- package/src/prompts.js +295 -339
- package/src/runner.js +420 -394
- package/src/scanner.js +62 -62
- package/src/session.js +352 -320
- package/src/setup.js +579 -397
- package/src/tasks.js +172 -172
- package/src/validator.js +181 -170
- package/templates/requirements.example.md +56 -56
- package/templates/test_rule.md +194 -157
- package/docs/ARCHITECTURE.md +0 -516
- package/docs/PLAYWRIGHT_CREDENTIALS.md +0 -178
- package/docs/README.en.md +0 -103
package/src/validator.js
CHANGED
|
@@ -1,170 +1,181 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
const { paths, log, getProjectRoot } = require('./config');
|
|
6
|
-
const { loadTasks, getFeatures } = require('./tasks');
|
|
7
|
-
|
|
8
|
-
function tryExtractFromBroken(text) {
|
|
9
|
-
const result = {};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
68
|
-
log('warn', `
|
|
69
|
-
return { valid: false, fatal: false, recoverable: true, reason:
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (data.session_result
|
|
73
|
-
log('
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const { paths, log, getProjectRoot } = require('./config');
|
|
6
|
+
const { loadTasks, getFeatures } = require('./tasks');
|
|
7
|
+
|
|
8
|
+
function tryExtractFromBroken(text) {
|
|
9
|
+
const result = {};
|
|
10
|
+
|
|
11
|
+
// session_result: 只能是 success 或 failed
|
|
12
|
+
const srMatch = text.match(/"session_result"\s*:\s*"(success|failed)"/);
|
|
13
|
+
if (srMatch) result.session_result = srMatch[1];
|
|
14
|
+
|
|
15
|
+
// status_after: 可能是 pending/in_progress/testing/done/failed 或 N/A
|
|
16
|
+
const saMatch = text.match(/"status_after"\s*:\s*"([^"]+)"/);
|
|
17
|
+
if (saMatch) result.status_after = saMatch[1];
|
|
18
|
+
|
|
19
|
+
// status_before: 同上
|
|
20
|
+
const sbMatch = text.match(/"status_before"\s*:\s*"([^"]+)"/);
|
|
21
|
+
if (sbMatch) result.status_before = sbMatch[1];
|
|
22
|
+
|
|
23
|
+
// notes: 可选字段,字符串类型
|
|
24
|
+
const notesMatch = text.match(/"notes"\s*:\s*"([^"]*)"/);
|
|
25
|
+
if (notesMatch) result.notes = notesMatch[1];
|
|
26
|
+
|
|
27
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function inferFromTasks(taskId) {
|
|
31
|
+
if (!taskId) return null;
|
|
32
|
+
const data = loadTasks();
|
|
33
|
+
if (!data) return null;
|
|
34
|
+
const task = getFeatures(data).find(f => f.id === taskId);
|
|
35
|
+
return task ? task.status : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateSessionResult() {
|
|
39
|
+
const p = paths();
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(p.sessionResult)) {
|
|
42
|
+
log('error', 'Agent 未生成 session_result.json');
|
|
43
|
+
return { valid: false, fatal: true, recoverable: false, reason: 'session_result.json 不存在' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const raw = fs.readFileSync(p.sessionResult, 'utf8');
|
|
47
|
+
let data;
|
|
48
|
+
try {
|
|
49
|
+
data = JSON.parse(raw);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
log('warn', `session_result.json 解析失败: ${err.message}`);
|
|
52
|
+
const extracted = tryExtractFromBroken(raw);
|
|
53
|
+
if (extracted) {
|
|
54
|
+
log('info', `从截断 JSON 中提取到关键字段: ${JSON.stringify(extracted)}`);
|
|
55
|
+
return { valid: false, fatal: false, recoverable: true, reason: 'JSON 截断但提取到关键字段', data: extracted };
|
|
56
|
+
}
|
|
57
|
+
return { valid: false, fatal: false, recoverable: true, reason: `JSON 解析失败: ${err.message}` };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Backward compat: unwrap legacy { current: {...} } format
|
|
61
|
+
if (data.current && typeof data.current === 'object') {
|
|
62
|
+
data = data.current;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const required = ['session_result', 'status_after'];
|
|
66
|
+
const missing = required.filter(k => !(k in data));
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
log('warn', `session_result.json 缺少字段: ${missing.join(', ')}`);
|
|
69
|
+
return { valid: false, fatal: false, recoverable: true, reason: `缺少字段: ${missing.join(', ')}` };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!['success', 'failed'].includes(data.session_result)) {
|
|
73
|
+
log('warn', `session_result 必须是 success 或 failed,实际是: ${data.session_result}`);
|
|
74
|
+
return { valid: false, fatal: false, recoverable: true, reason: `无效 session_result: ${data.session_result}`, data };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const validStatuses = ['pending', 'in_progress', 'testing', 'done', 'failed'];
|
|
78
|
+
if (!validStatuses.includes(data.status_after)) {
|
|
79
|
+
log('warn', `status_after 不合法: ${data.status_after}`);
|
|
80
|
+
return { valid: false, fatal: false, recoverable: true, reason: `无效 status_after: ${data.status_after}`, data };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (data.session_result === 'success') {
|
|
84
|
+
log('ok', 'session_result.json 合法 (success)');
|
|
85
|
+
} else {
|
|
86
|
+
log('warn', 'session_result.json 合法,但 Agent 报告失败 (failed)');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { valid: true, fatal: false, recoverable: false, data };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function checkGitProgress(headBefore) {
|
|
93
|
+
if (!headBefore) {
|
|
94
|
+
log('info', '未提供 head_before,跳过 git 检查');
|
|
95
|
+
return { hasCommit: false, warning: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const projectRoot = getProjectRoot();
|
|
99
|
+
let headAfter;
|
|
100
|
+
try {
|
|
101
|
+
headAfter = execSync('git rev-parse HEAD', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
102
|
+
} catch {
|
|
103
|
+
headAfter = 'none';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (headBefore === headAfter) {
|
|
107
|
+
log('warn', '本次会话没有新的 git 提交');
|
|
108
|
+
return { hasCommit: false, warning: true };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const msg = execSync('git log --oneline -1', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
113
|
+
log('ok', `检测到新提交: ${msg}`);
|
|
114
|
+
} catch { /* ignore */ }
|
|
115
|
+
|
|
116
|
+
return { hasCommit: true, warning: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function checkTestCoverage(taskId, statusAfter) {
|
|
120
|
+
const p = paths();
|
|
121
|
+
|
|
122
|
+
if (!fs.existsSync(p.testsFile)) return;
|
|
123
|
+
if (statusAfter !== 'done' || !taskId) return;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const tests = JSON.parse(fs.readFileSync(p.testsFile, 'utf8'));
|
|
127
|
+
const testCases = tests.test_cases || [];
|
|
128
|
+
const taskTests = testCases.filter(t => t.feature_id === taskId);
|
|
129
|
+
if (taskTests.length > 0) {
|
|
130
|
+
const failed = taskTests.filter(t => t.last_result === 'fail');
|
|
131
|
+
if (failed.length > 0) {
|
|
132
|
+
log('warn', `tests.json 中有失败的验证记录: ${failed.map(t => t.id).join(', ')}`);
|
|
133
|
+
} else {
|
|
134
|
+
log('ok', `${taskTests.length} 条验证记录覆盖任务 ${taskId}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} catch { /* ignore */ }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function validate(headBefore, taskId) {
|
|
141
|
+
log('info', '========== 开始校验 ==========');
|
|
142
|
+
|
|
143
|
+
const srResult = validateSessionResult();
|
|
144
|
+
const gitResult = checkGitProgress(headBefore);
|
|
145
|
+
|
|
146
|
+
let fatal = false;
|
|
147
|
+
let hasWarnings = false;
|
|
148
|
+
|
|
149
|
+
if (srResult.valid) {
|
|
150
|
+
hasWarnings = gitResult.warning;
|
|
151
|
+
} else {
|
|
152
|
+
// session_result.json has issues — cross-validate with git + tasks.json
|
|
153
|
+
if (gitResult.hasCommit) {
|
|
154
|
+
const taskStatus = inferFromTasks(taskId);
|
|
155
|
+
if (taskStatus === 'done' || taskStatus === 'testing') {
|
|
156
|
+
log('warn', `session_result.json 异常,但 tasks.json 显示 ${taskId} 已 ${taskStatus},且有新提交,降级为警告`);
|
|
157
|
+
} else {
|
|
158
|
+
log('warn', 'session_result.json 异常,但有新提交,降级为警告(不回滚代码)');
|
|
159
|
+
}
|
|
160
|
+
hasWarnings = true;
|
|
161
|
+
} else {
|
|
162
|
+
log('error', '无新提交且 session_result.json 异常,视为致命');
|
|
163
|
+
fatal = true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const statusAfter = srResult.data?.status_after || inferFromTasks(taskId) || null;
|
|
168
|
+
checkTestCoverage(taskId, statusAfter);
|
|
169
|
+
|
|
170
|
+
if (fatal) {
|
|
171
|
+
log('error', '========== 校验失败 (致命) ==========');
|
|
172
|
+
} else if (hasWarnings) {
|
|
173
|
+
log('warn', '========== 校验通过 (有警告) ==========');
|
|
174
|
+
} else {
|
|
175
|
+
log('ok', '========== 校验全部通过 ==========');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { fatal, hasWarnings, sessionData: srResult.data };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = { validate, validateSessionResult, checkGitProgress };
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
# 需求文档 / Requirements
|
|
2
|
-
|
|
3
|
-
> 将本文件复制为 `requirements.md`,填写你的需求后启动:
|
|
4
|
-
> ```bash
|
|
5
|
-
> cp requirements.example.md requirements.md
|
|
6
|
-
> vim requirements.md # 编辑你的需求
|
|
7
|
-
> claude-coder run
|
|
8
|
-
> ```
|
|
9
|
-
> Agent 会在初始化和每个 session 中自动读取此文件。
|
|
10
|
-
> **你可以随时修改 `requirements.md`**:新增的功能需求会在下次 session 中自动同步到 `tasks.json`;发现需要改进的地方,补充到下方功能需求或「其他要求」即可。
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## 项目概述(必填)
|
|
15
|
-
|
|
16
|
-
<!-- 一两句话描述你要做什么 -->
|
|
17
|
-
|
|
18
|
-
例如:做一个网页版的 AI 文章总结工具,用户粘贴 URL 后自动抓取内容并生成摘要。
|
|
19
|
-
|
|
20
|
-
## 功能需求(必填)
|
|
21
|
-
|
|
22
|
-
<!-- 列出你需要的功能,越具体越好 -->
|
|
23
|
-
|
|
24
|
-
- [ ] 功能 1:用户输入 URL,后端抓取文章内容
|
|
25
|
-
- [ ] 功能 2:调用 LLM API 生成中文摘要
|
|
26
|
-
- [ ] 功能 3:前端展示摘要结果,支持复制
|
|
27
|
-
- [ ] 功能 4:历史记录,保存已总结的文章
|
|
28
|
-
- [ ] 功能 5:...
|
|
29
|
-
|
|
30
|
-
## 技术约束(可选)
|
|
31
|
-
|
|
32
|
-
<!-- 如果你对技术栈有偏好,写在这里。不写则由 Agent 自行决定。 -->
|
|
33
|
-
|
|
34
|
-
- 后端:Python FastAPI
|
|
35
|
-
- 前端:React + Vite
|
|
36
|
-
- 数据库:SQLite
|
|
37
|
-
- 状态管理:Zustand(不要用 Redux)
|
|
38
|
-
- LLM:OpenAI API(gpt-4o)
|
|
39
|
-
|
|
40
|
-
## 样式与设计(可选)
|
|
41
|
-
|
|
42
|
-
<!-- UI 风格、配色、参考链接等。不写则由 Agent 自行决定。 -->
|
|
43
|
-
|
|
44
|
-
- 整体风格:简约、现代,参考 Notion
|
|
45
|
-
- 配色:深色主题为主,主色调 #4F46E5(靛蓝)
|
|
46
|
-
- CSS 框架:Tailwind CSS
|
|
47
|
-
- 移动端适配:是
|
|
48
|
-
- 参考链接:https://example.com/design-reference
|
|
49
|
-
|
|
50
|
-
## 其他要求(可选)
|
|
51
|
-
|
|
52
|
-
<!-- 性能、安全、部署、国际化等任何额外要求 -->
|
|
53
|
-
|
|
54
|
-
- 支持中英文界面
|
|
55
|
-
- API 响应时间 < 3 秒
|
|
56
|
-
- 需要 Docker 部署支持
|
|
1
|
+
# 需求文档 / Requirements
|
|
2
|
+
|
|
3
|
+
> 将本文件复制为 `requirements.md`,填写你的需求后启动:
|
|
4
|
+
> ```bash
|
|
5
|
+
> cp requirements.example.md requirements.md
|
|
6
|
+
> vim requirements.md # 编辑你的需求
|
|
7
|
+
> claude-coder run
|
|
8
|
+
> ```
|
|
9
|
+
> Agent 会在初始化和每个 session 中自动读取此文件。
|
|
10
|
+
> **你可以随时修改 `requirements.md`**:新增的功能需求会在下次 session 中自动同步到 `tasks.json`;发现需要改进的地方,补充到下方功能需求或「其他要求」即可。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 项目概述(必填)
|
|
15
|
+
|
|
16
|
+
<!-- 一两句话描述你要做什么 -->
|
|
17
|
+
|
|
18
|
+
例如:做一个网页版的 AI 文章总结工具,用户粘贴 URL 后自动抓取内容并生成摘要。
|
|
19
|
+
|
|
20
|
+
## 功能需求(必填)
|
|
21
|
+
|
|
22
|
+
<!-- 列出你需要的功能,越具体越好 -->
|
|
23
|
+
|
|
24
|
+
- [ ] 功能 1:用户输入 URL,后端抓取文章内容
|
|
25
|
+
- [ ] 功能 2:调用 LLM API 生成中文摘要
|
|
26
|
+
- [ ] 功能 3:前端展示摘要结果,支持复制
|
|
27
|
+
- [ ] 功能 4:历史记录,保存已总结的文章
|
|
28
|
+
- [ ] 功能 5:...
|
|
29
|
+
|
|
30
|
+
## 技术约束(可选)
|
|
31
|
+
|
|
32
|
+
<!-- 如果你对技术栈有偏好,写在这里。不写则由 Agent 自行决定。 -->
|
|
33
|
+
|
|
34
|
+
- 后端:Python FastAPI
|
|
35
|
+
- 前端:React + Vite
|
|
36
|
+
- 数据库:SQLite
|
|
37
|
+
- 状态管理:Zustand(不要用 Redux)
|
|
38
|
+
- LLM:OpenAI API(gpt-4o)
|
|
39
|
+
|
|
40
|
+
## 样式与设计(可选)
|
|
41
|
+
|
|
42
|
+
<!-- UI 风格、配色、参考链接等。不写则由 Agent 自行决定。 -->
|
|
43
|
+
|
|
44
|
+
- 整体风格:简约、现代,参考 Notion
|
|
45
|
+
- 配色:深色主题为主,主色调 #4F46E5(靛蓝)
|
|
46
|
+
- CSS 框架:Tailwind CSS
|
|
47
|
+
- 移动端适配:是
|
|
48
|
+
- 参考链接:https://example.com/design-reference
|
|
49
|
+
|
|
50
|
+
## 其他要求(可选)
|
|
51
|
+
|
|
52
|
+
<!-- 性能、安全、部署、国际化等任何额外要求 -->
|
|
53
|
+
|
|
54
|
+
- 支持中英文界面
|
|
55
|
+
- API 响应时间 < 3 秒
|
|
56
|
+
- 需要 Docker 部署支持
|