@raolin2025/claude-code-node 2.8.13 → 2.8.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raolin2025/claude-code-node",
3
- "version": "2.8.13",
3
+ "version": "2.8.15",
4
4
  "description": "Node.js AI Code Agent CLI - Zero dependencies, pure JavaScript, security hardened, multi-channel notifications, Telegram & QQ Bot remote programming, rich media upload, multi-account management",
5
5
  "type": "module",
6
6
  "main": "src/core/index.js",
@@ -18,6 +18,8 @@ import {
18
18
  buildSmallModelSystemPrompt,
19
19
  SMALL_MODEL_SYSTEM_PROMPT,
20
20
  isSmallModelEnabled,
21
+ buildMultiStepPlan,
22
+ buildCombinedGuidance,
21
23
  } from '../core/small-model.js'
22
24
 
23
25
  // ---- 敷衍输出检测 ----
@@ -52,6 +54,13 @@ test('意图识别:写文档任务', () => {
52
54
  assert.ok(r2 && r2.toolHint.includes('Write'))
53
55
  })
54
56
 
57
+ test('意图识别:阅读文档是读取任务(不误判为写)', () => {
58
+ const r = detectIntent('阅读DEVELOPMENT_PLAN.md文档')
59
+ assert.ok(r, '应识别到意图')
60
+ assert.ok(r.toolHint.includes('Read'), '应提示用 Read')
61
+ assert.ok(!r.toolHint.includes('Write'), '读取任务不应提示 Write')
62
+ })
63
+
55
64
  test('意图识别:找文件任务', () => {
56
65
  const r = detectIntent('查找src目录下的文件')
57
66
  assert.ok(r && r.toolHint.includes('Glob'))
@@ -138,3 +147,45 @@ test('小模型开关判断', () => {
138
147
  assert.equal(isSmallModelEnabled({ smallModel: false }), false)
139
148
  assert.equal(isSmallModelEnabled(undefined), false)
140
149
  })
150
+
151
+ // ---- system prompt 注入 cwd ----
152
+ test('system prompt 强化:注入当前工作目录', () => {
153
+ const base = 'You are cc-node.'
154
+ const enhanced = buildSmallModelSystemPrompt(base, { cwd: 'D:\\workspace\\miniQMT-trader' })
155
+ assert.ok(enhanced.includes('当前工作目录'), '应包含工作目录说明')
156
+ assert.ok(enhanced.includes('miniQMT-trader'), '应包含实际 cwd 路径')
157
+ assert.ok(enhanced.includes('不要猜测其它绝对路径'), '应提示不要瞎猜路径')
158
+ })
159
+
160
+ // ---- 多步计划拆解 ----
161
+ test('多步计划:识别"按计划实现"任务', () => {
162
+ const r = buildMultiStepPlan('按计划实现第一阶段', { cwd: 'D:\\x' })
163
+ assert.ok(r && r.isMultiStep, '应识别为多步任务')
164
+ assert.ok(r.plan.includes('步骤 1'), '应有步骤 1')
165
+ assert.ok(r.plan.includes('Read'), '步骤 1 应用 Read')
166
+ assert.ok(r.plan.includes('DEVELOPMENT_PLAN.md'), '应引用计划文件')
167
+ })
168
+
169
+ test('多步计划:提取指令里的计划文件名', () => {
170
+ const r = buildMultiStepPlan('实现DEV_PLAN.md里的第二阶段', { cwd: 'D:\\x' })
171
+ assert.ok(r && r.plan.includes('DEV_PLAN.md'), '应提取指令中的计划文件名')
172
+ })
173
+
174
+ test('多步计划:非实现任务返回 null', () => {
175
+ assert.equal(buildMultiStepPlan('阅读DEVELOPMENT_PLAN.md', { enable: true }), null)
176
+ assert.equal(buildMultiStepPlan('你好', { enable: true }), null)
177
+ assert.equal(buildMultiStepPlan('按计划实现第一阶段', { enable: false }), null)
178
+ })
179
+
180
+ test('多步计划:combined guidance 优先走多步拆解', () => {
181
+ const g = buildCombinedGuidance('按计划实现第一阶段', { cwd: 'D:\\x', enable: true })
182
+ assert.ok(g, '应生成引导')
183
+ assert.ok(g.includes('多步执行计划'), '应走多步计划')
184
+ assert.ok(g.includes('步骤 1'), '应包含步骤引导')
185
+ })
186
+
187
+ test('多步计划:普通任务走简单意图引导', () => {
188
+ const g = buildCombinedGuidance('阅读DEVELOPMENT_PLAN.md文档', { cwd: 'D:\\x', enable: true })
189
+ assert.ok(g, '应生成引导')
190
+ assert.ok(g.includes('任务引导'), '应走简单意图引导')
191
+ })
@@ -19,7 +19,7 @@ import {
19
19
  isSmallModelEnabled,
20
20
  buildSmallModelSystemPrompt,
21
21
  isFillerResponse,
22
- buildIntentGuidance,
22
+ buildCombinedGuidance,
23
23
  selectRelevantTools,
24
24
  RETRY_GUIDANCE,
25
25
  } from './small-model.js'
@@ -228,12 +228,28 @@ export class QueryEngine {
228
228
  // 发送前硬校验:工具结果可能已使上下文超窗,确保 ≤ 窗口(摘要优先 + 滑动窗口裁剪兜底)
229
229
  this._ensureFitWindow()
230
230
 
231
- // 小模型模式:首轮若识别到明确意图,注入意图引导 system 消息
231
+ // 小模型模式:首轮若识别到意图/多步计划,把引导【追加到首条 system 消息】。
232
+ // 注意:不能 push 一条新的 system 消息到中间——llama.cpp/Jinja 严格要求
233
+ // system 消息必须在对话开头,中间插 system 会报 "System message must be at the beginning" (500)。
232
234
  if (smallModel && !intentGuided && this.state.messages.length > 0) {
233
- const guidance = buildIntentGuidance(userMessage.content, { enable: true })
235
+ const guidance = buildCombinedGuidance(userMessage.content, {
236
+ enable: true,
237
+ cwd: this.config.cwd,
238
+ })
234
239
  if (guidance) {
235
- this.state.messages.push({ role: 'system', content: guidance })
236
- if (this.config.verbose) console.error('[small-model] 已注入意图引导:' + guidance.slice(0, 80) + '...')
240
+ // 找到第一条 system 消息,把引导拼接进去(保持 system 全在开头)
241
+ const sysIdx = this.state.messages.findIndex(m => m.role === 'system')
242
+ if (sysIdx !== -1) {
243
+ const sysMsg = this.state.messages[sysIdx]
244
+ this.state.messages[sysIdx] = {
245
+ ...sysMsg,
246
+ content: (typeof sysMsg.content === 'string' ? sysMsg.content : '') + '\n\n' + guidance,
247
+ }
248
+ } else {
249
+ // 没有 system 消息 → 作为 user 引导插入(跟随在最新 user 之后作为新 user)
250
+ this.state.messages.push({ role: 'user', content: guidance })
251
+ }
252
+ if (this.config.verbose) console.error('[small-model] 已注入引导(并入首条 system):' + guidance.slice(0, 60) + '...')
237
253
  }
238
254
  intentGuided = true
239
255
  }
@@ -302,10 +318,10 @@ export class QueryEngine {
302
318
  _buildRequest(messages) {
303
319
  const request = []
304
320
 
305
- // 系统提示(小模型模式:追加强制工具调用引导)
321
+ // 系统提示(小模型模式:追加强制工具调用引导 + 当前工作目录)
306
322
  if (this.config.systemPrompt) {
307
323
  const sysContent = isSmallModelEnabled(this.config)
308
- ? buildSmallModelSystemPrompt(this.config.systemPrompt)
324
+ ? buildSmallModelSystemPrompt(this.config.systemPrompt, { cwd: this.config.cwd })
309
325
  : this.config.systemPrompt
310
326
  request.push({ role: 'system', content: sysContent })
311
327
  }
@@ -97,10 +97,18 @@ export const SMALL_MODEL_SYSTEM_PROMPT = `
97
97
  /**
98
98
  * 生成小模型模式下的完整 system prompt(基础 prompt + 强化引导)
99
99
  * @param {string} basePrompt — 基础 system prompt
100
+ * @param {object} [opts]
101
+ * @param {string} [opts.cwd] — 当前工作目录(注入给模型,避免它瞎猜文件路径)
100
102
  * @returns {string}
101
103
  */
102
- export function buildSmallModelSystemPrompt(basePrompt) {
103
- return basePrompt + SMALL_MODEL_SYSTEM_PROMPT
104
+ export function buildSmallModelSystemPrompt(basePrompt, opts = {}) {
105
+ let prompt = basePrompt + SMALL_MODEL_SYSTEM_PROMPT
106
+ // 注入当前工作目录:小模型没有上下文意识,不知道用户在哪个项目目录,
107
+ // 不给 cwd 它会瞎猜绝对路径(如 /home/user/repos/...),导致 File not found。
108
+ if (opts.cwd) {
109
+ prompt += `\n\n## 当前工作目录\n用户当前的工作目录是:\`${opts.cwd}\`\n查找/读取/写入文件时,优先在此目录(或此目录的子目录)中定位文件,不要猜测其它绝对路径。\n`
110
+ }
111
+ return prompt
104
112
  }
105
113
 
106
114
  // ---- 重试追加强引导(层 A)----
@@ -118,6 +126,28 @@ export const RETRY_GUIDANCE = `[系统提示] 你刚才没有调用任何工具
118
126
  // note: 给模型的动作说明
119
127
 
120
128
  const INTENT_RULES = [
129
+ {
130
+ // 实现/开发任务:按计划实现、开始写代码、实现模块等 —— 多步复杂任务
131
+ // 这类任务 detectIntent 只给工具提示,真正的分步拆解由 buildMultiStepPlan 完成
132
+ pattern: /(按计划|依据计划|根据计划).*(实现|开发|完成|写代码|编码|落地)/i,
133
+ plan: ['Read(读计划)', 'Glob/Grep(看现状)', 'Write/Edit(实现)', 'Bash(验证)'],
134
+ note: '这是按计划实现代码的多步任务,需要先读计划再逐步实现。',
135
+ toolHint: 'Bash, Read, Edit, Write, Glob, Grep',
136
+ },
137
+ {
138
+ // 实现/开发任务(不带"按计划"):实现模块/功能/代码
139
+ pattern: /(实现|开发|编写|写出|完成).*(模块|功能|代码|接口|函数|类|系统|第一阶段|阶段)/i,
140
+ plan: ['Read(读相关文件)', 'Glob/Grep(看现状)', 'Write/Edit(实现)', 'Bash(验证)'],
141
+ note: '这是实现代码的多步任务,先了解现状再实现并验证。',
142
+ toolHint: 'Bash, Read, Edit, Write, Glob, Grep',
143
+ },
144
+ {
145
+ // 读/查看/阅读文档或文件 → 读取任务(优先于写规则,避免"阅读XX文档"被误判为写)
146
+ pattern: /(读|阅读|查看|打开|展示|显示|看看|浏览).*(文档|文件|内容|md|readme|代码|计划)/i,
147
+ plan: ['Read(读文件内容)'],
148
+ note: '这是读取任务,用 Read 读取目标文件内容并理解。',
149
+ toolHint: 'Read, Glob',
150
+ },
121
151
  {
122
152
  // 写文档/文件/计划/代码:覆盖"写/创建/生成/更新/保存/做好...文档/计划"等
123
153
  pattern: /(写|创建|生成|更新|保存|做好|做一份|制定|起草|撰写|输出|整理|编写).*(文档|文件|计划|方案|说明|md|readme|代码|函数|模块|接口)/i,
@@ -203,6 +233,96 @@ export function buildIntentGuidance(userInput, opts = {}) {
203
233
  return `[任务引导] 根据用户指令,建议按此思路用工具推进:${intent.intent}。\n说明:${intent.note}\n可用工具:${intent.toolHint}`
204
234
  }
205
235
 
236
+ // 常见计划文档文件名(供多步拆解时定位计划文件)
237
+ const PLAN_FILE_CANDIDATES = [
238
+ 'DEVELOPMENT_PLAN.md',
239
+ 'development_plan.md',
240
+ 'DEV_PLAN.md',
241
+ 'PLAN.md',
242
+ 'plan.md',
243
+ 'PROJECT_PLAN.md',
244
+ 'ROADMAP.md',
245
+ ]
246
+
247
+ /**
248
+ * 框架深度拆解:为"按计划实现"类多步任务生成分步执行计划(层 B 深化)
249
+ *
250
+ * 背景:小模型(27B Q3)撑不住"按计划实现第一阶段"这类多步复杂任务——
251
+ * 它常常不读计划就瞎写,或干脆敷衍。框架必须【接管规划】,把任务拆成
252
+ * 明确、可执行的步骤注入给模型。
253
+ *
254
+ * 针对"按计划实现/开发"任务,生成强制分步引导,核心是:
255
+ * 第 1 步必须 Read 计划文件(定位要实现的模块/函数);
256
+ * 后续步骤按"看现状 → 实现 → 验证"推进。
257
+ *
258
+ * @param {string} userInput — 用户指令
259
+ * @param {object} [opts]
260
+ * @param {string} [opts.cwd] — 当前工作目录(用于给计划文件相对路径)
261
+ * @param {boolean} [opts.enable=true] — 是否启用
262
+ * @returns {{ isMultiStep: boolean, plan: string } | null}
263
+ * isMultiStep=true 表示识别为多步实现任务;plan 为注入给模型的分步引导
264
+ */
265
+ export function buildMultiStepPlan(userInput, opts = {}) {
266
+ if (opts.enable === false) return null
267
+ if (!userInput) return null
268
+ const s = userInput
269
+
270
+ // 识别"按计划实现/开发"类任务
271
+ const isPlanImplement = /(按计划|依据计划|根据计划|按开发计划|按照计划).*(实现|开发|完成|写|落地|推进)/i.test(s)
272
+ || /实现.*(计划|第一阶段|阶段|模块)/i.test(s)
273
+
274
+ if (!isPlanImplement) return null
275
+
276
+ // 定位计划文件:从指令里提取文件名,否则用常见候选
277
+ let planFile = ''
278
+ const m = s.match(/([\w./-]+\.md)/i)
279
+ if (m) {
280
+ planFile = m[1]
281
+ } else {
282
+ planFile = PLAN_FILE_CANDIDATES[0] // 默认 DEVELOPMENT_PLAN.md
283
+ }
284
+ // 若非绝对路径且给了 cwd,拼成相对提示
285
+ const planRef = planFile
286
+
287
+ const plan = `[多步执行计划] 这是一个按计划实现代码的任务,请【严格按以下步骤】用工具推进,不要跳过,不要一次做太多:
288
+
289
+ 步骤 1(必做):用 Read 读取计划文件 \`${planRef}\`,从中找出用户要求实现的阶段/模块/函数(明确要做什么、涉及哪些文件、有哪些要求)。
290
+ 步骤 2:用 Glob 查看项目当前文件结构(cwd 下),用 Grep 搜索计划里提到的模块/函数是否已有代码。
291
+ 步骤 3:对每个需要实现的目标,先 Read 相关现有文件了解现状,再用 Write 创建新文件 / 用 Edit 修改已有文件来落地实现。
292
+ 步骤 4:实现完成后,用 Bash 运行测试或编译命令验证(如有),并总结实现了什么。
293
+
294
+ 严格遵守:第 1 步必须先 Read 计划文件;每一步完成后再进行下一步;不要在没有读计划的情况下直接写代码。`
295
+
296
+ return { isMultiStep: true, plan }
297
+ }
298
+
299
+ /**
300
+ * 生成注入到 system 的完整小模型引导(意图引导 + 多步计划 + cwd)
301
+ *
302
+ * 把多个引导源合并成一段,追加到首条 system 消息末尾。
303
+ *
304
+ * @param {string} userInput — 用户指令
305
+ * @param {object} [opts]
306
+ * @param {string} [opts.cwd] — 当前工作目录
307
+ * @param {boolean} [opts.enable=true]
308
+ * @returns {string|null} 合并后的引导文本
309
+ */
310
+ export function buildCombinedGuidance(userInput, opts = {}) {
311
+ if (opts.enable === false) return null
312
+ const parts = []
313
+ // 多步计划优先(复杂实现任务走深度拆解)
314
+ const multi = buildMultiStepPlan(userInput, opts)
315
+ if (multi) {
316
+ parts.push(multi.plan)
317
+ } else {
318
+ // 否则用简单意图引导
319
+ const g = buildIntentGuidance(userInput, opts)
320
+ if (g) parts.push(g)
321
+ }
322
+ if (parts.length === 0) return null
323
+ return parts.join('\n\n')
324
+ }
325
+
206
326
  /**
207
327
  * 精简工具列表(层 A)— 小模型一次看太多工具会混乱
208
328
  *