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.
Files changed (52) hide show
  1. package/README.md +177 -125
  2. package/bin/cli.js +159 -161
  3. package/package.json +52 -47
  4. package/src/commands/auth.js +294 -0
  5. package/src/commands/setup-modules/helpers.js +105 -0
  6. package/src/commands/setup-modules/index.js +26 -0
  7. package/src/commands/setup-modules/mcp.js +95 -0
  8. package/src/commands/setup-modules/provider.js +261 -0
  9. package/src/commands/setup-modules/safety.js +62 -0
  10. package/src/commands/setup-modules/simplify.js +53 -0
  11. package/src/commands/setup.js +172 -0
  12. package/src/common/assets.js +192 -0
  13. package/src/{config.js → common/config.js} +138 -201
  14. package/src/common/constants.js +57 -0
  15. package/src/{indicator.js → common/indicator.js} +222 -217
  16. package/src/common/interaction.js +170 -0
  17. package/src/common/logging.js +77 -0
  18. package/src/common/sdk.js +51 -0
  19. package/src/{tasks.js → common/tasks.js} +157 -172
  20. package/src/common/utils.js +147 -0
  21. package/src/core/base.js +54 -0
  22. package/src/core/coding.js +55 -0
  23. package/src/core/context.js +132 -0
  24. package/src/core/hooks.js +529 -0
  25. package/src/{init.js → core/init.js} +163 -144
  26. package/src/core/plan.js +318 -0
  27. package/src/core/prompts.js +253 -0
  28. package/src/core/query.js +48 -0
  29. package/src/core/repair.js +58 -0
  30. package/src/{runner.js → core/runner.js} +352 -420
  31. package/src/core/scan.js +89 -0
  32. package/src/core/simplify.js +59 -0
  33. package/src/core/validator.js +138 -0
  34. package/{prompts/ADD_GUIDE.md → templates/addGuide.md} +98 -98
  35. package/templates/addUser.md +26 -0
  36. package/{prompts/CLAUDE.md → templates/agentProtocol.md} +195 -199
  37. package/templates/bash-process.md +5 -0
  38. package/{prompts/coding_user.md → templates/codingUser.md} +31 -23
  39. package/templates/guidance.json +35 -0
  40. package/templates/playwright.md +17 -0
  41. package/templates/requirements.example.md +56 -56
  42. package/{prompts/SCAN_PROTOCOL.md → templates/scanProtocol.md} +118 -118
  43. package/{prompts/scan_user.md → templates/scanUser.md} +17 -17
  44. package/templates/test_rule.md +194 -194
  45. package/prompts/add_user.md +0 -24
  46. package/src/auth.js +0 -245
  47. package/src/hooks.js +0 -160
  48. package/src/prompts.js +0 -295
  49. package/src/scanner.js +0 -62
  50. package/src/session.js +0 -352
  51. package/src/setup.js +0 -579
  52. package/src/validator.js +0 -181
@@ -0,0 +1,253 @@
1
+ 'use strict';
2
+
3
+ const { loadConfig } = require('../common/config');
4
+ const { assets } = require('../common/assets');
5
+ const { loadTasks, findNextTask, getStats } = require('../common/tasks');
6
+
7
+ // --------------- System Prompt ---------------
8
+
9
+ function buildSystemPrompt(includeScanProtocol = false) {
10
+ let prompt = assets.read('agentProtocol');
11
+ if (includeScanProtocol) {
12
+ const scan = assets.read('scanProtocol');
13
+ if (scan) prompt += '\n\n' + scan;
14
+ }
15
+ return prompt;
16
+ }
17
+
18
+ // --------------- Hint Builders ---------------
19
+
20
+ function buildMcpHint(config) {
21
+ return config.mcpPlaywright
22
+ ? '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。'
23
+ : '';
24
+ }
25
+
26
+ function buildRetryHint(consecutiveFailures, lastValidateLog) {
27
+ if (consecutiveFailures > 0 && lastValidateLog) {
28
+ return `\n注意:上次会话校验失败,原因:${lastValidateLog}。请避免同样的问题。`;
29
+ }
30
+ return '';
31
+ }
32
+
33
+ function buildEnvHint(consecutiveFailures, sessionNum) {
34
+ if (consecutiveFailures === 0 && sessionNum > 1) {
35
+ return '环境已就绪,第二步可跳过 claude-coder init,仅确认服务存活。涉及新依赖时仍需运行 claude-coder init。';
36
+ }
37
+ return '';
38
+ }
39
+
40
+ function buildTestHint() {
41
+ const testsData = assets.readJson('tests', null);
42
+ if (testsData) {
43
+ const count = (testsData.test_cases || []).length;
44
+ if (count > 0) return `tests.json 已有 ${count} 条验证记录,Step 5 时先查已有记录避免重复验证。`;
45
+ }
46
+ return '';
47
+ }
48
+
49
+ function buildDocsHint() {
50
+ const profile = assets.readJson('profile', null);
51
+ if (!profile) return '';
52
+ let hint = '';
53
+ const docs = profile.existing_docs || [];
54
+ if (docs.length > 0) {
55
+ hint = `项目文档: ${docs.join(', ')}。Step 4 编码前先读与任务相关的文档,了解接口约定和编码规范。完成后若新增了模块或 API,更新对应文档。`;
56
+ }
57
+ if (profile.tech_stack?.backend?.framework &&
58
+ (!profile.services || profile.services.length === 0)) {
59
+ hint += ' 注意:project_profile.json 的 services 为空,请在本次 session 末尾补全 services 数组(command, port, health_check)。';
60
+ }
61
+ if (!docs.length) {
62
+ hint += ' 注意:project_profile.json 的 existing_docs 为空,请在 Step 6 收尾时补全文档列表。';
63
+ }
64
+ return hint;
65
+ }
66
+
67
+ function buildTaskHint(projectRoot) {
68
+ try {
69
+ const taskData = loadTasks();
70
+ if (!taskData) return '';
71
+ const next = findNextTask(taskData);
72
+ const stats = getStats(taskData);
73
+ if (next) {
74
+ return `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
75
+ `category=${next.category}, steps=${next.steps.length}步。` +
76
+ `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
77
+ `项目绝对路径: ${projectRoot}。运行时目录: ${projectRoot}/.claude-coder/(隐藏目录)。` +
78
+ `第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
79
+ }
80
+ } catch { /* ignore */ }
81
+ return '';
82
+ }
83
+
84
+ function buildTestEnvHint(projectRoot) {
85
+ if (assets.exists('testEnv')) {
86
+ return `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source ${projectRoot}/.claude-coder/test.env 加载。发现新凭证需求时可追加写入(KEY=value 格式)。`;
87
+ }
88
+ return `如需持久化测试凭证(API Key、测试账号密码等),写入 ${projectRoot}/.claude-coder/test.env(KEY=value 格式,每行一个)。后续 session 会自动感知。`;
89
+ }
90
+
91
+ function buildPlaywrightAuthHint(config) {
92
+ if (!config.mcpPlaywright) return '';
93
+ const mode = config.playwrightMode;
94
+ switch (mode) {
95
+ case 'persistent':
96
+ return 'Playwright MCP 使用 persistent 模式(user-data-dir),浏览器登录状态持久保存在本地配置中,无需额外登录操作。';
97
+ case 'isolated':
98
+ return assets.exists('playwrightAuth')
99
+ ? 'Playwright MCP 使用 isolated 模式,已检测到登录状态文件(playwright-auth.json),每次会话自动加载 cookies 和 localStorage。'
100
+ : 'Playwright MCP 使用 isolated 模式,但未检测到登录状态文件。如目标页面需要登录,请先运行 claude-coder auth <URL>。';
101
+ case 'extension':
102
+ return 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用浏览器已有的登录态和扩展。注意:操作会影响用户正在使用的浏览器。';
103
+ default:
104
+ return '';
105
+ }
106
+ }
107
+
108
+ function buildMemoryHint() {
109
+ const sr = assets.readJson('sessionResult', null);
110
+ if (sr?.session_result) {
111
+ return `上次会话: ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})` +
112
+ (sr.notes ? `, 要点: ${sr.notes.slice(0, 150)}` : '') + '。';
113
+ }
114
+ return '';
115
+ }
116
+
117
+ function buildServiceHint(maxSessions) {
118
+ return maxSessions === 1
119
+ ? '单次模式:收尾时停止所有后台服务。'
120
+ : '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
121
+ }
122
+
123
+ // --------------- Coding Session ---------------
124
+
125
+ function buildCodingPrompt(sessionNum, opts = {}) {
126
+ const config = loadConfig();
127
+ const consecutiveFailures = opts.consecutiveFailures || 0;
128
+ const projectRoot = assets.projectRoot;
129
+
130
+ return assets.render('codingUser', {
131
+ sessionNum,
132
+ mcpHint: buildMcpHint(config),
133
+ retryContext: buildRetryHint(consecutiveFailures, opts.lastValidateLog),
134
+ envHint: buildEnvHint(consecutiveFailures, sessionNum),
135
+ testHint: buildTestHint(),
136
+ docsHint: buildDocsHint(),
137
+ taskHint: buildTaskHint(projectRoot),
138
+ testEnvHint: buildTestEnvHint(projectRoot),
139
+ playwrightAuthHint: buildPlaywrightAuthHint(config),
140
+ memoryHint: buildMemoryHint(),
141
+ serviceHint: buildServiceHint(opts.maxSessions || 50),
142
+ });
143
+ }
144
+
145
+ // --------------- Scan Session ---------------
146
+
147
+ function buildScanPrompt(projectType, requirement) {
148
+ const requirementLine = requirement
149
+ ? `用户需求概述: ${requirement.slice(0, 500)}`
150
+ : '';
151
+
152
+ return assets.render('scanUser', {
153
+ projectType,
154
+ requirement: requirementLine,
155
+ });
156
+ }
157
+
158
+ // --------------- Plan Session ---------------
159
+
160
+ function buildPlanSystemPrompt() {
161
+ return '你是一个任务分解专家,擅长将模糊需求拆解为结构化、可执行的原子任务。你只分析需求和分解任务,不实现任何代码。';
162
+ }
163
+
164
+ function buildPlanPrompt(planPath) {
165
+ const projectRoot = assets.projectRoot;
166
+ const addGuide = assets.read('addGuide');
167
+
168
+ let profileContext = '';
169
+ const profile = assets.readJson('profile', null);
170
+ if (profile) {
171
+ const stack = profile.tech_stack || {};
172
+ const parts = [];
173
+ if (stack.backend?.framework) parts.push(`后端: ${stack.backend.framework}`);
174
+ if (stack.frontend?.framework) parts.push(`前端: ${stack.frontend.framework}`);
175
+ if (stack.backend?.language) parts.push(`语言: ${stack.backend.language}`);
176
+ if (parts.length) profileContext = `项目技术栈: ${parts.join(', ')}`;
177
+ }
178
+
179
+ let taskContext = '';
180
+ let recentExamples = '';
181
+ try {
182
+ const taskData = loadTasks();
183
+ if (taskData) {
184
+ const stats = getStats(taskData);
185
+ const features = taskData.features || [];
186
+ const maxId = features.length ? features[features.length - 1].id : 'feat-000';
187
+ const maxPriority = features.length ? Math.max(...features.map(f => f.priority || 0)) : 0;
188
+ const categories = [...new Set(features.map(f => f.category))].join(', ');
189
+
190
+ taskContext = `已有 ${stats.total} 个任务(${stats.done} done, ${stats.pending} pending, ${stats.failed} failed)。` +
191
+ `最大 id: ${maxId}, 最大 priority: ${maxPriority}。已有 category: ${categories}。`;
192
+
193
+ const recent = features.slice(-3);
194
+ if (recent.length) {
195
+ recentExamples = '已有任务格式参考(保持一致性):\n' +
196
+ recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${(f.steps || []).length}步, depends_on=[${(f.depends_on || []).join(',')}])`).join('\n');
197
+ }
198
+ }
199
+ } catch { /* ignore */ }
200
+
201
+ let testRuleHint = '';
202
+ if (assets.exists('testRule') && assets.exists('mcpConfig')) {
203
+ testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
204
+ '`.claude-coder/assets/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。端到端测试任务请参考 test_rule.md。';
205
+ }
206
+
207
+ return assets.render('addUser', {
208
+ profileContext,
209
+ taskContext,
210
+ recentExamples,
211
+ projectRoot,
212
+ addGuide,
213
+ testRuleHint,
214
+ planPath,
215
+ });
216
+ }
217
+
218
+ // --------------- Archive Prompt ---------------
219
+
220
+ function buildArchivePrompt(doneTasks, milestones, tasksPath) {
221
+ const nextId = String((milestones || []).length + 1).padStart(3, '0');
222
+ const doneList = doneTasks.map(f => `- ${f.id}: ${f.description}`).join('\n');
223
+ const existing = milestones && milestones.length > 0
224
+ ? `已有里程碑示例:\n${JSON.stringify(milestones.slice(-2), null, 2)}`
225
+ : '目前无已有里程碑';
226
+
227
+ return `维护 tasks.json 文件:将已完成任务归档为里程碑。
228
+
229
+ 文件路径: ${tasksPath}
230
+
231
+ ${existing}
232
+
233
+ 需要归档的 ${doneTasks.length} 个 done 任务:
234
+ ${doneList}
235
+
236
+ 操作要求:
237
+ 1. 在 completed_milestones 数组末尾追加一条记录,格式: { "id": "${nextId}", "summary": "5-20字的精简概述" }
238
+ 2. summary 要概括这批任务的核心成果,不要机械拼接描述
239
+ 3. 从 features 数组中移除所有 status 为 "done" 的任务
240
+ 4. 保持文件中其他字段不变
241
+ 5. 用 Write 工具写回 ${tasksPath}`;
242
+ }
243
+
244
+ // --------------- Exports ---------------
245
+
246
+ module.exports = {
247
+ buildSystemPrompt,
248
+ buildCodingPrompt,
249
+ buildScanPrompt,
250
+ buildPlanSystemPrompt,
251
+ buildPlanPrompt,
252
+ buildArchivePrompt,
253
+ };
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { buildEnvVars, getAllowedTools } = require('../common/config');
6
+ const { assets } = require('../common/assets');
7
+
8
+ /**
9
+ * 检查项目是否包含代码文件
10
+ */
11
+ function hasCodeFiles(projectRoot) {
12
+ const markers = [
13
+ 'package.json', 'pyproject.toml', 'requirements.txt', 'setup.py',
14
+ 'Cargo.toml', 'go.mod', 'pom.xml', 'build.gradle',
15
+ 'Makefile', 'Dockerfile', 'docker-compose.yml',
16
+ 'README.md', 'main.py', 'app.py', 'index.js', 'index.ts',
17
+ ];
18
+ for (const m of markers) {
19
+ if (fs.existsSync(path.join(projectRoot, m))) return true;
20
+ }
21
+ for (const d of ['src', 'lib', 'app', 'backend', 'frontend', 'web', 'server', 'client']) {
22
+ if (fs.existsSync(path.join(projectRoot, d)) && fs.statSync(path.join(projectRoot, d)).isDirectory()) return true;
23
+ }
24
+ return false;
25
+ }
26
+
27
+ /**
28
+ * 构建 SDK query 选项
29
+ */
30
+ function buildQueryOptions(config, opts = {}) {
31
+ const base = {
32
+ allowedTools: getAllowedTools(config),
33
+ permissionMode: 'bypassPermissions',
34
+ allowDangerouslySkipPermissions: true,
35
+ cwd: opts.projectRoot || assets.projectRoot,
36
+ env: buildEnvVars(config),
37
+ settingSources: ['project'],
38
+ };
39
+ if (config.maxTurns > 0) base.maxTurns = config.maxTurns;
40
+ if (opts.model) base.model = opts.model;
41
+ else if (config.model) base.model = config.model;
42
+ return base;
43
+ }
44
+
45
+ module.exports = {
46
+ hasCodeFiles,
47
+ buildQueryOptions,
48
+ };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { runSession } = require('./base');
6
+ const { buildQueryOptions } = require('./query');
7
+ const { log } = require('../common/config');
8
+
9
+ /**
10
+ * 通用 AI 文件修复/维护工具
11
+ * 可嵌入 runner.js、plan.js 及任何需要 AI 修复文件的场景
12
+ *
13
+ * @param {string} filePath - 待修复文件的绝对路径
14
+ * @param {object} [opts] - 选项
15
+ * @param {string} [opts.prompt] - 自定义 prompt(省略则使用默认 JSON 修复 prompt)
16
+ * @param {string} [opts.model] - 模型覆盖
17
+ * @returns {Promise<{success: boolean}>}
18
+ */
19
+ async function repairFile(filePath, opts = {}) {
20
+ if (!fs.existsSync(filePath)) {
21
+ log('error', `修复目标不存在: ${filePath}`);
22
+ return { success: false };
23
+ }
24
+
25
+ const content = fs.readFileSync(filePath, 'utf8');
26
+ const filename = path.basename(filePath);
27
+
28
+ const defaultPrompt = `以下文件出现格式错误,请修复并用 Write 工具写回原路径。
29
+ 只修复格式问题(JSON 语法、截断、尾逗号等),不改变数据内容。
30
+
31
+ 文件路径: ${filePath}
32
+ 当前内容:
33
+ \`\`\`
34
+ ${content.slice(0, 30000)}
35
+ \`\`\`
36
+
37
+ 修复后用 Write 写回 ${filePath}`;
38
+
39
+ const prompt = opts.prompt || defaultPrompt;
40
+ const ts = Date.now();
41
+
42
+ return runSession('repair', {
43
+ opts,
44
+ sessionNum: 0,
45
+ logFileName: `repair_${filename}_${ts}.log`,
46
+ label: `repair ${filename}`,
47
+
48
+ async execute(sdk, ctx) {
49
+ const queryOpts = buildQueryOptions(ctx.config, opts);
50
+ queryOpts.hooks = ctx.hooks;
51
+ queryOpts.abortController = ctx.abortController;
52
+ await ctx.runQuery(sdk, prompt, queryOpts);
53
+ return { success: true };
54
+ },
55
+ });
56
+ }
57
+
58
+ module.exports = { repairFile };