mocode-ai 1.0.14 → 1.1.1

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.
@@ -2,8 +2,6 @@ import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import dotenv from 'dotenv';
5
- import { loadSnapshot } from '../project-snapshot/index.js';
6
- import { buildProjectSkillSection } from '../project-skill/index.js';
7
5
  import { getSandboxRoot } from '../sandbox/root.js';
8
6
  import { getCurrentSessionId } from '../session/state.js';
9
7
  import { buildWorkDisciplineSection, inferModelFamily } from '../agent/work-discipline.js';
@@ -64,57 +62,32 @@ export function isModelConfigured() {
64
62
  const PLATFORM_NOTE = (() => {
65
63
  if (process.platform === 'win32') {
66
64
  return `## Environment (Windows)
67
- - You are on Windows; run_command runs commands via cmd.exe (/c). Unix shell builtins are NOT available here.
68
- - Windows equivalents: which→where, cat→type, ls→dir, rm→del/rd, cp→copy, mv→move. cmd.exe uses %VAR% (not $VAR); pipes (|) and redirects (>, >>) work, but no $(...) command substitution or backticks.
69
- - head/tail/find/grep/sed have no cmd.exe equivalent — use the dedicated tools (read_file for head/tail, glob for find, grep for grep), or invoke PowerShell via run_command if you need more.
70
- - **Avoid \`run_command\` for file ops on Windows**: cmd /c re-parses paths with backslashes / spaces / quotes — fragile, and ~half of "agent can't find file" failures trace back to this. Use the dedicated tools (read_file/glob/grep) which take absolute Windows paths natively, no shell involved. In particular, NEVER \`dir\` / \`ls\` / \`Test-Path\` / \`if exist\` / \`python -c "os.path.exists(...)"\` — those waste turns on escaping. Use \`glob\` to list, and just call \`read_file\` to test existence (returns ENOENT as a clean error string). If you must shell out, use forward slashes (\`C:/foo/bar\`).
71
- - Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they're cross-platform and already wired in.`;
65
+ - \`run_command\` uses \`cmd.exe /c\`: use cmd syntax and \`%VAR%\`; Unix builtins and command substitution are unavailable.
66
+ - Prefer read_file/glob/grep for file discovery and reading. When shell is necessary, use forward-slash paths or invoke PowerShell explicitly.`;
72
67
  }
73
68
  if (process.platform === 'darwin') {
74
69
  return `## Environment (macOS)
75
- - You are on macOS; run_command runs via bash -c (user default shell may be zsh). BSD coreutils, not GNU.
76
- - Pitfalls: sed -i needs an empty backup-ext arg (sed -i '' 's/x/y/' file); grep -P unavailable (use grep -E or the grep tool); find/readlink/date are BSD variants; readlink -f unsupported (use realpath, or greadlink -f if GNU coreutils installed via brew).
77
- - Prefer the dedicated tools (read_file/glob/grep) over shell equivalents — they sidestep BSD/GNU differences.`;
70
+ - \`run_command\` uses bash with BSD utilities. Prefer read_file/glob/grep; account for BSD/GNU differences when shell commands are necessary.`;
78
71
  }
79
72
  return `## Environment (Linux/Unix)
80
- - You are on ${process.platform}; run_command runs via bash -c. GNU coreutils — standard POSIX/GNU shell syntax is safe.
81
- - Still prefer the dedicated tools (read_file/glob/grep) over hand-rolled shell where they fit — they avoid quoting pitfalls and are already wired in.`;
73
+ - \`run_command\` uses bash. Prefer read_file/glob/grep when they fit; otherwise use standard POSIX/GNU syntax.`;
82
74
  })();
83
75
  /**
84
76
  * 基础系统提示的"记忆段落":开 isMemoryEnabled() 时才拼。
85
77
  * 默认关(新用户零侵入):这段 + 工具表里的 5 个 memory_* + 系统提示尾部的 Memory Index
86
78
  * 都不出现;打开 /memory_switch 后下一次新建 system message 才注入。
87
79
  */
88
- /**
89
- * 项目快照段落:开 isProjectSnapshotEnabled() 且有快照时才拼。
90
- * 告诉 LLM 项目已有哪些静态文件可 cache hit,减少无谓 read_file。
91
- */
92
- function buildSnapshotSection() {
93
- if (!config.projectSnapshotEnabled)
94
- return '';
95
- try {
96
- const snap = loadSnapshot();
97
- if (!snap)
98
- return '';
99
- // 快照内容已经是完整的 markdown,直接返回
100
- return `\n${snap.content}\n`;
101
- }
102
- catch {
103
- return '';
104
- }
105
- }
106
- /**
107
- * 项目快照总开关查询器(read-file 工具用)。
108
- */
109
- export function isProjectSnapshotEnabled() {
110
- return config.projectSnapshotEnabled;
111
- }
112
80
  /**
113
81
  * Session notepad 段落:读取 .mocode/sessions/<sessionId>/notes.md,只注入 ## 标题行作为目录摘要。
114
82
  * Agent 用 write_file/edit_file/read_file 维护此文件,抗 compact(在 context window 之外)。
115
83
  * 文件不存在或为空时返空串(零开销)。
84
+ *
85
+ * 输出按"活跃 / 已完成"两栏分桶,让 agent 一眼看到还有未结的工作:
86
+ * - Active: ## Plan: ... ## Open Questions ## <其它正在用的 topic>
87
+ * - Done: ## Done: ... (agent 在完成时把 topic 重命名为 "## Done: ...")
88
+ * 这样比纯目录列表更显眼,降低 agent 在长上下文里扫过去就忘了的概率。
116
89
  */
117
- function buildNotepadSection(sessionId = getCurrentSessionId()) {
90
+ export function buildNotepadSection(sessionId = getCurrentSessionId()) {
118
91
  if (!sessionId)
119
92
  return '';
120
93
  const root = getSandboxRoot() ?? process.cwd();
@@ -125,39 +98,54 @@ function buildNotepadSection(sessionId = getCurrentSessionId()) {
125
98
  const content = fs.readFileSync(p, 'utf8').trim();
126
99
  if (!content)
127
100
  return '';
128
- // 1) 提取 ## 标题行(最多 15 个),用作目录摘要
101
+ // 1) 提取 ## 标题行(最多 15 个,按文件出现顺序保留)
129
102
  const headers = content.split('\n')
130
103
  .filter(l => /^##\s/.test(l))
131
104
  .slice(0, 15);
132
- // 2) 提取 Plan 段进度(仅当存在 "## Plan: ..." 时)
133
- const planMatch = content.match(/^## Plan:\s*(.+)$/m);
134
- const stepsTotal = (content.match(/^\s*-\s*\[[ xX]\]\s*\d+\./gm) || []).length;
135
- const stepsDone = (content.match(/^\s*-\s*\[[xX]\]\s*\d+\./gm) || []).length;
136
- const planChip = planMatch
137
- ? `\nPlan: ${planMatch[1].trim()} (${stepsDone}/${stepsTotal})`
138
- : '';
139
- if (headers.length === 0 && !planChip)
105
+ // 2) 分桶:Done: 开头 → archived;其余 → active
106
+ // "## Plan:" 和 "## Open Questions" 视为永久 active(不需要改名为 Done)。
107
+ const archived = [];
108
+ const active = [];
109
+ for (const h of headers) {
110
+ if (/^##\s+Done:\s/.test(h))
111
+ archived.push(h);
112
+ else
113
+ active.push(h);
114
+ }
115
+ if (active.length === 0 && archived.length === 0)
140
116
  return '';
141
- return [
142
- '',
143
- `## Session Notepad (your working notes — use read_file(".mocode/sessions/${sessionId}/notes.md") for details)`,
144
- 'Sections:',
145
- ...headers,
146
- planChip,
117
+ const totalCount = active.length + archived.length;
118
+ const lines = [
147
119
  '',
148
- ].join('\n');
120
+ `## Session Notepad (${totalCount} section${totalCount === 1 ? '' : 's'} — read \`.mocode/sessions/${sessionId}/notes.md\` to recover full context; surviving compact is the whole point of this file)`,
121
+ `Active (${active.length}):`,
122
+ ...(active.length ? active.map(h => ` - ${h.replace(/^##\s+/, '')}`) : [' - (none)']),
123
+ ];
124
+ if (archived.length) {
125
+ lines.push(`Done (${archived.length}):`);
126
+ lines.push(...archived.map(h => ` - ${h.replace(/^##\s+/, '')}`));
127
+ }
128
+ lines.push('');
129
+ return lines.join('\n');
149
130
  }
150
131
  catch {
151
132
  return '';
152
133
  }
153
134
  }
154
135
  const SYSTEM_PROMPT_MEMORY_SECTION = `
155
- ## Memory (cross-session long-term facts)
156
- - A "memory index" (id/title/summary only) is injected into the system prompt. Retrieve full body via memory_search (pass id or keyword); use memory_list to see the entire index.
157
- - Store non-obvious, cross-session-useful facts/decisions/pitfalls (architecture conventions, gotchas, user preferences, decisions made) with memory_save — only long-term stable items, not current bugs / temp files / undecided TODOs.
158
- - If an existing memory is outdated or contradicts new facts, correct it in-place with memory_update(id, …) (don't create a duplicate); archive clearly-stale ones with memory_forget(id).
159
- - Before saving, memory_search to check for an existing similar entry to avoid duplicates. Better to store less than to store trivially correct information.
160
- - A background reflection pass periodically mines and organizes memories from the session (no manual action needed), but key facts you proactively save are more reliable.`;
136
+ ## Memory (cross-session facts)
137
+ - The prompt may contain a title/summary index; retrieve details with memory_search or inspect all with memory_list.
138
+ - Save only stable, non-obvious cross-session facts. Search before saving; update an existing entry instead of duplicating it, and archive stale entries.`;
139
+ /** Inject only retrieval guidance; MOCODE.md contents stay outside the prompt until read on demand. */
140
+ function buildMemoryPromptSection() {
141
+ if (!isMemoryEnabled())
142
+ return '';
143
+ const projectMocode = path.join(process.cwd(), 'MOCODE.md');
144
+ const mocodeHint = fs.existsSync(projectMocode)
145
+ ? '\n- `MOCODE.md` exists at the workspace root but is not preloaded. Read it with `read_file` only when the task may depend on project architecture, conventions, commands, prior decisions, or user preferences; skip it for greetings and unrelated simple requests. Current code and the user request override stale memory.'
146
+ : '';
147
+ return SYSTEM_PROMPT_MEMORY_SECTION + mocodeHint;
148
+ }
161
149
  /**
162
150
  * plan 模式追加到系统提示末尾的指令(切到 plan 模式时由 repl 拼进 history[0])。
163
151
  * 与 SYSTEM_PROMPT 同语种(英文),指示:只读探查、产出步骤化计划、不执行、审批后回 auto。
@@ -165,167 +153,74 @@ const SYSTEM_PROMPT_MEMORY_SECTION = `
165
153
  * memoryEnabled=false 时:memory_save/update/forget 三个写工具名字 + "memory-write tools" 这
166
154
  * 句都不出现,且 read-only 列表里的 memory_search/memory_list 也移除——避免提示词里出现
167
155
  * 根本不存在的工具名引起 LLM 调不到。
156
+ *
157
+ * .codegraph/ 存在性也动态决定:有索引时引导 LLM 优先用 codegraph skill(免去逐文件扫);
158
+ * 没索引时干脆不提,避免 LLM 调出失败。函数化(非 const)以便在 buildPlanModeSuffix 里现拼。
168
159
  */
169
- const PLAN_RESEARCH_RULES = `
170
- - Research enough to locate relevant code, trace call paths, and understand existing conventions. Use the codegraph-first Workflow, but do not repeat information already retrieved in this session.
171
- - Produce an actionable plan: files and reasons, ordered steps, edge cases, and verification (typecheck / tests / build).
172
- - When ready, MUST call the \`ask_human\` tool with a concise summary and exactly these options:
173
- 1. "按计划执行 (switch to auto and implement)" — call \`switch_mode("auto")\` in the same turn, then implement.
174
- 2. "继续细化方案 (stay in plan, refine)" — remain in plan and refine.
175
- 3. "取消 / 暂不执行 (abort)" — stop without switching mode.
176
- - Never silently switch or stop. Do not ask approval in plain text; \`ask_human\` is the approval channel.
177
- - The REPL approval prompt is only a fallback; do not rely on it.`;
178
- function buildPlanModeSuffix() {
179
- const memoryTools = isMemoryEnabled()
180
- ? 'memory_save, memory_update, memory_forget'
160
+ function buildPlanResearchRules() {
161
+ const cg = hasCodegraphIndex()
162
+ ? ' Prefer the available codegraph skill for call paths and blast radius.'
181
163
  : '';
182
- const readOnlyTools = isMemoryEnabled()
183
- ? 'read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human, memory_search, memory_list'
184
- : 'read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human';
185
- const removed = ['write_file', 'edit_file', 'run_command', memoryTools]
186
- .filter(Boolean)
187
- .join(', ');
164
+ return `
165
+ - Locate relevant code and conventions without repeating retrieved work.${cg}
166
+ - Return an actionable plan with affected files, ordered steps, edge cases, and verification.
167
+ - When ready, call \`ask_human\` with exactly: "按计划执行", "继续细化方案", and "取消 / 暂不执行". Approval requires the user to switch to /auto; never execute or switch modes silently.`;
168
+ }
169
+ function buildPlanModeSuffix() {
188
170
  return `
189
171
 
190
172
  ## ⛯ PLAN MODE (active now)
191
- You are in PLAN mode: investigate and design only — do NOT execute or change anything.
192
- - Removed from your tool list: ${removed}. Use only these read-only tools: ${readOnlyTools}.
193
- ${PLAN_RESEARCH_RULES}`;
173
+ Investigate and design only. Use only the read-only tools currently exposed; do not execute commands or change files.
174
+ ${buildPlanResearchRules()}`;
194
175
  }
195
176
  /** 兼容旧名字:repl 的 buildSystemMessage 仍引 PLAN_MODE_SUFFIX(变量)。运行时按需现拼。 */
196
177
  export function buildBasePrompt(sessionId = getCurrentSessionId()) {
197
- const autoAllToolsLine = isMemoryEnabled()
198
- ? '- Default is AUTO mode: you research and execute with all tools (read/edit/run_command/memory/web/skills).'
199
- : '- Default is AUTO mode: you research and execute with all tools (read/edit/run_command/web/skills).';
200
- const memorySection = isMemoryEnabled() ? SYSTEM_PROMPT_MEMORY_SECTION : '';
201
- const planLine = isMemoryEnabled()
202
- ? '- For complex or multi-step tasks, the user may switch to PLAN mode (Shift+Tab): your editing/command/memory-write tools are then removed from your tool list, and you must research with read-only tools only and produce a step-by-step plan (no execution). On approval the session returns to auto mode to execute the plan.'
203
- : '- For complex or multi-step tasks, the user may switch to PLAN mode (Shift+Tab): your editing/command tools are then removed from your tool list, and you must research with read-only tools only and produce a step-by-step plan (no execution). On approval the session returns to auto mode to execute the plan.';
178
+ const memorySection = buildMemoryPromptSection();
204
179
  return `## Core behavior
205
180
  You are mocode, a terminal coding agent. Complete programming tasks through a "think → call tool → observe result → think again" loop until solved. ${t('assistant.languageInstruction')}
206
181
 
207
- ## 模式 (Modes)
208
- ${autoAllToolsLine}
209
- ${planLine}
182
+ ## Modes
183
+ - AUTO is the default: investigate and complete the task with the tools currently exposed.
184
+ - PLAN is read-only research and design; do not make changes until the user approves and switches back to AUTO.
210
185
 
211
186
  ${PLATFORM_NOTE}
212
187
 
213
188
  ${buildWorkDisciplineSection(inferModelFamily(config.model))}
214
189
 
215
- ## Tool details
216
- ### Token-efficient execution
217
- - First check whether the answer is already in this conversation or a previous tool result. If yes, answer directly; do not re-run tools "to be safe".
218
- - Plan the complete sub-task before calling tools. Batch independent reads in one turn. Because tool calls in one response execute without intermediate model reasoning, never batch a read with an edit that depends on its result.
219
- - Do not read "just to see". Read only what supports the next decision. Re-read after a change, compaction, stale state, or uncertain line context.
220
- - Prefer one precise call over overlapping searches. If a call fails, inspect the error and change the approach instead of repeating it unchanged.
221
- - Batch only independent read-only calls. After their results arrive, make the dependent edit in the next turn; then batch independent edits and one final verification when their exact inputs are already known.
222
- - Read only what supports the next decision; verify once after a related edit set, not after every edit.
223
- - Do not repeat an unchanged failing call; after three unproductive attempts, change tools or ask for the missing decision.
224
- - For \`edit_file\`, derive \`old_string\` by copying the exact relevant lines from the latest successful \`read_file\` of that same path and pass that read's \`expected_hash\`; never reconstruct either from memory, a summary, grep output, or a previous diff. That read becomes stale after any edit/write to the path, compaction/resume, or a possible external change. On a conflict, re-read the exact region and retry once with the new text and hash; never retry identical arguments.
225
-
226
190
  ## Workflow
227
- - Understand requirements and current code before acting; do not guess.
228
- - If \`.codegraph/\` exists, use \`codegraph\` first for unfamiliar code questions. Use direct reads for known or recently changed files.
229
- - After modifications, run the smallest relevant verification, then typecheck/build when appropriate. Never claim success without evidence.
230
- - Use web search only when freshness materially affects the answer (new APIs, versions, security, current UI conventions).
231
-
232
- ## Tool rules
233
- - Precise path/symbol → go directly to \`read_file\` or \`codegraph node\`; use \`glob\`/\`grep\` only for discovery.
234
- - Before editing, read the exact target region and copy both its artifact \`hash\` and verbatim text. Use \`edit_file\` with \`expected_hash\` for unique local replacements, and \`write_file\` with the latest hash for replacement (or null only for creation).
235
- - Local edits require an exact unique match; use \`write_file\` for new/full files.
236
- - Use \`glob\`/\`grep\` for discovery and \`run_command\` for execution or verification, not file existence checks. State intent before side effects.
237
- - Call \`ask_human\` only when a real user decision is required; otherwise decide and proceed.
238
- - Drop stale tool output when it no longer supports the current sub-task. Keep only evidence needed for the next decision.
239
- - Batch independent writes only when each input is already known and their order does not matter. Keep dependent mutations sequential. Combine a clear shell workflow in one command; follow up when its result creates a decision.
240
-
241
- ## Large file writes (avoid token-cap truncation)
242
- - \`write_file\` / \`edit_file\` arguments are part of the model's JSON output — a single tool call's content > ~5K tokens risks mid-stream truncation when the model's max output (default 8K–16K tokens) is exceeded, producing a "arguments 不是合法 JSON" error. Even with \`MAX_TOKENS=32000\` set, huge files still risk truncation.
243
- - **For large files (rough threshold: >200 lines OR >5K tokens of content)**, default to one of these strategies instead of one giant \`write_file\`:
244
- - **Skeleton + edit**: \`write_file\` a small skeleton (head + placeholders), then call \`edit_file\` repeatedly to append/replace sections — each edit stays well under the cap, and partial progress survives a stream error.
245
- - **Shell heredoc**: \`run_command\` with \`cat > path <<'EOF' ... EOF\` (bash) or \`Set-Content -Path ... -Value @"..."@\` (PowerShell) — the file content bypasses the model's JSON output entirely, so no token cap applies. Prefer this for generated/structured content (JSON config, full HTML pages, large code dumps).
246
- - For small files (≤200 lines, ≤5K tokens) just use \`write_file\` directly — no need to over-engineer.
191
+ - Use existing conversation and tool evidence before gathering more. Inspect only what supports the next decision; do not guess.
192
+ - Keep changes focused. After modifications, run the smallest relevant executable verification and report its result.
193
+ - Use web search only when freshness materially affects the answer.
194
+ ${buildCodegraphSection()}
247
195
 
248
- ## Failure Handling
249
- - Tools return errors as strings (edit_file no match or non-unique, run_command non-zero exit, etc.). Analyze the root cause, adjust, then retry — don't resend the same call verbatim.
250
- - When a command errors, read the actual output before judging; don't skip it.
196
+ ## Tool use
197
+ - Go directly to a known path or symbol; use discovery tools only when the location is unknown.
198
+ - Before an edit, use fresh exact file content and its hash. A mutation, compaction, resume, conflict, or external change makes prior edit context stale.
199
+ - Batch only independent calls. Never batch a read with an edit that depends on it; do not repeat overlapping reads or unchanged failed calls.
200
+ - On failure, inspect the full error, change the approach, and retry only with a reason. Drop stale tool output when it no longer supports the task.
201
+ - For generated content over roughly 200 lines or 5K tokens, use small staged writes rather than one oversized tool argument.
202
+ - Use \`ask_human\` only for a genuinely user-owned decision; otherwise choose the safest reversible option and proceed.
251
203
 
252
204
  ## Safety & Boundaries
253
- - Confirm with the user before irreversible or outward-facing operations (delete, overwrite existing files, push, request external services), unless explicitly authorized.
254
- - Operate only within authorized scope; when unsure, ask — don't guess.
205
+ - Get confirmation before irreversible or outward-facing actions such as deletion, push, production changes, or external requests, unless explicitly authorized.
206
+ - Stay within the authorized workspace and disclose anything skipped or unverifiable.
255
207
 
256
208
  ## Project context (dynamic reference)
257
- ${buildSnapshotSection()}${config.projectSkillEnabled ? buildProjectSkillSection() : ''}${memorySection}${buildNotepadSection(sessionId)}
258
-
259
- ## Session Notepad — working notes file
260
- ${sessionId
261
- ? `You maintain a working notepad at \`.mocode/sessions/${sessionId}/notes.md\` using write_file / edit_file / read_file.`
262
- : 'You maintain a working notepad (path will be shown after the session starts).'}
263
- This is your private working surface — write intermediate findings, decisions, open questions,
264
- and anything you might need to recall later. The file survives context compaction.
265
-
266
- ### WHEN TO WRITE
267
- The notepad is opt-in for complex work, not a routine task log. Use it only when the task has at least 3 meaningful steps, spans multiple investigation/implementation phases, or contains details that are genuinely at risk of being lost to context compaction.
268
-
269
- Do NOT create, read, or update the notepad for simple tasks, including:
270
- - Questions that can be answered directly
271
- - One-step commands or lookups
272
- - Small, localized edits that can be completed without intermediate notes
273
- - Work that only needs a few tool calls and fits comfortably in the current context
209
+ ${memorySection}${buildNotepadSection(sessionId)}
274
210
 
275
- For qualifying complex work:
276
- - After exploring code and discovering key constraints → add a section
277
- - Before making a consequential design decision → record reasoning and alternatives considered
278
- - When accumulating data across many tool calls → store concise intermediates
279
- - When you realize important information may be lost after compaction → write it down
280
- - After completing a substantial phase → summarize what you learned
211
+ ## Session Notepad (\`.mocode/sessions/${sessionId ?? '<id>'}/notes.md\`)
212
+ Use this compact, persistent working surface for tasks with at least three steps or context-loss risk; skip it for simple work.
281
213
 
282
- ### FORMAT (markdown, section-based)
283
- Use \`## <topic>\` headers to organize. Each section is self-contained.
284
- Example:
285
-
286
- ## Auth Module
287
- - JWT TTL: 86400s, hardcoded at src/auth/jwt.ts:42
288
- - Config path: config.auth.jwt.ttl (does not exist yet)
289
- - Migration: read from config with fallback to 86400
290
-
291
- ## Decision: Schema Validation
292
- - Chose: zod over joi
293
- - Why: project already uses zod (config/index.ts:8), joi would add a dep
294
- - Risk: none — zod already in dependency tree
295
-
296
- ## Open Questions
297
- - [ ] Does the refresh token flow need TTL config too?
298
- - [ ] Check if rate limiter interacts with auth middleware
299
-
300
- ### RULES
301
- ${sessionId
302
- ? `- Your notepad file path is: \`.mocode/sessions/${sessionId}/notes.md\`. Use this exact path for all read_file/write_file/edit_file operations on your notes.`
303
- : '- Your notepad file path will be available after the session starts.'}
304
- - Use write_file to create/overwrite; use edit_file to append or modify sections
305
- - Keep the file concise — summarize, don't dump raw tool output
306
- - At task completion, the file can be deleted or left for the user's reference
307
- - Do NOT use this for cross-session knowledge (use memory_save for that)
308
-
309
- ### PLAN FORMAT (use for any task with ≥3 steps)
310
- Write the plan as a top-level \`## Plan:\` section. The system extracts this for the status bar chip, so follow the format exactly.
311
-
312
- ## Plan: <task title>
313
-
314
- Goal: <one-line goal>
315
-
316
- ### Steps
317
- - [ ] 1. <step 1>
318
- - [x] 2. <step 2>
319
- - [ ] 3. <step 3>
320
-
321
- ### Progress
322
- - <what you learned / did in this phase>
323
-
324
- Rules:
325
- - Only ONE active \`## Plan:\` section at a time.
326
- - Mark steps \`[x]\` as you complete them; append a line to \`### Progress\` after each phase.
327
- - Before your final response, reconcile every step with the work actually completed, then delete the plan section or rename it to \`## Done: <title>\`.
328
- - The host hides an unchanged active plan when an agent turn ends as a safety fallback; this does not edit the notepad. Keep updating the plan during execution so live progress remains accurate.
214
+ Keep at most one active plan:
215
+ \`\`\`
216
+ ## Plan: <title>
217
+ Goal: <outcome>
218
+ ### Steps
219
+ - [ ] 1. <verifiable step>
220
+ ### Progress
221
+ - <completed phase and evidence>
222
+ \`\`\`
223
+ Update checkboxes and Progress after each completed phase. Before the final reply, reconcile the plan with actual work, then rename it to \`## Done:\` or remove it. Keep other notes concise and session-specific; use memory for stable cross-session facts.
329
224
 
330
225
  ## Termination & Reporting
331
226
  - Stop immediately when no more tools are needed; give conclusions directly.
@@ -394,10 +289,8 @@ export const config = {
394
289
  theme: process.env.MOCODE_THEME || 'default',
395
290
  themeFromShell,
396
291
  llmKeysFromShell,
397
- projectSnapshotEnabled: process.env.MOCODE_PROJECT_SNAPSHOT !== 'false',
398
292
  permissionEnabled: process.env.MOCODE_PERMISSION !== 'false',
399
293
  permissionNonInteractiveAllow: process.env.MOCODE_PERMISSION_NON_INTERACTIVE_ALLOW === 'true',
400
- projectSkillEnabled: process.env.MOCODE_PROJECT_SKILL === 'true',
401
294
  };
402
295
  /**
403
296
  * 运行时更新模型相关配置(/model 命令调)。
@@ -442,6 +335,32 @@ export function updateSubAgentConfig(enabled) {
442
335
  export function isMemoryEnabled() {
443
336
  return config.memoryEnabled;
444
337
  }
338
+ /**
339
+ * .codegraph/ 索引存在性:仅查 cwd 顶层 .codegraph(目录或文件均可,codegraph CLI
340
+ * 自己会处理内部布局)。用于动态决定是否在系统提示里注入 codegraph skill 用法段——
341
+ * 没有索引的项目不应被提示「用 codegraph」以免 LLM 调出失败。失败静默返 false。
342
+ */
343
+ export function hasCodegraphIndex() {
344
+ try {
345
+ return fs.existsSync(path.join(process.cwd(), '.codegraph'));
346
+ }
347
+ catch {
348
+ return false;
349
+ }
350
+ }
351
+ /**
352
+ * 当 .codegraph/ 存在时拼进 auto 模式系统提示的 codegraph 段;否则返空串(零成本)。
353
+ * 单一来源:被 buildBasePrompt 注入,确保 basePrompt 不含死字符串。
354
+ */
355
+ export function buildCodegraphSection() {
356
+ if (!hasCodegraphIndex())
357
+ return '';
358
+ return [
359
+ '',
360
+ '## Codegraph (project has .codegraph/ index)',
361
+ '- For unfamiliar code questions, prefer loading the `codegraph` skill (via use_skill) and querying it with run_command (`codegraph explore <entry>`, `codegraph node <symbol>`). Falls back to read_file / glob / grep when not applicable.',
362
+ ].join('\n');
363
+ }
445
364
  /**
446
365
  * 切换记忆子系统开关(/memory_switch on|off 调)。
447
366
  * - 更新 config 单例字段(其它模块下次调 isMemoryEnabled() 即拿新值)。
@@ -457,34 +376,6 @@ export function updateMemoryConfig(enabled) {
457
376
  config.memoryEnabled = enabled;
458
377
  process.env.MEMORY_ENABLED = enabled ? 'true' : 'false';
459
378
  }
460
- /**
461
- * 项目专属 Skill 总开关:单一来源。/project_skill、buildBasePrompt、
462
- * tools/builtins/index.ts 都从这里查。默认 false(零侵入)。
463
- */
464
- export function isProjectSkillEnabled() {
465
- return config.projectSkillEnabled;
466
- }
467
- /**
468
- * 切换项目专属 Skill 开关(/project_skill on|off 调)。
469
- * - 更新 config 单例字段(其它模块下次调 isProjectSkillEnabled() 即拿新值)。
470
- * - 同步 process.env.MOCODE_PROJECT_SKILL(下次启动 loadEnvFiles 不被文件回填)。
471
- * 持久化(写 ~/.mocode/config 的 MOCODE_PROJECT_SKILL 键)由调用方走 writeConfigKeys。
472
- */
473
- export function updateProjectSkillConfig(enabled) {
474
- config.projectSkillEnabled = enabled;
475
- process.env.MOCODE_PROJECT_SKILL = enabled ? 'true' : 'false';
476
- }
477
- /**
478
- * 切换项目快照开关(/snapshot on|off 调)。
479
- * - 更新 config 单例字段(其它模块下次读 config.projectSnapshotEnabled 即拿新值:
480
- * buildSnapshotSection 现拼现读、read-file 每次 execute 现读)。
481
- * - 同步 process.env.MOCODE_PROJECT_SNAPSHOT(下次启动 loadEnvFiles 不会被文件回填)。
482
- * 持久化(写 ~/.mocode/config 的 MOCODE_PROJECT_SNAPSHOT 键)由调用方走 updateConfigKey。
483
- */
484
- export function updateSnapshotConfig(enabled) {
485
- config.projectSnapshotEnabled = enabled;
486
- process.env.MOCODE_PROJECT_SNAPSHOT = enabled ? 'true' : 'false';
487
- }
488
379
  /** 切换界面与模型回复语言;持久化由 REPL 调用 config/file.ts 完成。 */
489
380
  export function updateLanguageConfig(language) {
490
381
  setLanguage(language);
@@ -40,7 +40,7 @@ function callArgs(history, idx) {
40
40
  function sourceType(tool) {
41
41
  if (tool === 'read_file')
42
42
  return 'read';
43
- if (tool === 'grep' || tool === 'glob' || tool === 'codegraph')
43
+ if (tool === 'grep' || tool === 'glob')
44
44
  return 'search';
45
45
  if (tool === 'run_command')
46
46
  return 'diagnostic';
@@ -53,7 +53,7 @@ function pathsFromOutput(tool, output) {
53
53
  if (normalized)
54
54
  paths.add(normalized);
55
55
  };
56
- if (tool === 'grep' || tool === 'codegraph' || tool === 'run_command') {
56
+ if (tool === 'grep' || tool === 'run_command') {
57
57
  const expression = /^(.+?\.[A-Za-z0-9]+):(?:\d+|\s*\d+\s*(?:处匹配|matches?))/gmi;
58
58
  let match;
59
59
  while ((match = expression.exec(output)))
@@ -1,21 +1,19 @@
1
1
  // Context Classifier:据工具名(强先验)+ 输出形状(启发)+ 兜底,选 ContextKind。
2
2
  //
3
3
  // 三级信号:
4
- // 1) 名字强先验(BY_NAME 表,覆盖全部 17 内置工具,确定性强)。
4
+ // 1) 名字强先验(BY_NAME 表,覆盖全部内置工具,确定性强)。
5
5
  // 2) 形状启发(为 MCP 工具 / 未来工具 / 未登记工具兜底识别)。
6
6
  // 3) 兜底 'passthrough'(不认识 = 不动,零行为变化)。
7
7
  //
8
8
  // 单一事实源风格(仿 tools/constants.ts 的 READ_TOOL_NAMES / PLAN_DISABLED_TOOLS)。
9
9
  // 加新工具:在 BY_NAME 加一行;或靠形状启发自动识别。
10
- /** 工具名 → ContextKind 的强先验表(覆盖全部 18 内置工具)。 */
10
+ /** 工具名 → ContextKind 的强先验表(覆盖全部内置工具)。 */
11
11
  const BY_NAME = {
12
12
  // tree:路径列表 → 缩进树
13
13
  glob: 'tree',
14
14
  // search:file:line 分组
15
15
  grep: 'search',
16
16
  web_search: 'search',
17
- // graph:CLI dump → 精炼图
18
- codegraph: 'graph',
19
17
  // log:分级 / 折叠 / 尾偏置
20
18
  run_command: 'log',
21
19
  // code:保行号(edit_file 依赖,最敏感)
@@ -31,7 +29,6 @@ const BY_NAME = {
31
29
  edit_file: 'status',
32
30
  write_file: 'status',
33
31
  ask_human: 'status',
34
- switch_mode: 'status',
35
32
  drop_context: 'status',
36
33
  memory_save: 'status',
37
34
  memory_update: 'status',
@@ -47,7 +44,7 @@ function classifyByShape(output) {
47
44
  // file:line: content 形(grep 风格)
48
45
  if (/^[^\n:]+:\d+:[^\n]*$/m.test(output))
49
46
  return 'search';
50
- // [退出码 N] 前缀(run_command / codegraph 风格)
47
+ // [退出码 N] 前缀(run_command 风格)
51
48
  if (/^\[退出码 \d+\]/m.test(output))
52
49
  return 'log';
53
50
  // 路径列表:多行都是含分隔符的相对路径(glob 风格)
@@ -6,8 +6,9 @@ export function stripAnsi(s) {
6
6
  }
7
7
  /**
8
8
  * 折叠连续空行(只含空白字符的行)≥ threshold → 单个空行。
9
- * 用于 prose / source dump(web_fetch / use_skill / task / codegraph):HTML→文本与 markdown 常留多余空行,
9
+ * 用于 prose / source dump(web_fetch / use_skill / task):HTML→文本与 markdown 常留多余空行,
10
10
  * 多空行与单空行语义等价,折叠无损。默认 threshold=3(只动真正过量的空行,常见 ≤2 空行不动)。
11
+ * 注:codegraph 已 skill 化(用 run_command 调 CLI,落到 log encoder),graph kind 现仅作保留值。
11
12
  *
12
13
  * 注意:用 `.trim() === ''` 判空——故 read_file 的 ` 2\t`(行号前缀 + tab,trim 后剩 `2`)不会被
13
14
  * 视作空行。read_file 的空行折叠见 code encoder(前缀感知)。
@@ -4,8 +4,9 @@
4
4
  // Phase 2:tree / search / log / table / memory(高价值低风险)。
5
5
  // Phase 2.5:code / graph / doc / summary(覆盖剩余有 encoder 的 kind)。
6
6
  // - code(read_file):仅折叠 ≥3 连续空行,行号保真(edit_file 依赖)。
7
- // - graph(codegraph)/ doc(web_fetch, use_skill)/ summary(task):去 ANSI + 折叠空行,保守不重构结构。
8
- // - status(edit/write/ask_human/switch_mode/mem 增删改)无 encoder:本就是一行,无需编码。
7
+ // - graph(保留 kind,暂无 builtin 工具直接命中)/ doc(web_fetch, use_skill)/ summary(task):
8
+ // 去 ANSI + 折叠空行,保守不重构结构。
9
+ // - status(edit/write/ask_human/mem 增删改)无 encoder:本就是一行,无需编码。
9
10
  //
10
11
  // 加 encoder:新建 encoders/xxx.ts 导出 ContextEncoder,在此数组加一行。无需动 agent / llm / core。
11
12
  import { passthroughEncoder } from './passthrough.js';
@@ -15,7 +16,6 @@ import { commandEncoder } from './command.js';
15
16
  import { tableEncoder } from './table.js';
16
17
  import { memoryEncoder } from './memory.js';
17
18
  import { codeEncoder } from './code.js';
18
- import { graphEncoder } from './graph.js';
19
19
  import { docEncoder } from './doc.js';
20
20
  import { summaryEncoder } from './summary.js';
21
21
  export const builtinEncoders = [
@@ -26,7 +26,6 @@ export const builtinEncoders = [
26
26
  tableEncoder,
27
27
  memoryEncoder,
28
28
  codeEncoder,
29
- graphEncoder,
30
29
  docEncoder,
31
30
  summaryEncoder,
32
31
  ];
@@ -1,7 +1,7 @@
1
1
  // Observation Lifecycle Engine:tool 消息的「观察者生命周期」状态机。
2
2
  //
3
3
  // 在 Relevance Pruner 之上的第二层被动裁剪。Relevance Pruner 只管 read_file 的「同 path 新旧替换 +
4
- // mutation 覆写」,本层补足「grep/glob/codegraph 这类观察类工具」的引用追踪。
4
+ // mutation 覆写」,本层补足「grep/glob/web_search/web_fetch 这类观察类工具」的引用追踪。
5
5
  //
6
6
  // 四态机(LIVE → REFERENCED → OBSOLETE → STUB):
7
7
  // - LIVE:刚 push 进 history 的工具结果,尚未被任何下游工具消费。
@@ -10,7 +10,7 @@
10
10
  // - STUB:已被替换为存根(物理上 content 变成「⌦[无消费者:...]」)。
11
11
  //
12
12
  // 观察类工具两阶段衰减(避免误伤):
13
- // - grep/glob/codegraph/web_search/web_fetch 等「观察/检索类」工具两阶段衰减:
13
+ // - grep/glob/web_search/web_fetch 等「观察/检索类」工具两阶段衰减:
14
14
  // Phase 1(10 步):LIVE → REFERENCED,保留完整内容(返回多个候选,剩余候选可能后续被消费)。
15
15
  // Phase 2(+5 步):REFERENCED → DIGEST,替换为摘要存根(保留文件列表+命中数+参数,丢弃详情),
16
16
  // 释放 ~90% token。states 仍为 REFERENCED,不引入新状态。
@@ -34,7 +34,6 @@ import { canonicalizePath, extractPath, isToolResultSuccess, lastUserIndex, toTe
34
34
  const OBSERVER_TOOLS = new Set([
35
35
  'grep',
36
36
  'glob',
37
- 'codegraph',
38
37
  'web_search',
39
38
  'web_fetch',
40
39
  ]);
@@ -63,7 +62,6 @@ const OBSERVER_DIGEST_AGE = 5;
63
62
  * - grep:content 是 `file:line: ...` 行,提取每行的 file 段(只保留绝对路径形态或与 pattern 匹配的)。
64
63
  * 简化:把所有看起来像「相对路径 + 文件名」的 token 抽出,留 narrow。
65
64
  * - glob:content 是路径列表,按行 / 空格拆。
66
- * - codegraph:content 里通常含 `path/to/file.ts:line`,按行拆,提 file 段。
67
65
  *
68
66
  * 返回值:命中过的 path 字符串集合(已 dedup)。失败返空集。 */
69
67
  function extractProducerPaths(toolName, content) {
@@ -99,14 +97,6 @@ function extractProducerPaths(toolName, content) {
99
97
  addPath(t);
100
98
  }
101
99
  }
102
- else if (toolName === 'codegraph') {
103
- // codegraph 输出通常 `path\to\file.ts:line:col symbol` 或类似;按行 + 冒号分隔。
104
- for (const line of content.split(/\r?\n/)) {
105
- const m = /^([^\s:][^:]*?\.[A-Za-z0-9]+):(\d+):/.exec(line);
106
- if (m)
107
- addPath(m[1]);
108
- }
109
- }
110
100
  else if (toolName === 'web_search' || toolName === 'web_fetch') {
111
101
  // 网络结果不在文件系统路径范畴;不参与 producer 路径索引(避免误匹配)。
112
102
  }
@@ -287,7 +277,7 @@ export class LifecycleEngine {
287
277
  })();
288
278
  const path = canonicalizePath(extractPath(argsRaw));
289
279
  if (path) {
290
- // 1) 找该 path 的所有上游 producer(grep/glob/codegraph)→ 标 REFERENCED。
280
+ // 1) 找该 path 的所有上游 producer(grep/glob/web_search/web_fetch)→ 标 REFERENCED。
291
281
  const producers = this.producersByPath.get(path);
292
282
  if (producers) {
293
283
  for (const pidx of producers) {
@@ -478,16 +468,6 @@ export class LifecycleEngine {
478
468
  } })();
479
469
  return `${DIGEST_PREFIX}glob(${args}) — 历史结果文件 ${files},${origLen}→摘要]\n如需完整列表或最新信息请重新调用 glob`;
480
470
  }
481
- case 'codegraph': {
482
- const args = (() => { try {
483
- const a = JSON.parse(argsRaw);
484
- return `"${a.query ?? ''}"`;
485
- }
486
- catch {
487
- return '...';
488
- } })();
489
- return `${DIGEST_PREFIX}codegraph(${args}) — 历史结果文件 ${files},${origLen}→摘要]\n如需完整详情或最新信息请重新调用 codegraph`;
490
- }
491
471
  case 'web_search': {
492
472
  // 统计结果数
493
473
  let resultCount = 0;