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
package/src/hooks.js DELETED
@@ -1,160 +0,0 @@
1
- 'use strict';
2
-
3
- const { inferPhaseStep } = require('./indicator');
4
- const { log } = require('./config');
5
-
6
- const DEFAULT_EDIT_THRESHOLD = 15;
7
- const SESSION_RESULT_FILENAME = 'session_result.json';
8
-
9
- function logToolCall(logStream, input) {
10
- if (!logStream) return;
11
- const target = input.tool_input?.file_path || input.tool_input?.path || '';
12
- const cmd = input.tool_input?.command || '';
13
- const pattern = input.tool_input?.pattern || '';
14
- const detail = target || cmd.slice(0, 200) || (pattern ? `pattern: ${pattern}` : '');
15
- if (detail) {
16
- logStream.write(`[${new Date().toISOString()}] ${input.tool_name}: ${detail}\n`);
17
- }
18
- }
19
-
20
- /**
21
- * Detect whether a tool call writes to session_result.json.
22
- * Covers both the Write tool (exact path match) and Bash redirect writes.
23
- */
24
- function isSessionResultWrite(toolName, toolInput) {
25
- if (toolName === 'Write') {
26
- const target = toolInput?.file_path || toolInput?.path || '';
27
- return target.endsWith(SESSION_RESULT_FILENAME);
28
- }
29
- if (toolName === 'Bash') {
30
- const cmd = toolInput?.command || '';
31
- if (!cmd.includes(SESSION_RESULT_FILENAME)) return false;
32
- return />\s*[^\s]*session_result/.test(cmd);
33
- }
34
- return false;
35
- }
36
-
37
- /**
38
- * Create unified session hooks with stall detection, edit guard,
39
- * and completion detection (auto-shorten timeout after session_result written).
40
- * @param {Indicator} indicator
41
- * @param {WriteStream|null} logStream
42
- * @param {object} [options]
43
- * @param {boolean} [options.enableStallDetection=false]
44
- * @param {number} [options.stallTimeoutMs=1200000]
45
- * @param {AbortController} [options.abortController]
46
- * @param {boolean} [options.enableEditGuard=false]
47
- * @param {boolean} [options.enableCompletionDetection=true]
48
- * @param {number} [options.completionTimeoutMs=300000]
49
- * @returns {{ hooks: object, cleanup: () => void, isStalled: () => boolean }}
50
- */
51
- function createSessionHooks(indicator, logStream, options = {}) {
52
- const {
53
- enableStallDetection = false,
54
- stallTimeoutMs = 1200000,
55
- abortController = null,
56
- enableEditGuard = false,
57
- editThreshold = DEFAULT_EDIT_THRESHOLD,
58
- enableCompletionDetection = true,
59
- completionTimeoutMs = 300000,
60
- } = options;
61
-
62
- const editCounts = {};
63
- let stallDetected = false;
64
- let stallChecker = null;
65
- let completionDetectedAt = 0;
66
-
67
- if (enableStallDetection) {
68
- stallChecker = setInterval(() => {
69
- const now = Date.now();
70
- const idleMs = now - indicator.lastToolTime;
71
-
72
- if (completionDetectedAt > 0) {
73
- const sinceCompletion = now - completionDetectedAt;
74
- if (sinceCompletion > completionTimeoutMs && !stallDetected) {
75
- stallDetected = true;
76
- const secSince = Math.floor(sinceCompletion / 1000);
77
- log('warn', `session_result 已写入 ${secSince} 秒前,模型未自行终止,自动中断`);
78
- if (logStream) {
79
- logStream.write(`[${new Date().toISOString()}] COMPLETION_TIMEOUT: session_result 写入后 ${secSince}s 无终止,自动中断\n`);
80
- }
81
- if (abortController) {
82
- abortController.abort();
83
- log('warn', '已发送中断信号(完成检测)');
84
- }
85
- return;
86
- }
87
- }
88
-
89
- if (idleMs > stallTimeoutMs && !stallDetected) {
90
- stallDetected = true;
91
- const idleMin = Math.floor(idleMs / 60000);
92
- log('warn', `无新工具调用超过 ${idleMin} 分钟,自动中断 session`);
93
- if (logStream) {
94
- logStream.write(`[${new Date().toISOString()}] STALL: 无工具调用 ${idleMin} 分钟,自动中断\n`);
95
- }
96
- if (abortController) {
97
- abortController.abort();
98
- log('warn', '已发送中断信号');
99
- }
100
- }
101
- }, 30000);
102
- }
103
-
104
- const hooks = {
105
- PreToolUse: [{
106
- matcher: '*',
107
- hooks: [async (input) => {
108
- inferPhaseStep(indicator, input.tool_name, input.tool_input);
109
- logToolCall(logStream, input);
110
-
111
- if (enableEditGuard) {
112
- const target = input.tool_input?.file_path || input.tool_input?.path || '';
113
- if (['Write', 'Edit', 'MultiEdit'].includes(input.tool_name) && target) {
114
- editCounts[target] = (editCounts[target] || 0) + 1;
115
- if (editCounts[target] > editThreshold) {
116
- return {
117
- hookSpecificOutput: {
118
- hookEventName: 'PreToolUse',
119
- permissionDecision: 'deny',
120
- permissionDecisionReason: `已对 ${target} 编辑 ${editCounts[target]} 次,疑似死循环。请重新审视方案后再继续。`,
121
- },
122
- };
123
- }
124
- }
125
- }
126
-
127
- return {};
128
- }]
129
- }],
130
- PostToolUse: [{
131
- matcher: '*',
132
- hooks: [async (input) => {
133
- indicator.updatePhase('thinking');
134
- indicator.updateStep('');
135
- indicator.toolTarget = '';
136
-
137
- if (enableCompletionDetection && !completionDetectedAt) {
138
- if (isSessionResultWrite(input.tool_name, input.tool_input)) {
139
- completionDetectedAt = Date.now();
140
- const shortMin = Math.ceil(completionTimeoutMs / 60000);
141
- log('info', `检测到 session_result 写入,${shortMin} 分钟内模型未终止将自动中断`);
142
- if (logStream) {
143
- logStream.write(`[${new Date().toISOString()}] COMPLETION_DETECTED: session_result.json written, ${shortMin}min grace period\n`);
144
- }
145
- }
146
- }
147
-
148
- return {};
149
- }]
150
- }],
151
- };
152
-
153
- return {
154
- hooks,
155
- cleanup() { if (stallChecker) clearInterval(stallChecker); },
156
- isStalled() { return stallDetected; },
157
- };
158
- }
159
-
160
- module.exports = { createSessionHooks };
package/src/prompts.js DELETED
@@ -1,295 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { paths, loadConfig, getProjectRoot } = require('./config');
6
- const { loadTasks, findNextTask, getStats } = require('./tasks');
7
-
8
- // --------------- Template Engine ---------------
9
-
10
- /**
11
- * Replace {{key}} placeholders with values from vars object.
12
- * - Uses Object.prototype.hasOwnProperty.call to prevent prototype pollution
13
- * - Coerces values to string via String() for type safety
14
- * - Collapses 3+ consecutive newlines (from empty variables) into double newline
15
- * - Only matches \w+ inside {{ }}, so JSON braces and %{...} are safe
16
- */
17
- function renderTemplate(template, vars = {}) {
18
- return template
19
- .replace(/\{\{(\w+)\}\}/g, (_, key) =>
20
- Object.prototype.hasOwnProperty.call(vars, key) ? String(vars[key]) : ''
21
- )
22
- .replace(/^\s+$/gm, '') // remove lines that became whitespace-only after replacement
23
- .replace(/\n{3,}/g, '\n\n') // collapse 3+ consecutive newlines into double
24
- .trim();
25
- }
26
-
27
- /**
28
- * Read a prompt template file and render it with variables.
29
- * Falls back to empty string if file doesn't exist.
30
- */
31
- function loadAndRender(filepath, vars = {}) {
32
- if (!fs.existsSync(filepath)) return '';
33
- const template = fs.readFileSync(filepath, 'utf8');
34
- return renderTemplate(template, vars);
35
- }
36
-
37
- // --------------- System Prompt ---------------
38
-
39
- /**
40
- * Build system prompt by combining prompt files.
41
- * CLAUDE.md and SCAN_PROTOCOL.md are read as-is (no variable injection).
42
- */
43
- function buildSystemPrompt(includeScanProtocol = false) {
44
- const p = paths();
45
- let prompt = fs.readFileSync(p.claudeMd, 'utf8');
46
- if (includeScanProtocol && fs.existsSync(p.scanProtocol)) {
47
- prompt += '\n\n' + fs.readFileSync(p.scanProtocol, 'utf8');
48
- }
49
- return prompt;
50
- }
51
-
52
- // --------------- Coding Session ---------------
53
-
54
- /**
55
- * Build user prompt for coding sessions.
56
- * Computes conditional hints, then injects them into coding_user.md template.
57
- */
58
- function buildCodingPrompt(sessionNum, opts = {}) {
59
- const p = paths();
60
- const config = loadConfig();
61
- const consecutiveFailures = opts.consecutiveFailures || 0;
62
- const projectRoot = getProjectRoot();
63
-
64
- // Hint 1: Playwright MCP availability
65
- const mcpHint = config.mcpPlaywright
66
- ? '前端/全栈任务可用 Playwright MCP(browser_navigate、browser_snapshot、browser_click 等)做端到端测试。'
67
- : '';
68
-
69
- // Hint 2: Retry context from previous failures
70
- let retryContext = '';
71
- if (consecutiveFailures > 0 && opts.lastValidateLog) {
72
- retryContext = `\n注意:上次会话校验失败,原因:${opts.lastValidateLog}。请避免同样的问题。`;
73
- }
74
-
75
- // Hint 3: Environment readiness
76
- let envHint = '';
77
- if (consecutiveFailures === 0 && sessionNum > 1) {
78
- envHint = '环境已就绪,第二步可跳过 claude-coder init,仅确认服务存活。涉及新依赖时仍需运行 claude-coder init。';
79
- }
80
-
81
- // Hint 4: Existing test records
82
- let testHint = '';
83
- if (fs.existsSync(p.testsFile)) {
84
- try {
85
- const count = (JSON.parse(fs.readFileSync(p.testsFile, 'utf8')).test_cases || []).length;
86
- if (count > 0) testHint = `tests.json 已有 ${count} 条验证记录,Step 5 时先查已有记录避免重复验证。`;
87
- } catch { /* ignore */ }
88
- }
89
-
90
- // Hint 5: Project documentation awareness + profile quality check
91
- let docsHint = '';
92
- if (fs.existsSync(p.profile)) {
93
- try {
94
- const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
95
- const docs = profile.existing_docs || [];
96
- if (docs.length > 0) {
97
- docsHint = `项目文档: ${docs.join(', ')}。Step 4 编码前先读与任务相关的文档,了解接口约定和编码规范。完成后若新增了模块或 API,更新对应文档。`;
98
- }
99
- if (profile.tech_stack?.backend?.framework &&
100
- (!profile.services || profile.services.length === 0)) {
101
- docsHint += ' 注意:project_profile.json 的 services 为空,请在本次 session 末尾补全 services 数组(command, port, health_check)。';
102
- }
103
- if (!docs.length) {
104
- docsHint += ' 注意:project_profile.json 的 existing_docs 为空,请在 Step 6 收尾时补全文档列表。';
105
- }
106
- } catch { /* ignore */ }
107
- }
108
-
109
- // Hint 6: Task context (harness pre-read, saves Agent 2-3 Read calls)
110
- let taskHint = '';
111
- try {
112
- const taskData = loadTasks();
113
- if (taskData) {
114
- const next = findNextTask(taskData);
115
- const stats = getStats(taskData);
116
- if (next) {
117
- taskHint = `任务上下文: ${next.id} "${next.description}" (${next.status}), ` +
118
- `category=${next.category}, steps=${next.steps.length}步。` +
119
- `进度: ${stats.done}/${stats.total} done, ${stats.failed} failed。` +
120
- `项目绝对路径: ${projectRoot}。运行时目录: ${projectRoot}/.claude-coder/(隐藏目录)。` +
121
- `第一步无需读取 tasks.json(已注入),直接确认任务后进入 Step 2。`;
122
- }
123
- }
124
- } catch { /* ignore */ }
125
-
126
- // Hint 6b: Test environment variables (readable + writable by Agent)
127
- let testEnvHint = '';
128
- if (p.testEnvFile && fs.existsSync(p.testEnvFile)) {
129
- testEnvHint = `测试凭证文件: ${projectRoot}/.claude-coder/test.env(含 API Key、测试账号等),测试前用 source ${projectRoot}/.claude-coder/test.env 加载。发现新凭证需求时可追加写入(KEY=value 格式)。`;
130
- } else {
131
- testEnvHint = `如需持久化测试凭证(API Key、测试账号密码等),写入 ${projectRoot}/.claude-coder/test.env(KEY=value 格式,每行一个)。后续 session 会自动感知。`;
132
- }
133
-
134
- // Hint 6c: Playwright mode awareness
135
- let playwrightAuthHint = '';
136
- if (config.mcpPlaywright) {
137
- const mode = config.playwrightMode;
138
- switch (mode) {
139
- case 'persistent':
140
- playwrightAuthHint = 'Playwright MCP 使用 persistent 模式(user-data-dir),浏览器登录状态持久保存在本地配置中,无需额外登录操作。';
141
- break;
142
- case 'isolated':
143
- playwrightAuthHint = fs.existsSync(p.playwrightAuth)
144
- ? `Playwright MCP 使用 isolated 模式,已检测到登录状态文件(playwright-auth.json),每次会话自动加载 cookies 和 localStorage。`
145
- : 'Playwright MCP 使用 isolated 模式,但未检测到登录状态文件。如目标页面需要登录,请先运行 claude-coder auth <URL>。';
146
- break;
147
- case 'extension':
148
- playwrightAuthHint = 'Playwright MCP 使用 extension 模式,已连接用户真实浏览器,直接复用浏览器已有的登录态和扩展。注意:操作会影响用户正在使用的浏览器。';
149
- break;
150
- }
151
- }
152
-
153
- // Hint 7: Session memory (read flat session_result.json)
154
- let memoryHint = '';
155
- if (fs.existsSync(p.sessionResult)) {
156
- try {
157
- const sr = JSON.parse(fs.readFileSync(p.sessionResult, 'utf8'));
158
- if (sr?.session_result) {
159
- memoryHint = `上次会话: ${sr.session_result}(${sr.status_before || '?'} → ${sr.status_after || '?'})` +
160
- (sr.notes ? `, 要点: ${sr.notes.slice(0, 150)}` : '') + '。';
161
- }
162
- } catch { /* ignore */ }
163
- }
164
-
165
- // Hint 8: Service management (continuous vs single-shot mode)
166
- const maxSessions = opts.maxSessions || 50;
167
- const serviceHint = maxSessions === 1
168
- ? '单次模式:收尾时停止所有后台服务。'
169
- : '连续模式:收尾时不要停止后台服务,保持服务运行以便下个 session 继续使用。';
170
-
171
- return loadAndRender(p.codingUser, {
172
- sessionNum,
173
- mcpHint,
174
- retryContext,
175
- envHint,
176
- testHint,
177
- docsHint,
178
- taskHint,
179
- testEnvHint,
180
- playwrightAuthHint,
181
- memoryHint,
182
- serviceHint,
183
- });
184
- }
185
-
186
- // --------------- Scan Session ---------------
187
-
188
- /**
189
- * Build user prompt for scan sessions.
190
- * Scan only generates profile — task decomposition is handled by add session.
191
- */
192
- function buildScanPrompt(projectType, requirement) {
193
- const p = paths();
194
- const requirementLine = requirement
195
- ? `用户需求概述: ${requirement.slice(0, 500)}`
196
- : '';
197
-
198
- return loadAndRender(p.scanUser, {
199
- projectType,
200
- requirement: requirementLine,
201
- });
202
- }
203
-
204
- // --------------- Add Session ---------------
205
-
206
- /**
207
- * Build lightweight system prompt for add sessions.
208
- * CLAUDE.md is NOT injected to avoid role conflict and save ~2000 tokens.
209
- */
210
- function buildAddSystemPrompt() {
211
- return '你是一个任务分解专家,擅长将模糊需求拆解为结构化、可执行的原子任务。你只分析需求和分解任务,不实现任何代码。';
212
- }
213
-
214
- /**
215
- * Build user prompt for add sessions.
216
- * Structure: Role (primacy) → Dynamic context → ADD_GUIDE.md (reference) → Instruction (recency)
217
- */
218
- function buildAddPrompt(instruction) {
219
- const p = paths();
220
- const projectRoot = getProjectRoot();
221
-
222
- // --- Load ADD_GUIDE.md reference document ---
223
- let addGuide = '';
224
- if (fs.existsSync(p.addGuide)) {
225
- addGuide = fs.readFileSync(p.addGuide, 'utf8');
226
- }
227
-
228
- // --- Context injection: project tech stack ---
229
- let profileContext = '';
230
- if (fs.existsSync(p.profile)) {
231
- try {
232
- const profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
233
- const stack = profile.tech_stack || {};
234
- const parts = [];
235
- if (stack.backend?.framework) parts.push(`后端: ${stack.backend.framework}`);
236
- if (stack.frontend?.framework) parts.push(`前端: ${stack.frontend.framework}`);
237
- if (stack.backend?.language) parts.push(`语言: ${stack.backend.language}`);
238
- if (parts.length) profileContext = `项目技术栈: ${parts.join(', ')}`;
239
- } catch { /* ignore */ }
240
- }
241
-
242
- // --- Context injection: existing tasks summary ---
243
- let taskContext = '';
244
- let recentExamples = '';
245
- try {
246
- const taskData = loadTasks();
247
- if (taskData) {
248
- const stats = getStats(taskData);
249
- const features = taskData.features || [];
250
- const maxId = features.length ? features[features.length - 1].id : 'feat-000';
251
- const maxPriority = features.length ? Math.max(...features.map(f => f.priority || 0)) : 0;
252
- const categories = [...new Set(features.map(f => f.category))].join(', ');
253
-
254
- taskContext = `已有 ${stats.total} 个任务(${stats.done} done, ${stats.pending} pending, ${stats.failed} failed)。` +
255
- `最大 id: ${maxId}, 最大 priority: ${maxPriority}。已有 category: ${categories}。`;
256
-
257
- const recent = features.slice(-3);
258
- if (recent.length) {
259
- recentExamples = '已有任务格式参考(保持一致性):\n' +
260
- recent.map(f => ` ${f.id}: "${f.description}" (category=${f.category}, steps=${f.steps.length}步, depends_on=[${f.depends_on.join(',')}])`).join('\n');
261
- }
262
- }
263
- } catch { /* ignore */ }
264
-
265
- // --- Conditional: Playwright test rule hint ---
266
- let testRuleHint = '';
267
- const testRulePath = path.join(p.loopDir, 'test_rule.md');
268
- const hasMcp = fs.existsSync(p.mcpConfig);
269
- if (fs.existsSync(testRulePath) && hasMcp) {
270
- testRuleHint = '【Playwright 测试规则】项目已配置 Playwright MCP(.mcp.json),' +
271
- '`.claude-coder/test_rule.md` 包含测试规范(Smart Snapshot、等待策略、步骤模板等)。端到端测试任务请参考 test_rule.md。';
272
- }
273
-
274
- return loadAndRender(p.addUser, {
275
- profileContext,
276
- taskContext,
277
- recentExamples,
278
- projectRoot,
279
- addGuide,
280
- testRuleHint,
281
- instruction,
282
- });
283
- }
284
-
285
- // --------------- Exports ---------------
286
-
287
- module.exports = {
288
- renderTemplate,
289
- loadAndRender,
290
- buildSystemPrompt,
291
- buildCodingPrompt,
292
- buildScanPrompt,
293
- buildAddSystemPrompt,
294
- buildAddPrompt,
295
- };
package/src/scanner.js DELETED
@@ -1,62 +0,0 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const { paths, log, ensureLoopDir } = require('./config');
5
- const { runScanSession } = require('./session');
6
-
7
- function validateProfile() {
8
- const p = paths();
9
- if (!fs.existsSync(p.profile)) return { valid: false, issues: ['profile 不存在'] };
10
-
11
- let profile;
12
- try {
13
- profile = JSON.parse(fs.readFileSync(p.profile, 'utf8'));
14
- } catch {
15
- return { valid: false, issues: ['profile JSON 格式错误'] };
16
- }
17
-
18
- const issues = [];
19
-
20
- if (!profile.tech_stack?.backend?.framework && !profile.tech_stack?.frontend?.framework) {
21
- issues.push('tech_stack 缺少 backend 或 frontend 框架');
22
- }
23
- if (profile.tech_stack?.backend?.framework &&
24
- (!profile.services || profile.services.length === 0)) {
25
- issues.push('有后端框架但 services 为空(缺少启动命令和端口)');
26
- }
27
- if (!profile.existing_docs || profile.existing_docs.length === 0) {
28
- issues.push('existing_docs 为空(至少需要 README.md)');
29
- }
30
-
31
- return { valid: issues.length === 0, issues };
32
- }
33
-
34
- async function scan(requirement, opts = {}) {
35
- const p = paths();
36
- ensureLoopDir();
37
-
38
- const maxAttempts = 3;
39
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
40
- log('info', `初始化尝试 ${attempt} / ${maxAttempts} ...`);
41
-
42
- const result = await runScanSession(requirement, opts);
43
-
44
- if (fs.existsSync(p.profile)) {
45
- const profileCheck = validateProfile();
46
- if (!profileCheck.valid) {
47
- log('warn', `profile 质量问题: ${profileCheck.issues.join('; ')}`);
48
- }
49
- log('ok', '项目扫描完成');
50
- return { success: true, cost: result.cost };
51
- }
52
-
53
- if (attempt < maxAttempts) {
54
- log('warn', '初始化未完成,将重试...');
55
- }
56
- }
57
-
58
- log('error', `初始化失败:已重试 ${maxAttempts} 次,关键文件仍未生成`);
59
- return { success: false, cost: null };
60
- }
61
-
62
- module.exports = { scan, validateProfile };