claude-coder 1.8.1 → 1.8.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.
Files changed (47) hide show
  1. package/README.md +167 -159
  2. package/bin/cli.js +171 -158
  3. package/package.json +52 -52
  4. package/src/commands/auth.js +290 -240
  5. package/src/commands/setup-modules/helpers.js +99 -99
  6. package/src/commands/setup-modules/index.js +25 -25
  7. package/src/commands/setup-modules/mcp.js +94 -94
  8. package/src/commands/setup-modules/provider.js +260 -260
  9. package/src/commands/setup-modules/safety.js +61 -61
  10. package/src/commands/setup-modules/simplify.js +52 -52
  11. package/src/commands/setup.js +172 -172
  12. package/src/common/assets.js +236 -206
  13. package/src/common/config.js +125 -125
  14. package/src/common/constants.js +55 -55
  15. package/src/common/indicator.js +222 -222
  16. package/src/common/interaction.js +170 -170
  17. package/src/common/logging.js +77 -77
  18. package/src/common/sdk.js +50 -50
  19. package/src/common/tasks.js +88 -88
  20. package/src/common/utils.js +161 -161
  21. package/src/core/coding.js +55 -55
  22. package/src/core/context.js +117 -117
  23. package/src/core/go.js +310 -0
  24. package/src/core/harness.js +484 -484
  25. package/src/core/hooks.js +533 -532
  26. package/src/core/init.js +171 -163
  27. package/src/core/plan.js +325 -325
  28. package/src/core/prompts.js +227 -226
  29. package/src/core/query.js +49 -49
  30. package/src/core/repair.js +46 -46
  31. package/src/core/runner.js +195 -195
  32. package/src/core/scan.js +89 -89
  33. package/src/core/session.js +56 -56
  34. package/src/core/simplify.js +53 -52
  35. package/templates/bash-process.md +12 -12
  36. package/templates/codingSystem.md +65 -65
  37. package/templates/codingUser.md +17 -17
  38. package/templates/coreProtocol.md +29 -29
  39. package/templates/goSystem.md +130 -0
  40. package/templates/guidance.json +52 -34
  41. package/templates/planSystem.md +78 -78
  42. package/templates/planUser.md +8 -8
  43. package/templates/playwright.md +16 -16
  44. package/templates/requirements.example.md +57 -57
  45. package/templates/scanSystem.md +120 -120
  46. package/templates/scanUser.md +10 -10
  47. package/templates/test_rule.md +194 -194
@@ -1,46 +1,46 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { runSession } = require('./session');
6
- const { buildQueryOptions } = require('./query');
7
- const { log } = require('../common/config');
8
-
9
- /**
10
- * 使用 AI 修复损坏的 JSON 文件
11
- * @param {string} filePath - 文件绝对路径
12
- * @param {object} [opts] - 透传给 runSession 的选项
13
- */
14
- async function repairJsonFile(filePath, opts = {}) {
15
- if (!fs.existsSync(filePath)) return;
16
-
17
- const rawContent = fs.readFileSync(filePath, 'utf8');
18
- if (!rawContent || !rawContent.trim()) return;
19
-
20
- const fileName = path.basename(filePath);
21
- log('info', `正在使用 AI 修复 ${fileName}...`);
22
-
23
- const prompt = `文件 ${filePath} 的 JSON 格式已损坏,请修复并用 Write 工具写入原路径。\n\n当前损坏内容:\n${rawContent}`;
24
-
25
- try {
26
- await runSession('repair', {
27
- opts,
28
- sessionNum: 0,
29
- logFileName: `repair_${fileName.replace('.json', '')}.log`,
30
- label: `repair:${fileName}`,
31
-
32
- async execute(sdk, ctx) {
33
- const queryOpts = buildQueryOptions(ctx.config, opts);
34
- queryOpts.hooks = ctx.hooks;
35
- queryOpts.abortController = ctx.abortController;
36
- await ctx.runQuery(sdk, prompt, queryOpts);
37
- log('ok', `AI 修复 ${fileName} 完成`);
38
- return {};
39
- },
40
- });
41
- } catch (err) {
42
- log('warn', `AI 修复 ${fileName} 失败: ${err.message}`);
43
- }
44
- }
45
-
46
- module.exports = { repairJsonFile };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { runSession } = require('./session');
6
+ const { buildQueryOptions } = require('./query');
7
+ const { log } = require('../common/config');
8
+
9
+ /**
10
+ * 使用 AI 修复损坏的 JSON 文件
11
+ * @param {string} filePath - 文件绝对路径
12
+ * @param {object} [opts] - 透传给 runSession 的选项
13
+ */
14
+ async function repairJsonFile(filePath, opts = {}) {
15
+ if (!fs.existsSync(filePath)) return;
16
+
17
+ const rawContent = fs.readFileSync(filePath, 'utf8');
18
+ if (!rawContent || !rawContent.trim()) return;
19
+
20
+ const fileName = path.basename(filePath);
21
+ log('info', `正在使用 AI 修复 ${fileName}...`);
22
+
23
+ const prompt = `文件 ${filePath} 的 JSON 格式已损坏,请修复并用 Write 工具写入原路径。\n\n当前损坏内容:\n${rawContent}`;
24
+
25
+ try {
26
+ await runSession('repair', {
27
+ opts,
28
+ sessionNum: 0,
29
+ logFileName: `repair_${fileName.replace('.json', '')}.log`,
30
+ label: `repair:${fileName}`,
31
+
32
+ async execute(sdk, ctx) {
33
+ const queryOpts = buildQueryOptions(ctx.config, opts);
34
+ queryOpts.hooks = ctx.hooks;
35
+ queryOpts.abortController = ctx.abortController;
36
+ await ctx.runQuery(sdk, prompt, queryOpts);
37
+ log('ok', `AI 修复 ${fileName} 完成`);
38
+ return {};
39
+ },
40
+ });
41
+ } catch (err) {
42
+ log('warn', `AI 修复 ${fileName} 失败: ${err.message}`);
43
+ }
44
+ }
45
+
46
+ module.exports = { repairJsonFile };
@@ -1,195 +1,195 @@
1
- 'use strict';
2
-
3
- const readline = require('readline');
4
- const { log, loadConfig } = require('../common/config');
5
- const { assets } = require('../common/assets');
6
- const { loadTasks, getFeatures, getStats, printStats } = require('../common/tasks');
7
- const { runCodingSession } = require('./coding');
8
- const { Harness, selectNextTask } = require('./harness');
9
- const { simplify } = require('./simplify');
10
- const { repairJsonFile } = require('./repair');
11
-
12
- // ─── Display Helpers ──────────────────────────────────────────
13
-
14
- function printBanner(dryRun) {
15
- console.log('');
16
- console.log('============================================');
17
- console.log(` Claude Coder${dryRun ? ' (预览模式)' : ''}`);
18
- console.log('============================================');
19
- console.log('');
20
- }
21
-
22
- function printSessionHeader(session, maxSessions) {
23
- console.log('');
24
- console.log('--------------------------------------------');
25
- log('info', `Session ${session} / ${maxSessions}`);
26
- console.log('--------------------------------------------');
27
- }
28
-
29
- function printProgress(taskData) {
30
- const stats = getStats(taskData);
31
- log('info', `进度: ${stats.done}/${stats.total} done, ${stats.in_progress} in_progress, ${stats.testing} testing, ${stats.failed} failed, ${stats.pending} pending`);
32
- }
33
-
34
- function printDryRun(taskData) {
35
- const next = selectNextTask(taskData);
36
- log('info', `[DRY-RUN] 下一个任务: ${next ? `${next.id} - ${next.description}` : '无待处理任务'}`);
37
-
38
- if (!next) {
39
- log('ok', '[DRY-RUN] 无可执行任务,预览结束');
40
- return;
41
- }
42
-
43
- console.log('');
44
- log('info', '[DRY-RUN] 任务队列:');
45
- const features = getFeatures(taskData);
46
- for (const f of features) {
47
- const st = f.status || 'unknown';
48
- const icon = { done: '✓', in_progress: '▸', pending: '○', failed: '✗', testing: '◇' }[st] || '?';
49
- log('info', ` ${icon} [${st.padEnd(11)}] ${f.id} - ${f.description || ''}`);
50
- }
51
- }
52
-
53
- function printEndBanner() {
54
- console.log('');
55
- console.log('============================================');
56
- console.log(' 运行结束');
57
- console.log('============================================');
58
- console.log('');
59
- }
60
-
61
- async function promptContinue() {
62
- if (!process.stdin.isTTY) return true;
63
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
64
- return new Promise(resolve => {
65
- rl.question('是否继续?(y/n) ', answer => {
66
- rl.close();
67
- resolve(/^[Yy]/.test(answer.trim()));
68
- });
69
- });
70
- }
71
-
72
- // ─── Simplify Helper ─────────────────────────────────────────
73
-
74
- async function tryRunSimplify(harness, config, msg, commitMsg) {
75
- log('info', msg || `每 ${config.simplifyInterval} 个成功 session 运行代码审查...`);
76
- try {
77
- await simplify(null, { n: config.simplifyCommits });
78
- harness.afterSimplify(commitMsg);
79
- } catch (err) {
80
- log('warn', `代码审查失败,跳过: ${err.message}`);
81
- }
82
- }
83
-
84
- // ─── Main Orchestration Loop ──────────────────────────────────
85
-
86
- async function run(opts = {}) {
87
- const config = loadConfig();
88
- const harness = new Harness(config);
89
-
90
- harness.ensureEnvironment();
91
-
92
- const dryRun = opts.dryRun || false;
93
- const maxSessions = opts.max || 50;
94
- const pauseEvery = opts.pause ?? 0;
95
-
96
- printBanner(dryRun);
97
-
98
- if (config.provider !== 'claude' && config.baseUrl) {
99
- log('ok', `模型配置已加载: ${config.provider}${config.model ? ` (${config.model})` : ''}`);
100
- }
101
-
102
- const prereq = harness.checkPrerequisites();
103
- if (!prereq.ok) {
104
- log('error', prereq.msg);
105
- process.exit(1);
106
- }
107
-
108
- printStats();
109
-
110
- log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
111
- console.log('');
112
-
113
- let state = { consecutiveFailures: 0, lastFailReason: '' };
114
-
115
- for (let session = 1; session <= maxSessions; session++) {
116
- printSessionHeader(session, maxSessions);
117
-
118
- let taskData = loadTasks();
119
- if (!taskData) {
120
- const tasksPath = assets.path('tasks');
121
- if (tasksPath) await repairJsonFile(tasksPath);
122
- taskData = loadTasks();
123
- if (!taskData) {
124
- log('error', 'tasks.json 无法读取且修复失败,终止循环');
125
- break;
126
- }
127
- }
128
-
129
- if (harness.isAllDone(taskData)) {
130
- if (!dryRun) {
131
- if (harness.needsFinalSimplify()) {
132
- await tryRunSimplify(harness, config, '所有任务完成,运行最终代码审查...', 'style: final simplify');
133
- }
134
- harness.tryPush();
135
- }
136
- console.log('');
137
- log('ok', '所有任务已完成!');
138
- printStats();
139
- break;
140
- }
141
-
142
- printProgress(taskData);
143
-
144
- if (dryRun) {
145
- printDryRun(taskData);
146
- break;
147
- }
148
-
149
- const { headBefore, taskId } = harness.snapshot(taskData);
150
-
151
- const sessionResult = await runCodingSession(session, {
152
- projectRoot: harness.projectRoot,
153
- taskId,
154
- consecutiveFailures: state.consecutiveFailures,
155
- maxSessions,
156
- lastValidateLog: state.lastFailReason,
157
- });
158
-
159
- if (sessionResult.stalled) {
160
- state = await harness.onStall(session, { headBefore, taskId, sessionResult, ...state });
161
- continue;
162
- }
163
-
164
- log('info', '开始 harness 校验 ...');
165
- const validateResult = await harness.validate(headBefore, taskId);
166
-
167
- if (!validateResult.fatal) {
168
- const level = validateResult.hasWarnings ? 'warn' : 'ok';
169
- log(level, `Session ${session} 校验通过${validateResult.hasWarnings ? ' (有警告)' : ''}`);
170
- state = await harness.onSuccess(session, { taskId, sessionResult, validateResult });
171
-
172
- if (harness.shouldSimplify()) {
173
- await tryRunSimplify(harness, config);
174
- }
175
- harness.tryPush();
176
- } else {
177
- state = await harness.onFailure(session, { headBefore, taskId, sessionResult, validateResult, ...state });
178
- }
179
-
180
- if (pauseEvery > 0 && session % pauseEvery === 0) {
181
- console.log('');
182
- printStats();
183
- if (!await promptContinue()) {
184
- log('info', '手动停止');
185
- break;
186
- }
187
- }
188
- }
189
-
190
- harness.cleanup();
191
- printEndBanner();
192
- printStats();
193
- }
194
-
195
- module.exports = { run };
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const { log, loadConfig } = require('../common/config');
5
+ const { assets } = require('../common/assets');
6
+ const { loadTasks, getFeatures, getStats, printStats } = require('../common/tasks');
7
+ const { runCodingSession } = require('./coding');
8
+ const { Harness, selectNextTask } = require('./harness');
9
+ const { simplify } = require('./simplify');
10
+ const { repairJsonFile } = require('./repair');
11
+
12
+ // ─── Display Helpers ──────────────────────────────────────────
13
+
14
+ function printBanner(dryRun) {
15
+ console.log('');
16
+ console.log('============================================');
17
+ console.log(` Claude Coder${dryRun ? ' (预览模式)' : ''}`);
18
+ console.log('============================================');
19
+ console.log('');
20
+ }
21
+
22
+ function printSessionHeader(session, maxSessions) {
23
+ console.log('');
24
+ console.log('--------------------------------------------');
25
+ log('info', `Session ${session} / ${maxSessions}`);
26
+ console.log('--------------------------------------------');
27
+ }
28
+
29
+ function printProgress(taskData) {
30
+ const stats = getStats(taskData);
31
+ log('info', `进度: ${stats.done}/${stats.total} done, ${stats.in_progress} in_progress, ${stats.testing} testing, ${stats.failed} failed, ${stats.pending} pending`);
32
+ }
33
+
34
+ function printDryRun(taskData) {
35
+ const next = selectNextTask(taskData);
36
+ log('info', `[DRY-RUN] 下一个任务: ${next ? `${next.id} - ${next.description}` : '无待处理任务'}`);
37
+
38
+ if (!next) {
39
+ log('ok', '[DRY-RUN] 无可执行任务,预览结束');
40
+ return;
41
+ }
42
+
43
+ console.log('');
44
+ log('info', '[DRY-RUN] 任务队列:');
45
+ const features = getFeatures(taskData);
46
+ for (const f of features) {
47
+ const st = f.status || 'unknown';
48
+ const icon = { done: '✓', in_progress: '▸', pending: '○', failed: '✗', testing: '◇' }[st] || '?';
49
+ log('info', ` ${icon} [${st.padEnd(11)}] ${f.id} - ${f.description || ''}`);
50
+ }
51
+ }
52
+
53
+ function printEndBanner() {
54
+ console.log('');
55
+ console.log('============================================');
56
+ console.log(' 运行结束');
57
+ console.log('============================================');
58
+ console.log('');
59
+ }
60
+
61
+ async function promptContinue() {
62
+ if (!process.stdin.isTTY) return true;
63
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
64
+ return new Promise(resolve => {
65
+ rl.question('是否继续?(y/n) ', answer => {
66
+ rl.close();
67
+ resolve(/^[Yy]/.test(answer.trim()));
68
+ });
69
+ });
70
+ }
71
+
72
+ // ─── Simplify Helper ─────────────────────────────────────────
73
+
74
+ async function tryRunSimplify(harness, config, msg, commitMsg) {
75
+ log('info', msg || `每 ${config.simplifyInterval} 个成功 session 运行代码审查...`);
76
+ try {
77
+ await simplify(null, { n: config.simplifyCommits });
78
+ harness.afterSimplify(commitMsg);
79
+ } catch (err) {
80
+ log('warn', `代码审查失败,跳过: ${err.message}`);
81
+ }
82
+ }
83
+
84
+ // ─── Main Orchestration Loop ──────────────────────────────────
85
+
86
+ async function run(opts = {}) {
87
+ const config = loadConfig();
88
+ const harness = new Harness(config);
89
+
90
+ harness.ensureEnvironment();
91
+
92
+ const dryRun = opts.dryRun || false;
93
+ const maxSessions = opts.max || 50;
94
+ const pauseEvery = opts.pause ?? 0;
95
+
96
+ printBanner(dryRun);
97
+
98
+ if (config.provider !== 'claude' && config.baseUrl) {
99
+ log('ok', `模型配置已加载: ${config.provider}${config.model ? ` (${config.model})` : ''}`);
100
+ }
101
+
102
+ const prereq = harness.checkPrerequisites();
103
+ if (!prereq.ok) {
104
+ log('error', prereq.msg);
105
+ process.exit(1);
106
+ }
107
+
108
+ printStats();
109
+
110
+ log('info', `开始编码循环 (最多 ${maxSessions} 个会话) ...`);
111
+ console.log('');
112
+
113
+ let state = { consecutiveFailures: 0, lastFailReason: '' };
114
+
115
+ for (let session = 1; session <= maxSessions; session++) {
116
+ printSessionHeader(session, maxSessions);
117
+
118
+ let taskData = loadTasks();
119
+ if (!taskData) {
120
+ const tasksPath = assets.path('tasks');
121
+ if (tasksPath) await repairJsonFile(tasksPath);
122
+ taskData = loadTasks();
123
+ if (!taskData) {
124
+ log('error', 'tasks.json 无法读取且修复失败,终止循环');
125
+ break;
126
+ }
127
+ }
128
+
129
+ if (harness.isAllDone(taskData)) {
130
+ if (!dryRun) {
131
+ if (harness.needsFinalSimplify()) {
132
+ await tryRunSimplify(harness, config, '所有任务完成,运行最终代码审查...', 'style: final simplify');
133
+ }
134
+ harness.tryPush();
135
+ }
136
+ console.log('');
137
+ log('ok', '所有任务已完成!');
138
+ printStats();
139
+ break;
140
+ }
141
+
142
+ printProgress(taskData);
143
+
144
+ if (dryRun) {
145
+ printDryRun(taskData);
146
+ break;
147
+ }
148
+
149
+ const { headBefore, taskId } = harness.snapshot(taskData);
150
+
151
+ const sessionResult = await runCodingSession(session, {
152
+ projectRoot: harness.projectRoot,
153
+ taskId,
154
+ consecutiveFailures: state.consecutiveFailures,
155
+ maxSessions,
156
+ lastValidateLog: state.lastFailReason,
157
+ });
158
+
159
+ if (sessionResult.stalled) {
160
+ state = await harness.onStall(session, { headBefore, taskId, sessionResult, ...state });
161
+ continue;
162
+ }
163
+
164
+ log('info', '开始 harness 校验 ...');
165
+ const validateResult = await harness.validate(headBefore, taskId);
166
+
167
+ if (!validateResult.fatal) {
168
+ const level = validateResult.hasWarnings ? 'warn' : 'ok';
169
+ log(level, `Session ${session} 校验通过${validateResult.hasWarnings ? ' (有警告)' : ''}`);
170
+ state = await harness.onSuccess(session, { taskId, sessionResult, validateResult });
171
+
172
+ if (harness.shouldSimplify()) {
173
+ await tryRunSimplify(harness, config);
174
+ }
175
+ harness.tryPush();
176
+ } else {
177
+ state = await harness.onFailure(session, { headBefore, taskId, sessionResult, validateResult, ...state });
178
+ }
179
+
180
+ if (pauseEvery > 0 && session % pauseEvery === 0) {
181
+ console.log('');
182
+ printStats();
183
+ if (!await promptContinue()) {
184
+ log('info', '手动停止');
185
+ break;
186
+ }
187
+ }
188
+ }
189
+
190
+ harness.cleanup();
191
+ printEndBanner();
192
+ printStats();
193
+ }
194
+
195
+ module.exports = { run };
package/src/core/scan.js CHANGED
@@ -1,89 +1,89 @@
1
- 'use strict';
2
-
3
- const { log } = require('../common/config');
4
- const { assets } = require('../common/assets');
5
- const { runSession } = require('./session');
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(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);
45
- const queryOpts = buildQueryOptions(ctx.config, opts);
46
- queryOpts.systemPrompt = buildSystemPrompt('scan');
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(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(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
- };
1
+ 'use strict';
2
+
3
+ const { log } = require('../common/config');
4
+ const { assets } = require('../common/assets');
5
+ const { runSession } = require('./session');
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(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);
45
+ const queryOpts = buildQueryOptions(ctx.config, opts);
46
+ queryOpts.systemPrompt = buildSystemPrompt('scan');
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(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(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
+ };