claude-coder 1.8.4 → 1.9.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/src/core/init.js CHANGED
@@ -6,15 +6,27 @@ const http = require('http');
6
6
  const { spawn, execSync } = require('child_process');
7
7
  const { log } = require('../common/config');
8
8
  const { assets } = require('../common/assets');
9
- const { scan } = require('./scan');
9
+ const { isGitRepo, ensureGitignore } = require('../common/utils');
10
+
11
+ function ensureEnvironment(projectRoot) {
12
+ ensureGitignore(projectRoot);
13
+ if (!isGitRepo(projectRoot)) {
14
+ log('info', '初始化 git 仓库...');
15
+ execSync('git init', { cwd: projectRoot, stdio: 'inherit' });
16
+ execSync('git add -A && git commit -m "init: 项目初始化" --allow-empty', {
17
+ cwd: projectRoot,
18
+ stdio: 'inherit',
19
+ });
20
+ }
21
+ }
10
22
 
11
- function loadProfile() {
12
- const data = assets.readJson('profile', null);
13
- if (!data) {
14
- log('error', 'project_profile.json 读取失败或已损坏');
15
- process.exit(1);
23
+ function runCmd(cmd, cwd) {
24
+ try {
25
+ execSync(cmd, { cwd: cwd || assets.projectRoot, stdio: 'inherit', shell: true });
26
+ return true;
27
+ } catch {
28
+ return false;
16
29
  }
17
- return data;
18
30
  }
19
31
 
20
32
  function isPortFree(port) {
@@ -31,9 +43,7 @@ function waitForHealth(url, timeoutMs = 15000) {
31
43
  return new Promise(resolve => {
32
44
  const check = () => {
33
45
  if (Date.now() - start > timeoutMs) { resolve(false); return; }
34
- const req = http.get(url, res => {
35
- resolve(res.statusCode < 500);
36
- });
46
+ const req = http.get(url, res => { resolve(res.statusCode < 500); });
37
47
  req.on('error', () => setTimeout(check, 1000));
38
48
  req.setTimeout(3000, () => { req.destroy(); setTimeout(check, 1000); });
39
49
  };
@@ -41,116 +51,104 @@ function waitForHealth(url, timeoutMs = 15000) {
41
51
  });
42
52
  }
43
53
 
44
- function runCmd(cmd, cwd) {
45
- try {
46
- execSync(cmd, { cwd: cwd || assets.projectRoot, stdio: 'inherit', shell: true });
47
- return true;
48
- } catch {
49
- return false;
54
+ function buildEnvSteps(profile, projectRoot) {
55
+ const steps = [];
56
+ const envSetup = profile.env_setup || {};
57
+
58
+ if (envSetup.python_env && !['system', 'none'].includes(envSetup.python_env)) {
59
+ if (envSetup.python_env.startsWith('conda:')) {
60
+ const name = envSetup.python_env.slice(6);
61
+ steps.push({ label: `Python 环境: conda activate ${name}`, cmd: `conda activate ${name}` });
62
+ } else if (envSetup.python_env === 'venv') {
63
+ steps.push({ label: 'Python 环境: venv', cmd: 'source .venv/bin/activate || .venv\\Scripts\\activate' });
64
+ }
50
65
  }
51
- }
52
66
 
53
- function deployAssets() {
54
- const deployed = assets.deployAll();
55
- if (deployed.length > 0) {
56
- for (const file of deployed) {
57
- log('ok', `已部署 .claude-coder/assets/${file}`);
67
+ if (envSetup.node_version && envSetup.node_version !== 'none') {
68
+ steps.push({ label: `Node.js: v${envSetup.node_version}`, cmd: `nvm use ${envSetup.node_version}` });
69
+ }
70
+
71
+ const pkgManagers = profile.tech_stack?.package_managers || [];
72
+ for (const pm of pkgManagers) {
73
+ if (['npm', 'yarn', 'pnpm'].includes(pm)) {
74
+ if (fs.existsSync(`${projectRoot}/node_modules`)) {
75
+ steps.push({ label: `${pm} 依赖已安装,跳过`, skip: true });
76
+ } else {
77
+ steps.push({ label: `安装依赖: ${pm} install`, cmd: `${pm} install`, cwd: projectRoot });
78
+ }
79
+ } else if (pm === 'pip' && fs.existsSync(`${projectRoot}/requirements.txt`)) {
80
+ steps.push({ label: '安装依赖: pip install -r requirements.txt', cmd: 'pip install -r requirements.txt', cwd: projectRoot });
58
81
  }
59
82
  }
83
+
84
+ for (const cmd of (profile.custom_init || [])) {
85
+ steps.push({ label: `自定义: ${cmd}`, cmd, cwd: projectRoot });
86
+ }
87
+
88
+ return steps;
60
89
  }
61
90
 
62
- function deployRecipes() {
63
- const deployed = assets.deployRecipes();
64
- if (deployed.length > 0) {
65
- log('ok', `已部署 ${deployed.length} 个食谱文件 .claude-coder/recipes/`);
91
+ async function startService(svc, projectRoot, stepNum) {
92
+ const free = await isPortFree(svc.port);
93
+ if (!free) {
94
+ log('ok', `[${stepNum}] ${svc.name} 已在端口 ${svc.port} 运行,跳过`);
95
+ return;
96
+ }
97
+
98
+ log('info', `[${stepNum}] 启动 ${svc.name} (端口 ${svc.port})...`);
99
+ const cwd = svc.cwd ? `${projectRoot}/${svc.cwd}` : projectRoot;
100
+ const child = spawn(svc.command, { cwd, shell: true, detached: true, stdio: 'ignore' });
101
+ child.unref();
102
+
103
+ if (svc.health_check) {
104
+ const healthy = await waitForHealth(svc.health_check);
105
+ log(healthy ? 'ok' : 'warn',
106
+ healthy ? `${svc.name} 就绪: ${svc.health_check}` : `${svc.name} 健康检查超时 (${svc.health_check}),继续执行`);
66
107
  }
67
108
  }
68
109
 
69
- async function init() {
70
- assets.ensureDirs();
110
+ async function executeInit(config, opts = {}) {
71
111
  const projectRoot = assets.projectRoot;
72
112
 
113
+ ensureEnvironment(projectRoot);
114
+
73
115
  if (!assets.exists('profile')) {
74
116
  log('info', 'profile 不存在,正在执行项目扫描...');
75
- const scanResult = await scan({ projectRoot });
117
+ const { executeScan } = require('./scan');
118
+ const scanResult = await executeScan(config, opts);
76
119
  if (!scanResult.success) {
77
- log('error', '项目扫描失败');
78
- process.exit(1);
120
+ throw new Error('项目扫描失败');
79
121
  }
80
122
  }
81
123
 
82
- const profile = loadProfile();
83
- let stepCount = 0;
84
-
85
- deployAssets();
86
- deployRecipes();
87
-
88
- const envSetup = profile.env_setup || {};
89
- if (envSetup.python_env && envSetup.python_env !== 'system' && envSetup.python_env !== 'none') {
90
- stepCount++;
91
- if (envSetup.python_env.startsWith('conda:')) {
92
- const envName = envSetup.python_env.slice(6);
93
- log('info', `[${stepCount}] Python 环境: conda activate ${envName}`);
94
- runCmd(`conda activate ${envName}`);
95
- } else if (envSetup.python_env === 'venv') {
96
- log('info', `[${stepCount}] Python 环境: venv`);
97
- runCmd('source .venv/bin/activate || .venv\\Scripts\\activate');
98
- }
99
- }
100
- if (envSetup.node_version && envSetup.node_version !== 'none') {
101
- stepCount++;
102
- log('info', `[${stepCount}] Node.js: v${envSetup.node_version}`);
103
- runCmd(`nvm use ${envSetup.node_version}`);
124
+ const profile = assets.readJson('profile', null);
125
+ if (!profile) {
126
+ throw new Error('project_profile.json 读取失败或已损坏');
104
127
  }
105
128
 
106
- const pkgManagers = (profile.tech_stack && profile.tech_stack.package_managers) || [];
107
- for (const pm of pkgManagers) {
108
- stepCount++;
109
- if (pm === 'npm' || pm === 'yarn' || pm === 'pnpm') {
110
- if (fs.existsSync(`${projectRoot}/node_modules`)) {
111
- log('ok', `[${stepCount}] ${pm} 依赖已安装,跳过`);
112
- } else {
113
- log('info', `[${stepCount}] 安装依赖: ${pm} install`);
114
- runCmd(`${pm} install`, projectRoot);
115
- }
116
- } else if (pm === 'pip') {
117
- const reqFile = fs.existsSync(`${projectRoot}/requirements.txt`);
118
- if (reqFile) {
119
- log('info', `[${stepCount}] 安装依赖: pip install -r requirements.txt`);
120
- runCmd('pip install -r requirements.txt', projectRoot);
121
- }
122
- }
129
+ if (opts.deployTemplates) {
130
+ for (const file of assets.deployAll()) log('ok', `已部署 → .claude-coder/assets/${file}`);
131
+ const recipes = assets.deployRecipes();
132
+ if (recipes.length > 0) log('ok', `已部署 ${recipes.length} 个食谱文件 .claude-coder/recipes/`);
123
133
  }
124
134
 
125
- const customInit = profile.custom_init || [];
126
- for (const cmd of customInit) {
135
+ const envSteps = buildEnvSteps(profile, projectRoot);
136
+ let stepCount = 0;
137
+
138
+ for (const step of envSteps) {
127
139
  stepCount++;
128
- log('info', `[${stepCount}] 自定义: ${cmd}`);
129
- runCmd(cmd, projectRoot);
140
+ if (step.skip) {
141
+ log('ok', `[${stepCount}] ${step.label}`);
142
+ } else {
143
+ log('info', `[${stepCount}] ${step.label}`);
144
+ runCmd(step.cmd, step.cwd);
145
+ }
130
146
  }
131
147
 
132
148
  const services = profile.services || [];
133
149
  for (const svc of services) {
134
150
  stepCount++;
135
- const free = await isPortFree(svc.port);
136
- if (!free) {
137
- log('ok', `[${stepCount}] ${svc.name} 已在端口 ${svc.port} 运行,跳过`);
138
- continue;
139
- }
140
-
141
- log('info', `[${stepCount}] 启动 ${svc.name} (端口 ${svc.port})...`);
142
- const cwd = svc.cwd ? `${projectRoot}/${svc.cwd}` : projectRoot;
143
- const child = spawn(svc.command, { cwd, shell: true, detached: true, stdio: 'ignore' });
144
- child.unref();
145
-
146
- if (svc.health_check) {
147
- const healthy = await waitForHealth(svc.health_check);
148
- if (healthy) {
149
- log('ok', `${svc.name} 就绪: ${svc.health_check}`);
150
- } else {
151
- log('warn', `${svc.name} 健康检查超时 (${svc.health_check}),继续执行`);
152
- }
153
- }
151
+ await startService(svc, projectRoot, stepCount);
154
152
  }
155
153
 
156
154
  if (stepCount === 0) {
@@ -159,13 +157,9 @@ async function init() {
159
157
  log('ok', `初始化完成 (${stepCount} 步)`);
160
158
  }
161
159
 
162
- if (services.length > 0) {
163
- console.log('');
164
- for (const svc of services) {
165
- console.log(` ${svc.name}: http://localhost:${svc.port}`);
166
- }
167
- console.log('');
160
+ for (const svc of services) {
161
+ console.log(` ${svc.name}: http://localhost:${svc.port}`);
168
162
  }
169
163
  }
170
164
 
171
- module.exports = { init };
165
+ module.exports = { executeInit };
package/src/core/plan.js CHANGED
@@ -4,126 +4,36 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
  const readline = require('readline');
7
- const { runSession } = require('./session');
8
- const { buildQueryOptions } = require('./query');
9
7
  const { buildSystemPrompt, buildPlanPrompt } = require('./prompts');
10
- const { log, loadConfig } = require('../common/config');
8
+ const { log } = require('../common/config');
11
9
  const { assets } = require('../common/assets');
12
- const { extractResultText } = require('../common/logging');
13
10
  const { printStats } = require('../common/tasks');
14
- const { syncAfterPlan } = require('./harness');
11
+ const { syncAfterPlan } = require('./state');
12
+ const { Session } = require('./session');
15
13
 
16
- const EXIT_TIMEOUT_MS = 300000;
17
14
  const PLANS_DIR = path.join(os.homedir(), '.claude', 'plans');
18
15
 
19
- function buildPlanOnlyPrompt(instruction, opts = {}) {
16
+ function buildPlanOnlySystem(opts = {}) {
20
17
  const interactive = opts.interactive || false;
21
- const reqFile = opts.reqFile || null;
22
-
23
- const inputSection = reqFile
24
- ? `需求文件路径: ${reqFile}\n先读取该文件,理解用户需求和约束。`
25
- : `用户需求:\n${instruction}`;
26
-
27
18
  const interactionRule = interactive
28
- ? '如有不确定的关键决策点,向用户提问对话确认方案。'
19
+ ? '如有不确定的关键决策点,使用 AskUserQuestion 工具向用户提问,对话确认方案。'
29
20
  : '不要提问,默认使用最佳推荐方案。';
30
21
 
31
- return `你是一个资深技术架构师。根据以下需求,探索项目代码库后输出完整的技术方案文档。
32
-
33
- ${inputSection}
22
+ return `你是一个资深技术架构师。根据用户需求,探索项目代码库后输出完整的技术方案文档。
34
23
 
35
24
  【流程】
36
25
  1. 探索项目代码库,理解结构和技术栈
37
26
  2. ${interactionRule}
38
27
  3. 使用 Write 工具将完整计划写入 ~/.claude/plans/ 目录(.md 格式)
39
28
  4. 写入后输出标记(独占一行):PLAN_FILE_PATH: <计划文件绝对路径>
40
- 5. 简要总结计划要点
41
- `;
42
- }
43
-
44
- /**
45
- * 从文本中提取计划文件路径
46
- * 优先级:PLAN_FILE_PATH 标记 > .claude/plans/*.md > 反引号包裹 .md > 任意绝对 .md
47
- */
48
- function extractPlanPath(text) {
49
- if (!text) return null;
50
-
51
- const tagMatch = text.match(/PLAN_FILE_PATH:\s*(\S+\.md)/);
52
- if (tagMatch) return tagMatch[1];
53
-
54
- const plansMatch = text.match(/([^\s`'"(]*\.claude\/plans\/[^\s`'"()]+\.md)/);
55
- if (plansMatch) return plansMatch[1];
56
-
57
- const backtickMatch = text.match(/`([^`]+\.md)`/);
58
- if (backtickMatch) return backtickMatch[1];
59
-
60
- const absMatch = text.match(/(\/[^\s`'"]+\.md)/);
61
- if (absMatch) return absMatch[1];
62
-
63
- return null;
29
+ 5. 简要总结计划要点`;
64
30
  }
65
31
 
66
- /**
67
- * 多源提取计划路径(按可靠性从高到低)
68
- * 1. Write 工具调用参数(最可靠)
69
- * 2. assistant 消息流文本
70
- * 3. result.result 文本
71
- * 4. plans 目录最新文件(兜底)
72
- */
73
- function extractPlanPathFromCollected(collected, startTime) {
74
- // 第一层:从 Write 工具调用参数中直接获取
75
- for (const msg of collected) {
76
- if (msg.type !== 'assistant' || !msg.message?.content) continue;
77
- for (const block of msg.message.content) {
78
- if (block.type === 'tool_use' && block.name === 'Write') {
79
- const target = block.input?.file_path || block.input?.path || '';
80
- if (target.includes('.claude/plans/') && target.endsWith('.md')) {
81
- if (fs.existsSync(target)) return target;
82
- }
83
- }
84
- }
85
- }
86
-
87
- // 第二层:从所有 assistant 文本中提取
88
- let fullText = '';
89
- for (const msg of collected) {
90
- if (msg.type === 'assistant' && msg.message?.content) {
91
- for (const block of msg.message.content) {
92
- if (block.type === 'text' && block.text) fullText += block.text;
93
- }
94
- }
95
- }
96
- if (fullText) {
97
- const p = extractPlanPath(fullText);
98
- if (p && fs.existsSync(p)) return p;
99
- }
100
-
101
- // 第三层:从 result.result 中提取
102
- const resultText = extractResultText(collected);
103
- if (resultText) {
104
- const p = extractPlanPath(resultText);
105
- if (p && fs.existsSync(p)) return p;
106
- }
107
-
108
- // 第四层:扫描 plans 目录,找 session 期间新建的文件
109
- if (fs.existsSync(PLANS_DIR)) {
110
- try {
111
- const files = fs.readdirSync(PLANS_DIR)
112
- .filter(f => f.endsWith('.md'))
113
- .map(f => {
114
- const fp = path.join(PLANS_DIR, f);
115
- return { path: fp, mtime: fs.statSync(fp).mtimeMs };
116
- })
117
- .filter(f => f.mtime >= startTime)
118
- .sort((a, b) => b.mtime - a.mtime);
119
- if (files.length > 0) {
120
- log('info', `从 plans 目录发现新文件: ${path.basename(files[0].path)}`);
121
- return files[0].path;
122
- }
123
- } catch { /* ignore */ }
124
- }
125
-
126
- return null;
32
+ function buildPlanOnlyPrompt(instruction, opts = {}) {
33
+ const reqFile = opts.reqFile || null;
34
+ return reqFile
35
+ ? `需求文件路径: ${reqFile}\n先读取该文件,理解用户需求和约束。`
36
+ : instruction;
127
37
  }
128
38
 
129
39
  function copyPlanToProject(generatedPath) {
@@ -142,57 +52,43 @@ function copyPlanToProject(generatedPath) {
142
52
  }
143
53
  }
144
54
 
145
- async function _executePlanGen(sdk, ctx, instruction, opts = {}) {
55
+ async function _executePlanGen(session, instruction, opts = {}) {
56
+ const interactive = opts.interactive || false;
146
57
  const prompt = buildPlanOnlyPrompt(instruction, opts);
147
58
  const queryOpts = {
148
59
  permissionMode: 'plan',
60
+ systemPrompt: buildPlanOnlySystem(opts),
149
61
  cwd: opts.projectRoot || assets.projectRoot,
150
- hooks: ctx.hooks,
62
+ hooks: session.hooks,
151
63
  };
152
- if (!opts.interactive) {
64
+ if (!interactive) {
153
65
  queryOpts.disallowedTools = ['askUserQuestion'];
154
66
  }
155
67
  if (opts.model) queryOpts.model = opts.model;
156
68
 
157
- const startTime = Date.now();
158
- let exitPlanModeDetected = false;
159
- let exitPlanModeTime = null;
160
-
161
- const collected = [];
162
- const session = sdk.query({ prompt, options: queryOpts });
163
-
164
- for await (const msg of session) {
165
- if (ctx._isStalled && ctx._isStalled()) {
166
- log('warn', '停顿超时,中断 plan 生成');
167
- break;
168
- }
169
-
170
- if (exitPlanModeDetected && exitPlanModeTime) {
171
- const elapsed = Date.now() - exitPlanModeTime;
172
- if (elapsed > EXIT_TIMEOUT_MS && msg.type !== 'result') {
173
- log('warn', '检测到 ExitPlanMode,等待审批超时,尝试从已收集消息中提取路径');
174
- break;
175
- }
176
- }
177
-
178
- collected.push(msg);
179
- ctx._logMessage(msg);
180
-
181
- if (msg.type === 'assistant' && msg.message?.content) {
182
- for (const block of msg.message.content) {
183
- if (block.type === 'tool_use' && block.name === 'ExitPlanMode') {
184
- exitPlanModeDetected = true;
185
- exitPlanModeTime = Date.now();
69
+ let capturedPlanPath = null;
70
+
71
+ const { success } = await session.runQuery(prompt, queryOpts, {
72
+ onMessage(message) {
73
+ if (message.type !== 'assistant' || !message.message?.content) return;
74
+ for (const block of message.message.content) {
75
+ if (block.type === 'tool_use' && block.name === 'Write') {
76
+ const target = block.input?.file_path || block.input?.path || '';
77
+ if (target.includes('.claude/plans/') && target.endsWith('.md')) {
78
+ capturedPlanPath = target;
79
+ }
186
80
  }
187
81
  }
188
- }
189
- }
82
+ },
83
+ });
190
84
 
191
- const planPath = extractPlanPathFromCollected(collected, startTime);
85
+ if (!success) {
86
+ log('warn', '计划生成查询未正常结束');
87
+ }
192
88
 
193
- if (planPath) {
194
- const targetPath = copyPlanToProject(planPath);
195
- return { success: true, targetPath, generatedPath: planPath };
89
+ if (capturedPlanPath && fs.existsSync(capturedPlanPath)) {
90
+ const targetPath = copyPlanToProject(capturedPlanPath);
91
+ return { success: true, targetPath, generatedPath: capturedPlanPath };
196
92
  }
197
93
 
198
94
  log('warn', '无法从输出中提取计划路径');
@@ -200,23 +96,53 @@ async function _executePlanGen(sdk, ctx, instruction, opts = {}) {
200
96
  return { success: false, reason: 'no_path', targetPath: null };
201
97
  }
202
98
 
203
- async function runPlanSession(instruction, opts = {}) {
204
- const planOnly = opts.planOnly || false;
205
- const interactive = opts.interactive || false;
99
+ async function promptAutoRun() {
100
+ if (!process.stdin.isTTY) return false;
101
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
102
+ return new Promise(resolve => {
103
+ rl.question('任务分解完成后是否自动开始执行?(y/n) ', answer => {
104
+ rl.close();
105
+ resolve(/^[Yy]/.test(answer.trim()));
106
+ });
107
+ });
108
+ }
109
+
110
+ // ─── Main Entry ──────────────────────────────────────────
111
+
112
+ async function executePlan(config, input, opts = {}) {
113
+ const instruction = input || '';
114
+
115
+ if (opts.reqFile && instruction) {
116
+ log('info', `-r 模式下忽略文本输入,使用需求文件: ${opts.reqFile}`);
117
+ } else if (opts.reqFile) {
118
+ console.log(`需求文件: ${opts.reqFile}`);
119
+ }
120
+
121
+ if (!instruction && !opts.reqFile) {
122
+ throw new Error('用法: claude-coder plan "需求内容" 或 claude-coder plan -r [requirements.md]');
123
+ }
124
+
125
+ if (opts.interactive) {
126
+ log('info', '交互模式已启用,模型可能会向您提问');
127
+ }
128
+
129
+ let shouldAutoRun = false;
130
+ if (!opts.planOnly) {
131
+ shouldAutoRun = await promptAutoRun();
132
+ }
133
+
134
+ const hookType = opts.interactive ? 'plan_interactive' : 'plan';
206
135
  const ts = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 12);
207
- const label = planOnly ? 'plan_only' : 'plan_tasks';
208
- const hookType = interactive ? 'plan_interactive' : 'plan';
136
+ const label = opts.planOnly ? 'plan_only' : 'plan_tasks';
209
137
 
210
- return runSession(hookType, {
211
- opts,
212
- sessionNum: 0,
138
+ const result = await Session.run(hookType, config, {
213
139
  logFileName: `plan_${ts}.log`,
214
140
  label,
215
141
 
216
- async execute(sdk, ctx) {
142
+ async execute(session) {
217
143
  log('info', '正在生成计划方案...');
218
144
 
219
- const planResult = await _executePlanGen(sdk, ctx, instruction, opts);
145
+ const planResult = await _executePlanGen(session, instruction, opts);
220
146
 
221
147
  if (!planResult.success) {
222
148
  log('error', `\n计划生成失败: ${planResult.reason || planResult.error}`);
@@ -225,90 +151,26 @@ async function runPlanSession(instruction, opts = {}) {
225
151
 
226
152
  log('ok', `\n计划已生成: ${planResult.targetPath}`);
227
153
 
228
- if (planOnly) {
154
+ if (opts.planOnly) {
229
155
  return { success: true, planPath: planResult.targetPath };
230
156
  }
231
157
 
232
158
  log('info', '正在生成任务列表...');
233
159
 
234
160
  const tasksPrompt = buildPlanPrompt(planResult.targetPath);
235
- const queryOpts = buildQueryOptions(ctx.config, opts);
161
+ const queryOpts = session.buildQueryOptions(opts);
236
162
  queryOpts.systemPrompt = buildSystemPrompt('plan');
237
- queryOpts.hooks = ctx.hooks;
238
- queryOpts.abortController = ctx.abortController;
239
163
 
240
- await ctx.runQuery(sdk, tasksPrompt, queryOpts);
164
+ const { success } = await session.runQuery(tasksPrompt, queryOpts);
165
+ if (!success) {
166
+ log('warn', '任务分解查询未正常结束');
167
+ }
241
168
 
242
169
  syncAfterPlan();
243
170
  log('ok', '任务追加完成');
244
171
  return { success: true, planPath: planResult.targetPath };
245
172
  },
246
173
  });
247
- }
248
-
249
- async function promptAutoRun() {
250
- if (!process.stdin.isTTY) return false;
251
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
252
- return new Promise(resolve => {
253
- rl.question('任务分解完成后是否自动开始执行?(y/n) ', answer => {
254
- rl.close();
255
- resolve(/^[Yy]/.test(answer.trim()));
256
- });
257
- });
258
- }
259
-
260
- async function run(input, opts = {}) {
261
- const instruction = input || '';
262
-
263
- assets.ensureDirs();
264
- const projectRoot = assets.projectRoot;
265
-
266
- if (opts.readFile) {
267
- const reqPath = path.resolve(projectRoot, opts.readFile);
268
- if (!fs.existsSync(reqPath)) {
269
- log('error', `文件不存在: ${reqPath}`);
270
- process.exit(1);
271
- }
272
- opts.reqFile = reqPath;
273
- if (instruction) {
274
- log('info', `-r 模式下忽略文本输入,使用需求文件: ${reqPath}`);
275
- } else {
276
- console.log(`需求文件: ${reqPath}`);
277
- }
278
- }
279
-
280
- if (!instruction && !opts.reqFile) {
281
- log('error', '用法: claude-coder plan "需求内容" 或 claude-coder plan -r [requirements.md]');
282
- process.exit(1);
283
- }
284
-
285
- const config = loadConfig();
286
- // if opts.model is not set, use the default opus model or default model, make sure the model is set.
287
- if (!opts.model) {
288
- if (config.defaultOpus) {
289
- opts.model = config.defaultOpus;
290
- } else if (config.model) {
291
- opts.model = config.model;
292
- }
293
- }
294
-
295
- const displayModel = opts.model || config.model || '(default)';
296
- log('ok', `模型配置已加载: ${config.provider || 'claude'} (plan 使用: ${displayModel})`);
297
- if (opts.interactive) {
298
- log('info', '交互模式已启用,模型可能会向您提问');
299
- }
300
-
301
- if (!assets.exists('profile')) {
302
- log('error', 'profile 不存在,请先运行 claude-coder init 初始化项目');
303
- process.exit(1);
304
- }
305
-
306
- let shouldAutoRun = false;
307
- if (!opts.planOnly) {
308
- shouldAutoRun = await promptAutoRun();
309
- }
310
-
311
- const result = await runPlanSession(instruction, { projectRoot, ...opts });
312
174
 
313
175
  if (result.success) {
314
176
  printStats();
@@ -316,10 +178,10 @@ async function run(input, opts = {}) {
316
178
  if (shouldAutoRun) {
317
179
  console.log('');
318
180
  log('info', '开始自动执行任务...');
319
- const { run: runCoding } = require('./runner');
320
- await runCoding(opts);
181
+ const { executeRun } = require('./runner');
182
+ await executeRun(config, opts);
321
183
  }
322
184
  }
323
185
  }
324
186
 
325
- module.exports = { runPlanSession, run };
187
+ module.exports = { executePlan };