claude-coder 1.7.0 → 1.8.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 +177 -125
- package/bin/cli.js +159 -161
- package/package.json +52 -47
- package/src/commands/auth.js +294 -0
- package/src/commands/setup-modules/helpers.js +105 -0
- package/src/commands/setup-modules/index.js +26 -0
- package/src/commands/setup-modules/mcp.js +95 -0
- package/src/commands/setup-modules/provider.js +261 -0
- package/src/commands/setup-modules/safety.js +62 -0
- package/src/commands/setup-modules/simplify.js +53 -0
- package/src/commands/setup.js +172 -0
- package/src/common/assets.js +192 -0
- package/src/{config.js → common/config.js} +138 -201
- package/src/common/constants.js +57 -0
- package/src/{indicator.js → common/indicator.js} +222 -217
- package/src/common/interaction.js +170 -0
- package/src/common/logging.js +77 -0
- package/src/common/sdk.js +51 -0
- package/src/{tasks.js → common/tasks.js} +157 -172
- package/src/common/utils.js +147 -0
- package/src/core/base.js +54 -0
- package/src/core/coding.js +55 -0
- package/src/core/context.js +132 -0
- package/src/core/hooks.js +529 -0
- package/src/{init.js → core/init.js} +163 -144
- package/src/core/plan.js +318 -0
- package/src/core/prompts.js +253 -0
- package/src/core/query.js +48 -0
- package/src/core/repair.js +58 -0
- package/src/{runner.js → core/runner.js} +352 -420
- package/src/core/scan.js +89 -0
- package/src/core/simplify.js +59 -0
- package/src/core/validator.js +138 -0
- package/{prompts/ADD_GUIDE.md → templates/addGuide.md} +98 -98
- package/templates/addUser.md +26 -0
- package/{prompts/CLAUDE.md → templates/agentProtocol.md} +195 -199
- package/templates/bash-process.md +5 -0
- package/{prompts/coding_user.md → templates/codingUser.md} +31 -23
- package/templates/guidance.json +35 -0
- package/templates/playwright.md +17 -0
- package/templates/requirements.example.md +56 -56
- package/{prompts/SCAN_PROTOCOL.md → templates/scanProtocol.md} +118 -118
- package/{prompts/scan_user.md → templates/scanUser.md} +17 -17
- package/templates/test_rule.md +194 -194
- package/prompts/add_user.md +0 -24
- package/src/auth.js +0 -245
- package/src/hooks.js +0 -160
- package/src/prompts.js +0 -295
- package/src/scanner.js +0 -62
- package/src/session.js +0 -352
- package/src/setup.js +0 -579
- package/src/validator.js +0 -181
package/src/core/scan.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { log } = require('../common/config');
|
|
4
|
+
const { assets } = require('../common/assets');
|
|
5
|
+
const { runSession } = require('./base');
|
|
6
|
+
const { buildQueryOptions, hasCodeFiles } = require('./query');
|
|
7
|
+
const { buildSystemPrompt, buildScanPrompt } = require('./prompts');
|
|
8
|
+
const { extractResult } = require('../common/logging');
|
|
9
|
+
const { RETRY } = require('../common/constants');
|
|
10
|
+
|
|
11
|
+
function validateProfile() {
|
|
12
|
+
if (!assets.exists('profile')) return { valid: false, issues: ['profile 不存在'] };
|
|
13
|
+
|
|
14
|
+
const profile = assets.readJson('profile', null);
|
|
15
|
+
if (!profile) return { valid: false, issues: ['profile 解析失败'] };
|
|
16
|
+
const issues = [];
|
|
17
|
+
|
|
18
|
+
if (!profile.tech_stack?.backend?.framework && !profile.tech_stack?.frontend?.framework) {
|
|
19
|
+
issues.push('tech_stack 缺少 backend 或 frontend 框架');
|
|
20
|
+
}
|
|
21
|
+
if (profile.tech_stack?.backend?.framework && (!profile.services || profile.services.length === 0)) {
|
|
22
|
+
issues.push('有后端框架但 services 为空(缺少启动命令和端口)');
|
|
23
|
+
}
|
|
24
|
+
if (!profile.existing_docs || profile.existing_docs.length === 0) {
|
|
25
|
+
issues.push('existing_docs 为空(至少需要 README.md)');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return { valid: issues.length === 0, issues };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function _runScanSession(requirement, opts = {}) {
|
|
32
|
+
const projectType = hasCodeFiles(opts.projectRoot || assets.projectRoot) ? 'existing' : 'new';
|
|
33
|
+
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
34
|
+
|
|
35
|
+
return runSession('scan', {
|
|
36
|
+
opts,
|
|
37
|
+
sessionNum: 0,
|
|
38
|
+
logFileName: `scan_${dateStr}.log`,
|
|
39
|
+
label: `scan (${projectType})`,
|
|
40
|
+
|
|
41
|
+
async execute(sdk, ctx) {
|
|
42
|
+
log('info', `正在调用 Claude Code 执行项目扫描(${projectType}项目)...`);
|
|
43
|
+
|
|
44
|
+
const prompt = buildScanPrompt(projectType, requirement);
|
|
45
|
+
const queryOpts = buildQueryOptions(ctx.config, opts);
|
|
46
|
+
queryOpts.systemPrompt = buildSystemPrompt(true);
|
|
47
|
+
queryOpts.hooks = ctx.hooks;
|
|
48
|
+
queryOpts.abortController = ctx.abortController;
|
|
49
|
+
|
|
50
|
+
const collected = await ctx.runQuery(sdk, prompt, queryOpts);
|
|
51
|
+
const result = extractResult(collected);
|
|
52
|
+
|
|
53
|
+
return { cost: result?.total_cost_usd ?? null };
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function scan(requirement, opts = {}) {
|
|
59
|
+
assets.ensureDirs();
|
|
60
|
+
|
|
61
|
+
const maxAttempts = RETRY.SCAN_ATTEMPTS;
|
|
62
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
63
|
+
log('info', `初始化尝试 ${attempt} / ${maxAttempts} ...`);
|
|
64
|
+
|
|
65
|
+
const result = await _runScanSession(requirement, opts);
|
|
66
|
+
|
|
67
|
+
if (assets.exists('profile')) {
|
|
68
|
+
const profileCheck = validateProfile();
|
|
69
|
+
if (!profileCheck.valid) {
|
|
70
|
+
log('warn', `profile 质量问题: ${profileCheck.issues.join('; ')}`);
|
|
71
|
+
}
|
|
72
|
+
log('ok', '项目扫描完成');
|
|
73
|
+
return { success: true, cost: result.cost };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (attempt < maxAttempts) {
|
|
77
|
+
log('warn', '初始化未完成,将重试...');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
log('error', `初始化失败:已重试 ${maxAttempts} 次,关键文件仍未生成`);
|
|
82
|
+
return { success: false, cost: null };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
scan,
|
|
87
|
+
validateProfile,
|
|
88
|
+
_runScanSession,
|
|
89
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { runSession } = require("./base");
|
|
4
|
+
const { buildQueryOptions } = require("./query");
|
|
5
|
+
const { log } = require("../common/config");
|
|
6
|
+
const { assets } = require("../common/assets");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
|
|
9
|
+
async function _runSimplifySession(n = 3, focus = null, opts = {}) {
|
|
10
|
+
const projectRoot = assets.projectRoot;
|
|
11
|
+
let diff = "";
|
|
12
|
+
try {
|
|
13
|
+
diff = execSync(`git diff HEAD~${n}..HEAD`, {
|
|
14
|
+
cwd: projectRoot,
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
17
|
+
});
|
|
18
|
+
} catch (err) {
|
|
19
|
+
log("warn", `无法获取最近 ${n} 个 commit 的 diff: ${err.message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const focusLine = focus ? `\n审查聚焦方向:${focus}` : "";
|
|
23
|
+
const tasksPath = assets.path('tasks');
|
|
24
|
+
const taskLine = tasksPath ? `\n任务文件: ${tasksPath}(可读取了解当前项目任务上下文)` : '';
|
|
25
|
+
const prompt = `/simplify\n\n审查范围:最近 ${n} 个 commit${taskLine}${focusLine}\n不要向用户提问,默认使用推荐方式直接执行。\n\n${diff.slice(0, 50000)}`;
|
|
26
|
+
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
|
27
|
+
|
|
28
|
+
return runSession("simplify", {
|
|
29
|
+
opts,
|
|
30
|
+
sessionNum: 0,
|
|
31
|
+
logFileName: `simplify_${dateStr}.log`,
|
|
32
|
+
label: "simplify",
|
|
33
|
+
|
|
34
|
+
async execute(sdk, ctx) {
|
|
35
|
+
log("info", `正在审查最近 ${n} 个 commit 的代码变更...`);
|
|
36
|
+
|
|
37
|
+
const queryOpts = buildQueryOptions(ctx.config, opts);
|
|
38
|
+
queryOpts.hooks = ctx.hooks;
|
|
39
|
+
queryOpts.abortController = ctx.abortController;
|
|
40
|
+
queryOpts.disallowedTools = ['askUserQuestion'];
|
|
41
|
+
|
|
42
|
+
await ctx.runQuery(sdk, prompt, queryOpts);
|
|
43
|
+
log("ok", "代码审查完成");
|
|
44
|
+
|
|
45
|
+
return {};
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function simplify(focus = null, opts = {}) {
|
|
51
|
+
assets.ensureDirs();
|
|
52
|
+
const n = opts.n || 3;
|
|
53
|
+
return _runSimplifySession(n, focus, opts);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
simplify,
|
|
58
|
+
_runSimplifySession,
|
|
59
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const { log } = require('../common/config');
|
|
5
|
+
const { getGitHead } = require('../common/utils');
|
|
6
|
+
const { assets } = require('../common/assets');
|
|
7
|
+
const { TASK_STATUSES } = require('../common/constants');
|
|
8
|
+
const { loadTasks, getFeatures } = require('../common/tasks');
|
|
9
|
+
|
|
10
|
+
function inferFromTasks(taskId) {
|
|
11
|
+
if (!taskId) return null;
|
|
12
|
+
const data = loadTasks();
|
|
13
|
+
if (!data) return null;
|
|
14
|
+
const task = getFeatures(data).find(f => f.id === taskId);
|
|
15
|
+
return task ? task.status : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validateSessionResult() {
|
|
19
|
+
if (!assets.exists('sessionResult')) {
|
|
20
|
+
log('error', 'Agent 未生成 session_result.json');
|
|
21
|
+
return { valid: false, fatal: true, recoverable: false, reason: 'session_result.json 不存在' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const data = assets.readJson('sessionResult', null);
|
|
25
|
+
if (data === null) {
|
|
26
|
+
log('warn', 'session_result.json 解析失败');
|
|
27
|
+
return { valid: false, fatal: false, recoverable: true, reason: 'JSON 解析失败' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const sessionData = data.current && typeof data.current === 'object' ? data.current : data;
|
|
31
|
+
|
|
32
|
+
const required = ['session_result', 'status_after'];
|
|
33
|
+
const missing = required.filter(k => !(k in sessionData));
|
|
34
|
+
if (missing.length > 0) {
|
|
35
|
+
log('warn', `session_result.json 缺少字段: ${missing.join(', ')}`);
|
|
36
|
+
return { valid: false, fatal: false, recoverable: true, reason: `缺少字段: ${missing.join(', ')}` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!['success', 'failed'].includes(sessionData.session_result)) {
|
|
40
|
+
log('warn', `session_result 必须是 success 或 failed,实际是: ${sessionData.session_result}`);
|
|
41
|
+
return { valid: false, fatal: false, recoverable: true, reason: `无效 session_result: ${sessionData.session_result}`, data: sessionData };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!TASK_STATUSES.includes(sessionData.status_after)) {
|
|
45
|
+
log('warn', `status_after 不合法: ${sessionData.status_after}`);
|
|
46
|
+
return { valid: false, fatal: false, recoverable: true, reason: `无效 status_after: ${sessionData.status_after}`, data: sessionData };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (sessionData.session_result === 'success') {
|
|
50
|
+
log('ok', 'session_result.json 合法 (success)');
|
|
51
|
+
} else {
|
|
52
|
+
log('warn', 'session_result.json 合法,但 Agent 报告失败 (failed)');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { valid: true, fatal: false, recoverable: false, data: sessionData };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function checkGitProgress(headBefore) {
|
|
59
|
+
if (!headBefore) {
|
|
60
|
+
log('info', '未提供 head_before,跳过 git 检查');
|
|
61
|
+
return { hasCommit: false, warning: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const projectRoot = assets.projectRoot;
|
|
65
|
+
const headAfter = getGitHead(projectRoot);
|
|
66
|
+
|
|
67
|
+
if (headBefore === headAfter) {
|
|
68
|
+
log('warn', '本次会话没有新的 git 提交');
|
|
69
|
+
return { hasCommit: false, warning: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const msg = execSync('git log --oneline -1', { cwd: projectRoot, encoding: 'utf8' }).trim();
|
|
74
|
+
log('ok', `检测到新提交: ${msg}`);
|
|
75
|
+
} catch { /* ignore */ }
|
|
76
|
+
|
|
77
|
+
return { hasCommit: true, warning: false };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function checkTestCoverage(taskId, statusAfter) {
|
|
81
|
+
if (statusAfter !== 'done' || !taskId) return;
|
|
82
|
+
if (!assets.exists('tests')) return;
|
|
83
|
+
|
|
84
|
+
const tests = assets.readJson('tests', null);
|
|
85
|
+
if (!tests) return;
|
|
86
|
+
const testCases = tests.test_cases || [];
|
|
87
|
+
const taskTests = testCases.filter(t => t.feature_id === taskId);
|
|
88
|
+
if (taskTests.length > 0) {
|
|
89
|
+
const failed = taskTests.filter(t => t.last_result === 'fail');
|
|
90
|
+
if (failed.length > 0) {
|
|
91
|
+
log('warn', `tests.json 中有失败的验证记录: ${failed.map(t => t.id).join(', ')}`);
|
|
92
|
+
} else {
|
|
93
|
+
log('ok', `${taskTests.length} 条验证记录覆盖任务 ${taskId}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function validate(headBefore, taskId) {
|
|
99
|
+
log('info', '========== 开始校验 ==========');
|
|
100
|
+
|
|
101
|
+
const srResult = validateSessionResult();
|
|
102
|
+
const gitResult = checkGitProgress(headBefore);
|
|
103
|
+
|
|
104
|
+
let fatal = false;
|
|
105
|
+
let hasWarnings = false;
|
|
106
|
+
|
|
107
|
+
if (srResult.valid) {
|
|
108
|
+
hasWarnings = gitResult.warning;
|
|
109
|
+
} else {
|
|
110
|
+
if (gitResult.hasCommit) {
|
|
111
|
+
const taskStatus = inferFromTasks(taskId);
|
|
112
|
+
if (taskStatus === 'done' || taskStatus === 'testing') {
|
|
113
|
+
log('warn', `session_result.json 异常,但 tasks.json 显示 ${taskId} 已 ${taskStatus},且有新提交,降级为警告`);
|
|
114
|
+
} else {
|
|
115
|
+
log('warn', 'session_result.json 异常,但有新提交,降级为警告(不回滚代码)');
|
|
116
|
+
}
|
|
117
|
+
hasWarnings = true;
|
|
118
|
+
} else {
|
|
119
|
+
log('error', '无新提交且 session_result.json 异常,视为致命');
|
|
120
|
+
fatal = true;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const statusAfter = srResult.data?.status_after || inferFromTasks(taskId) || null;
|
|
125
|
+
checkTestCoverage(taskId, statusAfter);
|
|
126
|
+
|
|
127
|
+
if (fatal) {
|
|
128
|
+
log('error', '========== 校验失败 (致命) ==========');
|
|
129
|
+
} else if (hasWarnings) {
|
|
130
|
+
log('warn', '========== 校验通过 (有警告) ==========');
|
|
131
|
+
} else {
|
|
132
|
+
log('ok', '========== 校验全部通过 ==========');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { fatal, hasWarnings, sessionData: srResult.data };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { validate, validateSessionResult, checkGitProgress };
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# 任务分解指南
|
|
2
|
-
|
|
3
|
-
> 本文档是 `claude-coder add` 指令的参考文档。
|
|
4
|
-
> ADD Agent 的唯一职责:分解需求为结构化任务,追加到 tasks.json。不实现任何代码。
|
|
5
|
-
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
## tasks.json 格式
|
|
9
|
-
|
|
10
|
-
```json
|
|
11
|
-
{
|
|
12
|
-
"project": "项目名称",
|
|
13
|
-
"created_at": "2026-02-13",
|
|
14
|
-
"features": [
|
|
15
|
-
{
|
|
16
|
-
"id": "feat-001",
|
|
17
|
-
"category": "backend | frontend | fullstack | infra",
|
|
18
|
-
"priority": 1,
|
|
19
|
-
"description": "功能的简要描述(40字内)",
|
|
20
|
-
"steps": [
|
|
21
|
-
"具体步骤 1",
|
|
22
|
-
"具体步骤 2",
|
|
23
|
-
"端到端测试:验证方法"
|
|
24
|
-
],
|
|
25
|
-
"status": "pending",
|
|
26
|
-
"depends_on": []
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 字段规范
|
|
33
|
-
|
|
34
|
-
| 字段 | 规则 |
|
|
35
|
-
|------|------|
|
|
36
|
-
| `id` | 格式 `feat-NNN`,从已有最大值递增 |
|
|
37
|
-
| `category` | `backend` / `frontend` / `fullstack` / `infra`,准确归类 |
|
|
38
|
-
| `priority` | 数字越小越优先,从已有最大值递增 |
|
|
39
|
-
| `description` | 简明扼要,40 字内,说明"做什么"而非"怎么做" |
|
|
40
|
-
| `steps` | 具体可操作步骤,最后一步必须是可验证的测试命令,单任务不超过 5 步 |
|
|
41
|
-
| `status` | 新增任务一律 `"pending"` |
|
|
42
|
-
| `depends_on` | 引用前置任务的 `id`,形成 DAG(有向无环图),不得循环依赖 |
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## 任务分解规则
|
|
47
|
-
|
|
48
|
-
### 粒度控制
|
|
49
|
-
|
|
50
|
-
- 每个任务是独立可测试的功能单元,1-3 session 可完成,新增不超 500 行
|
|
51
|
-
- 单任务 steps 不超过 5 步,超过则拆分为多个任务
|
|
52
|
-
- 第一个任务从第一个有业务逻辑的功能开始,不重复脚手架内容
|
|
53
|
-
- 新项目:infra 任务合并为尽量少的条目,不拆碎
|
|
54
|
-
|
|
55
|
-
### 验证命令模板
|
|
56
|
-
|
|
57
|
-
steps 的最后一步必须包含可执行的验证命令:
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
API: curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT/path → 200
|
|
61
|
-
文件: grep -q "关键内容" path/to/file && echo "pass"
|
|
62
|
-
构建: npm run build 2>&1 | tail -1 → 无 error
|
|
63
|
-
页面: Playwright MCP snapshot 验证关键元素存在
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 反面案例(禁止出现)
|
|
67
|
-
|
|
68
|
-
- `"实现用户功能"` → 太模糊,应拆为具体接口
|
|
69
|
-
- `"编写测试"` → 测试应内嵌在 steps 末尾,不是独立任务
|
|
70
|
-
- steps 只有 `"实现xxx"` 没有验证步骤
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## requirements.md 处理原则
|
|
75
|
-
|
|
76
|
-
`requirements.md` 是用户的需求输入,**绝对不能修改它**。但"不能改"不等于"必须盲从"。遇到以下情况时,在 `session_result.json` 的 `notes` 中记录问题,按最合理的方式继续分解:
|
|
77
|
-
|
|
78
|
-
| 场景 | 处理方式 |
|
|
79
|
-
|------|----------|
|
|
80
|
-
| 需求自相矛盾 | 记录矛盾,按技术可行的方案分解,说明选择理由 |
|
|
81
|
-
| 需求与已有代码冲突 | 记录冲突,说明重构成本,按现有架构分解,建议用户确认 |
|
|
82
|
-
| 需求太模糊无法执行 | 自行做出合理决策,在 notes 中记录选择,供用户确认 |
|
|
83
|
-
| 需求中途变更 | 记录变更影响,基于最新需求分解 |
|
|
84
|
-
| 需求引用了不可访问的资源 | 记录问题,根据文字描述尽力分解 |
|
|
85
|
-
| 需求指定了不存在的依赖 | 记录问题,使用最接近的可用版本 |
|
|
86
|
-
|
|
87
|
-
**核心原则:不停工、不擅改、留记录。**
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Playwright MCP 测试任务
|
|
92
|
-
|
|
93
|
-
当任务涉及前端或全栈端到端测试,且项目已配置 Playwright MCP 时,测试步骤的详细规范(结构化标签、Smart Snapshot 策略、SSE 等待模式、步骤模板等)统一参见 `.claude-coder/test_rule.md` 第五节(等待策略)和第八节(步骤模板)。
|
|
94
|
-
|
|
95
|
-
此处只列关键原则:
|
|
96
|
-
- steps 首步加入 `【规则】阅读 .claude-coder/test_rule.md`
|
|
97
|
-
- 使用 `【P0】【P1】【P2】` 标记优先级,预算不足时可按优先级裁剪
|
|
98
|
-
- 长等待操作使用 `browser_wait_for` 而非轮询 snapshot
|
|
1
|
+
# 任务分解指南
|
|
2
|
+
|
|
3
|
+
> 本文档是 `claude-coder add` 指令的参考文档。
|
|
4
|
+
> ADD Agent 的唯一职责:分解需求为结构化任务,追加到 tasks.json。不实现任何代码。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## tasks.json 格式
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"project": "项目名称",
|
|
13
|
+
"created_at": "2026-02-13",
|
|
14
|
+
"features": [
|
|
15
|
+
{
|
|
16
|
+
"id": "feat-001",
|
|
17
|
+
"category": "backend | frontend | fullstack | infra",
|
|
18
|
+
"priority": 1,
|
|
19
|
+
"description": "功能的简要描述(40字内)",
|
|
20
|
+
"steps": [
|
|
21
|
+
"具体步骤 1",
|
|
22
|
+
"具体步骤 2",
|
|
23
|
+
"端到端测试:验证方法"
|
|
24
|
+
],
|
|
25
|
+
"status": "pending",
|
|
26
|
+
"depends_on": []
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 字段规范
|
|
33
|
+
|
|
34
|
+
| 字段 | 规则 |
|
|
35
|
+
|------|------|
|
|
36
|
+
| `id` | 格式 `feat-NNN`,从已有最大值递增 |
|
|
37
|
+
| `category` | `backend` / `frontend` / `fullstack` / `infra`,准确归类 |
|
|
38
|
+
| `priority` | 数字越小越优先,从已有最大值递增 |
|
|
39
|
+
| `description` | 简明扼要,40 字内,说明"做什么"而非"怎么做" |
|
|
40
|
+
| `steps` | 具体可操作步骤,最后一步必须是可验证的测试命令,单任务不超过 5 步 |
|
|
41
|
+
| `status` | 新增任务一律 `"pending"` |
|
|
42
|
+
| `depends_on` | 引用前置任务的 `id`,形成 DAG(有向无环图),不得循环依赖 |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 任务分解规则
|
|
47
|
+
|
|
48
|
+
### 粒度控制
|
|
49
|
+
|
|
50
|
+
- 每个任务是独立可测试的功能单元,1-3 session 可完成,新增不超 500 行
|
|
51
|
+
- 单任务 steps 不超过 5 步,超过则拆分为多个任务
|
|
52
|
+
- 第一个任务从第一个有业务逻辑的功能开始,不重复脚手架内容
|
|
53
|
+
- 新项目:infra 任务合并为尽量少的条目,不拆碎
|
|
54
|
+
|
|
55
|
+
### 验证命令模板
|
|
56
|
+
|
|
57
|
+
steps 的最后一步必须包含可执行的验证命令:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
API: curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT/path → 200
|
|
61
|
+
文件: grep -q "关键内容" path/to/file && echo "pass"
|
|
62
|
+
构建: npm run build 2>&1 | tail -1 → 无 error
|
|
63
|
+
页面: Playwright MCP snapshot 验证关键元素存在
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 反面案例(禁止出现)
|
|
67
|
+
|
|
68
|
+
- `"实现用户功能"` → 太模糊,应拆为具体接口
|
|
69
|
+
- `"编写测试"` → 测试应内嵌在 steps 末尾,不是独立任务
|
|
70
|
+
- steps 只有 `"实现xxx"` 没有验证步骤
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## requirements.md 处理原则
|
|
75
|
+
|
|
76
|
+
`requirements.md` 是用户的需求输入,**绝对不能修改它**。但"不能改"不等于"必须盲从"。遇到以下情况时,在 `session_result.json` 的 `notes` 中记录问题,按最合理的方式继续分解:
|
|
77
|
+
|
|
78
|
+
| 场景 | 处理方式 |
|
|
79
|
+
|------|----------|
|
|
80
|
+
| 需求自相矛盾 | 记录矛盾,按技术可行的方案分解,说明选择理由 |
|
|
81
|
+
| 需求与已有代码冲突 | 记录冲突,说明重构成本,按现有架构分解,建议用户确认 |
|
|
82
|
+
| 需求太模糊无法执行 | 自行做出合理决策,在 notes 中记录选择,供用户确认 |
|
|
83
|
+
| 需求中途变更 | 记录变更影响,基于最新需求分解 |
|
|
84
|
+
| 需求引用了不可访问的资源 | 记录问题,根据文字描述尽力分解 |
|
|
85
|
+
| 需求指定了不存在的依赖 | 记录问题,使用最接近的可用版本 |
|
|
86
|
+
|
|
87
|
+
**核心原则:不停工、不擅改、留记录。**
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Playwright MCP 测试任务
|
|
92
|
+
|
|
93
|
+
当任务涉及前端或全栈端到端测试,且项目已配置 Playwright MCP 时,测试步骤的详细规范(结构化标签、Smart Snapshot 策略、SSE 等待模式、步骤模板等)统一参见 `.claude-coder/test_rule.md` 第五节(等待策略)和第八节(步骤模板)。
|
|
94
|
+
|
|
95
|
+
此处只列关键原则:
|
|
96
|
+
- steps 首步加入 `【规则】阅读 .claude-coder/test_rule.md`
|
|
97
|
+
- 使用 `【P0】【P1】【P2】` 标记优先级,预算不足时可按优先级裁剪
|
|
98
|
+
- 长等待操作使用 `browser_wait_for` 而非轮询 snapshot
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
你是资深需求分析师,擅长将模糊需求分解为可执行的原子任务。
|
|
2
|
+
这是任务追加 session,不是编码 session。你只分解任务,不实现代码。
|
|
3
|
+
|
|
4
|
+
{{profileContext}}
|
|
5
|
+
{{taskContext}}
|
|
6
|
+
{{recentExamples}}
|
|
7
|
+
项目绝对路径: {{projectRoot}}
|
|
8
|
+
|
|
9
|
+
【方案文件】
|
|
10
|
+
{{planPath}}
|
|
11
|
+
|
|
12
|
+
执行步骤(按顺序,不可跳过):
|
|
13
|
+
1. 读取方案文件({{planPath}}),理解技术方案和任务规划
|
|
14
|
+
2. 读取 .claude-coder/tasks.json 和 .claude-coder/project_profile.json,了解项目现状
|
|
15
|
+
3. 分析方案中的任务列表:识别核心功能点,判断是单任务还是需要拆分为多任务
|
|
16
|
+
4. 检查重复:对比已有任务,避免功能重叠
|
|
17
|
+
5. 确定依赖:新任务的 depends_on 引用已有或新增任务的 id,形成 DAG
|
|
18
|
+
6. 分解任务:按下方任务分解指南的规则,每个任务独立可测试
|
|
19
|
+
7. 追加到 tasks.json,id 和 priority 从已有最大值递增,status: pending
|
|
20
|
+
8. git add -A && git commit -m "chore: add new tasks"
|
|
21
|
+
9. 写入 session_result.json(格式:{ "session_result": "success", "status_before": "N/A", "status_after": "N/A", "notes": "追加了 N 个任务:简述" })
|
|
22
|
+
|
|
23
|
+
{{addGuide}}
|
|
24
|
+
|
|
25
|
+
{{testRuleHint}}
|
|
26
|
+
不修改已有任务,不实现代码。
|