mocode-ai 1.2.8 → 1.2.9

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 (129) hide show
  1. package/dist/agent/core.js +126 -43
  2. package/dist/config/index.js +31 -6
  3. package/dist/config/presets.js +36 -1
  4. package/dist/context/budget.js +0 -15
  5. package/dist/llm/index.js +33 -0
  6. package/dist/repl/index.js +57 -13
  7. package/dist/session/compact.js +237 -13
  8. package/dist/session/notes.js +63 -0
  9. package/dist/session/scheduler.js +5 -9
  10. package/dist/tools/constants.js +7 -0
  11. package/package.json +1 -1
  12. package/dist/__trace_manual_test__.js +0 -1
  13. package/dist/agent/core.d.ts +0 -112
  14. package/dist/agent/index.d.ts +0 -19
  15. package/dist/agent/mode.d.ts +0 -29
  16. package/dist/agent/retry-classifier.js +0 -64
  17. package/dist/agent/spawn.d.ts +0 -36
  18. package/dist/attachments/image.d.ts +0 -22
  19. package/dist/commands/config.d.ts +0 -8
  20. package/dist/commands/skill.js +0 -230
  21. package/dist/config/file.d.ts +0 -23
  22. package/dist/config/index.d.ts +0 -114
  23. package/dist/config/presets.d.ts +0 -57
  24. package/dist/context/budget.d.ts +0 -79
  25. package/dist/context/classifier.d.ts +0 -14
  26. package/dist/context/encoders/_util.d.ts +0 -11
  27. package/dist/context/encoders/code.d.ts +0 -2
  28. package/dist/context/encoders/doc.d.ts +0 -14
  29. package/dist/context/encoders/graph.d.ts +0 -11
  30. package/dist/context/encoders/graph.js +0 -81
  31. package/dist/context/encoders/index.d.ts +0 -2
  32. package/dist/context/encoders/log.d.ts +0 -2
  33. package/dist/context/encoders/log.js +0 -2
  34. package/dist/context/encoders/memory.d.ts +0 -2
  35. package/dist/context/encoders/passthrough.d.ts +0 -11
  36. package/dist/context/encoders/search.d.ts +0 -2
  37. package/dist/context/encoders/summary.d.ts +0 -12
  38. package/dist/context/encoders/table.d.ts +0 -2
  39. package/dist/context/encoders/tree.d.ts +0 -2
  40. package/dist/context/index.d.ts +0 -6
  41. package/dist/context/lifecycle.d.ts +0 -51
  42. package/dist/context/pipeline.d.ts +0 -9
  43. package/dist/context/registry.d.ts +0 -9
  44. package/dist/context/relevance.d.ts +0 -65
  45. package/dist/context/types.d.ts +0 -36
  46. package/dist/foo.js +0 -28
  47. package/dist/foo.test.js +0 -12
  48. package/dist/index.d.ts +0 -1
  49. package/dist/llm/capabilities.d.ts +0 -19
  50. package/dist/llm/index.d.ts +0 -111
  51. package/dist/memory/discover.d.ts +0 -15
  52. package/dist/memory/index.d.ts +0 -13
  53. package/dist/memory/reflect.d.ts +0 -34
  54. package/dist/memory/store.d.ts +0 -124
  55. package/dist/pet/bridge.d.ts +0 -73
  56. package/dist/pet/protocol.d.ts +0 -83
  57. package/dist/pet/state.d.ts +0 -28
  58. package/dist/plan/active.d.ts +0 -23
  59. package/dist/plan/active.js +0 -45
  60. package/dist/plan/index.d.ts +0 -13
  61. package/dist/plan/index.js +0 -33
  62. package/dist/plan/state.d.ts +0 -2
  63. package/dist/plan/state.js +0 -11
  64. package/dist/plan/store.d.ts +0 -78
  65. package/dist/plan/store.js +0 -429
  66. package/dist/project-skill/index.js +0 -95
  67. package/dist/project-skill/initializer.js +0 -178
  68. package/dist/project-snapshot/index.js +0 -85
  69. package/dist/project-snapshot/llm-snapshot.js +0 -150
  70. package/dist/project-snapshot/static-files.js +0 -87
  71. package/dist/repl/index.d.ts +0 -22
  72. package/dist/rollback/index.d.ts +0 -79
  73. package/dist/sandbox/command.d.ts +0 -3
  74. package/dist/sandbox/index.d.ts +0 -4
  75. package/dist/sandbox/jail.d.ts +0 -17
  76. package/dist/sandbox/policy.d.ts +0 -21
  77. package/dist/sandbox/root.d.ts +0 -5
  78. package/dist/session/compact.d.ts +0 -92
  79. package/dist/session/index.d.ts +0 -13
  80. package/dist/session/notes-plan.js +0 -41
  81. package/dist/session/persist.d.ts +0 -30
  82. package/dist/session/scheduler.d.ts +0 -53
  83. package/dist/skills/discover.d.ts +0 -33
  84. package/dist/skills/index.d.ts +0 -13
  85. package/dist/skills/skill-eval.js +0 -345
  86. package/dist/skills/skill-improve.js +0 -221
  87. package/dist/skills/stats.js +0 -102
  88. package/dist/tools/builtins/apply-patch.js +0 -174
  89. package/dist/tools/builtins/ask-human.d.ts +0 -9
  90. package/dist/tools/builtins/codegraph.d.ts +0 -2
  91. package/dist/tools/builtins/codegraph.js +0 -128
  92. package/dist/tools/builtins/edit-file.d.ts +0 -2
  93. package/dist/tools/builtins/glob.d.ts +0 -2
  94. package/dist/tools/builtins/grep.d.ts +0 -2
  95. package/dist/tools/builtins/index.d.ts +0 -2
  96. package/dist/tools/builtins/memory-forget.d.ts +0 -2
  97. package/dist/tools/builtins/memory-list.d.ts +0 -2
  98. package/dist/tools/builtins/memory-save.d.ts +0 -2
  99. package/dist/tools/builtins/memory-search.d.ts +0 -2
  100. package/dist/tools/builtins/memory-update.d.ts +0 -2
  101. package/dist/tools/builtins/project-skill-update.js +0 -102
  102. package/dist/tools/builtins/read-file.d.ts +0 -2
  103. package/dist/tools/builtins/run-command.d.ts +0 -2
  104. package/dist/tools/builtins/switch-mode.d.ts +0 -2
  105. package/dist/tools/builtins/switch-mode.js +0 -44
  106. package/dist/tools/builtins/task.d.ts +0 -2
  107. package/dist/tools/builtins/todolist.d.ts +0 -2
  108. package/dist/tools/builtins/todolist.js +0 -411
  109. package/dist/tools/builtins/use-skill.d.ts +0 -2
  110. package/dist/tools/builtins/web-fetch.d.ts +0 -2
  111. package/dist/tools/builtins/web-search.d.ts +0 -2
  112. package/dist/tools/builtins/write-file.d.ts +0 -2
  113. package/dist/tools/constants.d.ts +0 -52
  114. package/dist/tools/registry.d.ts +0 -18
  115. package/dist/tools/retry.js +0 -137
  116. package/dist/tools/types.d.ts +0 -42
  117. package/dist/ui/batch.d.ts +0 -67
  118. package/dist/ui/clipboard.d.ts +0 -9
  119. package/dist/ui/content.d.ts +0 -55
  120. package/dist/ui/diff.d.ts +0 -34
  121. package/dist/ui/intervention.d.ts +0 -46
  122. package/dist/ui/layout.d.ts +0 -225
  123. package/dist/ui/markdown.d.ts +0 -46
  124. package/dist/ui/mouse.d.ts +0 -29
  125. package/dist/ui/prompt.d.ts +0 -61
  126. package/dist/ui/render.d.ts +0 -60
  127. package/dist/ui/spinner.d.ts +0 -26
  128. package/dist/ui/theme.d.ts +0 -47
  129. package/dist/ui/thinking.js +0 -80
@@ -1,429 +0,0 @@
1
- // 单 plan 的文件级存储:CRUD + markdown 解析/序列化。
2
- //
3
- // 设计目标
4
- // - 文件是唯一事实源(抗压缩、可视化、用户可改)。每次写都 atomic(rename 覆盖)。
5
- // - 同会话单 plan(state.ts 跟踪「活跃」,本模块不参与并发控制——单进程单活跃 plan 串行调用)。
6
- // - 解析宽松:坏 frontmatter / 缺段 → 退化到最小可用 Plan,不抛。
7
- // - 序列化稳定:每段顺序固定(frontmatter → 目标 → 步骤 → 进度日志),便于 git diff 友好。
8
- //
9
- // 不依赖 agent / ui / llm;仅 node:fs + node:path。仿 memory/store.ts 的「叶子」分层。
10
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
11
- import { join } from 'node:path';
12
- import { getSandboxRoot } from '../sandbox/root.js';
13
- // ── 路径 ──
14
- /** plans 目录:<sandboxRoot>/.mocode/plans/。无 sandboxRoot 退到 cwd(防御)。 */
15
- export function plansDir() {
16
- const root = getSandboxRoot() ?? process.cwd();
17
- return join(root, '.mocode', 'plans');
18
- }
19
- /** 归档目录:<root>/.mocode/plans/archive/。finish 自动归档、用户手动 unarchive 都走这里。 */
20
- export function archiveDir() {
21
- return join(plansDir(), 'archive');
22
- }
23
- /** 单 plan 文件路径:<root>/.mocode/plans/<id>.md。可选 baseDir:默认 plans,也可指 archive。 */
24
- export function planPath(id, baseDir = plansDir()) {
25
- return join(baseDir, `${id}.md`);
26
- }
27
- /** 确保 plans 目录存在(惰性,工具每次写前调)。 */
28
- export function ensurePlansDir() {
29
- const d = plansDir();
30
- if (!existsSync(d))
31
- mkdirSync(d, { recursive: true });
32
- }
33
- /** 确保归档目录存在(惰性)。 */
34
- export function ensureArchiveDir() {
35
- const d = archiveDir();
36
- if (!existsSync(d))
37
- mkdirSync(d, { recursive: true });
38
- }
39
- // ── ID 生成 ──
40
- /** 本地时间戳(无时区后缀),与 filename 的 YYYY-MM-DDTHH-mm-ss 风格一致。
41
- * 例:2026-07-04T22:54:51.729(本地时,便于人对读 frontmatter / progress log,
42
- * 且与同 plan id 文件名"2026-07-04T22-54-51"对应)。
43
- * 不带 Z 后缀 → JS Date 解析会按本地时读(一致 round-trip);
44
- * 反例:toISOString() 返 2026-07-04T14:54:51.729Z(UTC),跟用户当地差 8 小时,易误解。 */
45
- export function localIsoTimestamp(d = new Date()) {
46
- const pad = (n) => String(n).padStart(2, '0');
47
- const padMs = (n) => String(n).padStart(3, '0');
48
- return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
49
- `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${padMs(d.getMilliseconds())}`);
50
- }
51
- /** plan id:YYYY-MM-DDTHH-mm-ss-xxxxxx(本地时间,文件名安全)。xxxxxx = 4 字节随机 hex(防同秒并发撞)。 */
52
- export function newPlanId(now = new Date()) {
53
- const pad = (n) => String(n).padStart(2, '0');
54
- const y = now.getFullYear();
55
- const mo = pad(now.getMonth() + 1);
56
- const d = pad(now.getDate());
57
- const h = pad(now.getHours());
58
- const mi = pad(now.getMinutes());
59
- const s = pad(now.getSeconds());
60
- // crypto 不必要;Math.random 够防同秒撞(碰撞概率 ~1/2^32)
61
- const r = Math.floor(Math.random() * 0xffffffff).toString(16).padStart(8, '0');
62
- return `${y}-${mo}-${d}T${h}-${mi}-${s}-${r}`;
63
- }
64
- // ── 解析(宽松,坏文件不抛,退化到最小可用 Plan)──
65
- const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
66
- const CHECKBOX_RE = /^(\s*)-\s+\[([ xX\/~])\]\s+(\d+)\.\s+(.+?)\s*$/;
67
- const LOG_RE = /^-\s+(\S+)\s+(.+?)\s*$/;
68
- /**
69
- * 解析 plan 文件为 Plan 对象。失败(无 frontmatter / 段缺失 / 坏 checkbox)→ 退化:
70
- * - 无 frontmatter:把整文当 title 段
71
- * - 缺字段:空串 / 空数组
72
- * - 坏 checkbox:跳过该行,不污染好行
73
- * 解析成功但 step id 缺序 / 重号:按出现顺序重排为 1..N(保稳定)。
74
- */
75
- export function parsePlan(raw, fallbackId) {
76
- const now = localIsoTimestamp();
77
- let meta = {};
78
- let body = raw;
79
- const m = FRONTMATTER_RE.exec(raw);
80
- if (m) {
81
- meta = parseFrontmatter(m[1]);
82
- body = m[2];
83
- }
84
- const id = meta.id || fallbackId;
85
- const title = meta.title || extractFirstHeading(body) || '(无标题)';
86
- const status = normalizePlanStatus(meta.status);
87
- const created = meta.created || now;
88
- const updated = meta.updated || now;
89
- const { goal, steps, log } = parseBody(body);
90
- // 步骤 id 规范化:1..N(防文件手改致 id 跳号)
91
- const normSteps = steps.map((s, i) => ({ ...s, id: i + 1 }));
92
- return { id, title, status, created, updated, goal, steps: normSteps, log };
93
- }
94
- function parseFrontmatter(s) {
95
- const out = {};
96
- for (const line of s.split('\n')) {
97
- const idx = line.indexOf(':');
98
- if (idx <= 0)
99
- continue;
100
- const k = line.slice(0, idx).trim();
101
- const v = line.slice(idx + 1).trim();
102
- if (k)
103
- out[k] = v;
104
- }
105
- return out;
106
- }
107
- function normalizePlanStatus(s) {
108
- if (s === 'finished' || s === 'abandoned')
109
- return s;
110
- return 'in_progress';
111
- }
112
- function extractFirstHeading(body) {
113
- const m = /^#\s+(.+?)\s*$/m.exec(body);
114
- return m ? m[1] : '';
115
- }
116
- function parseBody(body) {
117
- const sections = {};
118
- const re = /^##\s+(.+?)\s*$/gm;
119
- let lastKey = '';
120
- let lastStart = -1;
121
- let m;
122
- while ((m = re.exec(body)) !== null) {
123
- if (lastKey)
124
- sections[lastKey] = body.slice(lastStart, m.index);
125
- lastKey = m[1].trim();
126
- lastStart = m.index + m[0].length;
127
- }
128
- if (lastKey)
129
- sections[lastKey] = body.slice(lastStart);
130
- const goal = (sections['目标'] || sections['Goal'] || '').trim();
131
- const stepBlock = sections['步骤'] || sections['Steps'] || '';
132
- const logBlock = sections['进度日志'] || sections['Log'] || sections['进度'] || '';
133
- const steps = [];
134
- for (const line of stepBlock.split('\n')) {
135
- const cm = CHECKBOX_RE.exec(line);
136
- if (!cm)
137
- continue;
138
- const marker = cm[2].toLowerCase();
139
- const status = marker === 'x' ? 'done' :
140
- marker === '/' ? 'in_progress' :
141
- marker === '~' ? 'skipped' :
142
- 'pending';
143
- const id = Number(cm[3]);
144
- const title = cm[4].trim();
145
- if (!Number.isFinite(id) || id < 1)
146
- continue;
147
- steps.push({ id, title, status });
148
- }
149
- const log = [];
150
- for (const line of logBlock.split('\n')) {
151
- const lm = LOG_RE.exec(line);
152
- if (!lm)
153
- continue;
154
- log.push({ at: lm[1], text: lm[2] });
155
- }
156
- return { goal, steps, log };
157
- }
158
- // ── 序列化 ──
159
- /** 序列化为 markdown 文本。固定段序:frontmatter → 目标 → 步骤 → 进度日志。 */
160
- export function serializePlan(p) {
161
- const lines = [];
162
- lines.push('---');
163
- lines.push(`id: ${p.id}`);
164
- lines.push(`title: ${p.title}`);
165
- lines.push(`status: ${p.status}`);
166
- lines.push(`created: ${p.created}`);
167
- lines.push(`updated: ${p.updated}`);
168
- lines.push('---');
169
- lines.push('');
170
- lines.push(`# ${p.title}`);
171
- lines.push('');
172
- lines.push('## 目标');
173
- lines.push(p.goal || '(未填写)');
174
- lines.push('');
175
- lines.push('## 步骤');
176
- if (p.steps.length === 0) {
177
- lines.push('(无步骤)');
178
- }
179
- else {
180
- for (const s of p.steps) {
181
- lines.push(`- [${checkboxMarker(s.status)}] ${s.id}. ${s.title}`);
182
- }
183
- }
184
- lines.push('');
185
- lines.push('## 进度日志');
186
- if (p.log.length === 0) {
187
- lines.push('(无)');
188
- }
189
- else {
190
- for (const e of p.log) {
191
- lines.push(`- ${e.at} ${e.text}`);
192
- }
193
- }
194
- lines.push('');
195
- return lines.join('\n');
196
- }
197
- function checkboxMarker(s) {
198
- switch (s) {
199
- case 'done': return 'x';
200
- case 'in_progress': return '/';
201
- case 'skipped': return '~';
202
- case 'failed': return 'x'; // 用 x + log 标 failed(checkbox 集 [ ]/x/[x] 之外的细态不上,降级)
203
- case 'pending':
204
- default: return ' ';
205
- }
206
- }
207
- // ── CRUD(直接落盘)──
208
- /** 读 plan 文件;不存在 → null,坏文件 → fallbackId 最小可用 Plan(不抛,工具契约)。
209
- * 可选 baseDir:默认 plans,传 archiveDir() 可读归档。 */
210
- export function readPlan(id, baseDir = plansDir()) {
211
- const p = planPath(id, baseDir);
212
- if (!existsSync(p))
213
- return null;
214
- try {
215
- return parsePlan(readFileSync(p, 'utf8'), id);
216
- }
217
- catch {
218
- return null;
219
- }
220
- }
221
- /** 写 plan 文件(atomic:写 .tmp 再 rename)。返回是否成功。 */
222
- export function writePlan(p) {
223
- try {
224
- ensurePlansDir();
225
- const target = planPath(p.id);
226
- const tmp = target + '.tmp';
227
- writeFileSync(tmp, serializePlan(p), 'utf8');
228
- renameSync(tmp, target);
229
- return true;
230
- }
231
- catch {
232
- return false;
233
- }
234
- }
235
- /** 删 plan 文件;不存在静默 ok。可选 baseDir:默认 plans,传 archiveDir() 删归档。 */
236
- export function deletePlan(id, baseDir = plansDir()) {
237
- const p = planPath(id, baseDir);
238
- if (!existsSync(p))
239
- return true;
240
- try {
241
- unlinkSync(p);
242
- return true;
243
- }
244
- catch {
245
- return false;
246
- }
247
- }
248
- /**
249
- * 原子读-改-写:读 → mutator 改 → 写。mutator 返回 false 视为"无改动",跳过写。
250
- * mutator 抛错 → 写失败 → 返 null(不抛,工具契约)。
251
- *
252
- * mutator 同步:本 store 走同步 fs,mutator 必须同步(返 Promise 视为"无信号"——返原对象);
253
- * 设计如此以避免把 fs API 改成 async(同步 fs 在 node 单 tick 原子,无需锁)。
254
- */
255
- export function updatePlan(id, mutator) {
256
- const cur = readPlan(id);
257
- if (!cur)
258
- return null;
259
- let next;
260
- try {
261
- next = mutator(cur);
262
- }
263
- catch {
264
- return null;
265
- }
266
- if (next === false)
267
- return cur; // 无改动:返原对象
268
- next.updated = localIsoTimestamp();
269
- if (!writePlan(next))
270
- return null;
271
- return next;
272
- }
273
- /** 列 plans 目录下所有 plan(按 updated 倒序,最新在前)。解析失败的跳过。
274
- * scope: 'active'(默认)=仅 plans/;'archived'=仅 archive/;'all'=合并(去重按 id)。 */
275
- export function listPlans(scope = 'active') {
276
- const dirs = [];
277
- if (scope === 'active')
278
- dirs.push(plansDir());
279
- else if (scope === 'archived')
280
- dirs.push(archiveDir());
281
- else {
282
- dirs.push(plansDir());
283
- dirs.push(archiveDir());
284
- }
285
- const out = [];
286
- const seen = new Set();
287
- for (const dir of dirs) {
288
- if (!existsSync(dir))
289
- continue;
290
- let names;
291
- try {
292
- names = readdirSync(dir).filter((n) => n.endsWith('.md'));
293
- }
294
- catch {
295
- continue;
296
- }
297
- for (const n of names) {
298
- const id = n.slice(0, -3);
299
- if (seen.has(id))
300
- continue;
301
- seen.add(id);
302
- const p = readPlan(id, dir);
303
- if (p)
304
- out.push(p);
305
- }
306
- }
307
- out.sort((a, b) => (a.updated < b.updated ? 1 : a.updated > b.updated ? -1 : 0));
308
- return out;
309
- }
310
- // ── 归档/还原/全删 ──
311
- /** 把 plans/<id>.md mv 到 plans/archive/<id>.md(atomic rename)。
312
- * 源不存在 → false(已归档 / 删了 / 从未存在都算)。 */
313
- export function archivePlan(id) {
314
- const src = planPath(id, plansDir());
315
- if (!existsSync(src))
316
- return false;
317
- ensureArchiveDir();
318
- const dst = planPath(id, archiveDir());
319
- try {
320
- renameSync(src, dst);
321
- }
322
- catch {
323
- return false;
324
- }
325
- return true;
326
- }
327
- /** 把 plans/archive/<id>.md 还原到 plans/<id>.md。源不存在 → false。 */
328
- export function unarchivePlan(id) {
329
- const src = planPath(id, archiveDir());
330
- if (!existsSync(src))
331
- return false;
332
- ensurePlansDir();
333
- const dst = planPath(id, plansDir());
334
- try {
335
- renameSync(src, dst);
336
- }
337
- catch {
338
- return false;
339
- }
340
- return true;
341
- }
342
- /** 任一处存在就都删(活跃 + 归档)。用于 todolist delete 不关心来源。 */
343
- export function deletePlanAnywhere(id) {
344
- let any = false;
345
- if (existsSync(planPath(id, plansDir())))
346
- any = deletePlan(id, plansDir()) || any;
347
- if (existsSync(planPath(id, archiveDir())))
348
- any = deletePlan(id, archiveDir()) || any;
349
- return any;
350
- }
351
- // ── 渲染(给 LLM / UI 用)──
352
- /** 把 Plan 渲成给 LLM 看的紧凑摘要(多行文本)。 */
353
- export function renderPlanForLLM(p) {
354
- const lines = [];
355
- lines.push(`# ${p.title} [${p.status}]`);
356
- if (p.goal)
357
- lines.push(`\n## 目标\n${p.goal}`);
358
- const done = p.steps.filter((s) => s.status === 'done' || s.status === 'skipped').length;
359
- lines.push(`\n## 步骤 (${done}/${p.steps.length} 完成)`);
360
- if (p.steps.length === 0) {
361
- lines.push('(无)');
362
- }
363
- else {
364
- for (const s of p.steps) {
365
- lines.push(`- [${checkboxMarker(s.status)}] ${s.id}. ${s.title}`);
366
- }
367
- }
368
- if (p.log.length > 0) {
369
- lines.push(`\n## 进度日志(最近 ${Math.min(p.log.length, 5)} 条)`);
370
- for (const e of p.log.slice(-5))
371
- lines.push(`- ${e.at} ${e.text}`);
372
- }
373
- return lines.join('\n');
374
- }
375
- /**
376
- * 状态行 chip 的短摘要:`plan: 标题 (done/total) ▸ <当前 in_progress 步骤>`。
377
- * 无活跃 plan → 空串。
378
- *
379
- * 字符截断(非终端宽,固定长度 ~50 字符,方便状态行收纳):
380
- * - 标题(plan 标题):超 10 字截断,加 "..."
381
- * - 步骤标题:超 17 字截断,加 "..."
382
- * - 例:`plan: 3D 贪吃蛇游戏 (0/9) ▸ 1. 搭建项目骨架:创建 snake3d...`
383
- *
384
- * 当前步骤取 status=in_progress;无 in_progress 但有 pending 时回退到第一项 pending
385
- * (LLM 刚标记某步 done 还没动 next 时,这样能看清下一步)。
386
- * 全 done → 不带 tail;finished → 拼「✓ N/N」。
387
- *
388
- * maxWidth 是软上限:极端窄(<26)时只保 head+count 不带 tail;否则按上述字符限。
389
- */
390
- const PLAN_TITLE_MAX = 10;
391
- const STEP_TITLE_MAX = 17;
392
- const TRUNC_DOTS = '...';
393
- export function renderPlanChip(p, maxWidth = 56) {
394
- if (!p)
395
- return '';
396
- const title = truncateByChars(p.title, PLAN_TITLE_MAX);
397
- const head = p.status === 'finished'
398
- ? `plan ✓ ${title}`
399
- : `plan: ${title}`;
400
- const total = p.steps.length;
401
- const done = p.steps.filter((s) => s.status === 'done' || s.status === 'skipped').length;
402
- const count = `(${done}/${total})`;
403
- const fixed = `${head} ${count}`;
404
- // 极窄:没空间塞步骤,只保 head+count
405
- if (maxWidth < 26)
406
- return fixed;
407
- const cur = p.status === 'in_progress'
408
- ? p.steps.find((s) => s.status === 'in_progress')
409
- ?? p.steps.find((s) => s.status === 'pending')
410
- : null; // finished / abandoned → 不显当前步
411
- if (!cur)
412
- return fixed;
413
- const stepTitle = truncateByChars(cur.title, STEP_TITLE_MAX);
414
- return `${fixed} ▸ ${cur.id}. ${stepTitle}`;
415
- }
416
- /** 字符数截断(非显示宽):超 max 字符截到 max + "..."(3 字符省略号固定)。
417
- * <= max → 原样返回;> max → 前 max 字符 + "..."。 */
418
- function truncateByChars(s, max) {
419
- if (s.length <= max)
420
- return s;
421
- return s.slice(0, max) + TRUNC_DOTS;
422
- }
423
- function truncateForChip(s, max = 18) {
424
- if (s.length <= max)
425
- return s;
426
- if (max <= 1)
427
- return '…';
428
- return s.slice(0, max - 1) + '…';
429
- }
@@ -1,95 +0,0 @@
1
- /**
2
- * 项目专属 Skill — 跨 session 持久化的项目开发知识。
3
- *
4
- * 每个项目可维护一个专属 skill 文件(.mocode/project-skill.md),
5
- * 记录开发约定、架构决策、常见坑点等。开关启用后:
6
- * 1. 每次启动时内容注入系统提示词(buildProjectSkillSection)
7
- * 2. Agent 可通过 project_skill_update 工具动态更新
8
- * 3. 随着项目开发,skill 越来越了解项目,开发效率越来越高
9
- *
10
- * 存储位置: <cwd>/.mocode/project-skill.md
11
- * 开关: MOCODE_PROJECT_SKILL 环境变量,默认 false(零侵入)
12
- *
13
- * 与现有功能的关系:
14
- * - 通用 skill(SKILLS_DIRS): 按需加载(use_skill);项目 skill: 始终注入
15
- * - memory: 碎片化 fact/decision; 项目 skill: 结构化知识总结
16
- * - snapshot: 静态文件缓存(只读); 项目 skill: 动态知识(可写)
17
- */
18
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs';
19
- import path from 'node:path';
20
- /** 项目 skill 文件路径 */
21
- function skillPath() {
22
- return path.join(process.cwd(), '.mocode', 'project-skill.md');
23
- }
24
- /** 备份文件路径 */
25
- function backupPath() {
26
- return path.join(process.cwd(), '.mocode', 'project-skill.md.bak');
27
- }
28
- /** 读取项目 skill 内容。不存在/读失败 → null */
29
- export function readProjectSkill() {
30
- const p = skillPath();
31
- if (!existsSync(p))
32
- return null;
33
- try {
34
- const content = readFileSync(p, 'utf8').trim();
35
- return content || null;
36
- }
37
- catch {
38
- return null;
39
- }
40
- }
41
- /** 写入/更新项目 skill。内容不设字符上限;写入前备份旧内容。 */
42
- export function writeProjectSkill(content) {
43
- const trimmed = content.trim();
44
- const p = skillPath();
45
- const dir = path.dirname(p);
46
- try {
47
- if (existsSync(p)) {
48
- copyFileSync(p, backupPath());
49
- }
50
- mkdirSync(dir, { recursive: true });
51
- writeFileSync(p, trimmed + '\n', 'utf8');
52
- return { ok: true };
53
- }
54
- catch (e) {
55
- return { ok: false, error: `写入失败: ${e.message}` };
56
- }
57
- }
58
- /** 追加内容到项目 skill 末尾(以 \n\n 分隔),内容不设字符上限。 */
59
- export function appendProjectSkill(addition) {
60
- const existing = readProjectSkill() ?? '';
61
- const trimmed = addition.trim();
62
- if (!trimmed)
63
- return { ok: false, error: '追加内容为空' };
64
- const separator = existing ? '\n\n' : '';
65
- return writeProjectSkill(existing + separator + trimmed);
66
- }
67
- /** 兼容旧调用名:字数限制取消后直接写入,不再调用 LLM 压缩。 */
68
- export async function writeProjectSkillWithCompression(content, _maxAttempts = 3, _signal) {
69
- return { ...writeProjectSkill(content), compressed: false };
70
- }
71
- /** 兼容旧调用名:字数限制取消后直接追加,不再调用 LLM 压缩。 */
72
- export async function appendProjectSkillWithCompression(addition, _maxAttempts = 3, _signal) {
73
- return { ...appendProjectSkill(addition), compressed: false };
74
- }
75
- /**
76
- * 生成系统提示词注入段。
77
- * 开关关闭或文件不存在 → 空串(零行为变化)。
78
- *
79
- * 精简版:只注入 skill 内容本身,维护指南移到 project_skill_update 工具描述中。
80
- */
81
- export function buildProjectSkillSection() {
82
- const content = readProjectSkill();
83
- if (!content)
84
- return '';
85
- return [
86
- '',
87
- '',
88
- '## Project Skill (project-specific knowledge)',
89
- 'The following is project-specific knowledge maintained across sessions. Use it as context for all tasks in this project.',
90
- '',
91
- '<project-skill>',
92
- content,
93
- '</project-skill>',
94
- ].join('\n');
95
- }
@@ -1,178 +0,0 @@
1
- /**
2
- * 项目 Skill 初始化器 - 派生子 agent 带工具深度探索项目,生成高质量的初始 skill
3
- */
4
- import { spawnAgent } from '../agent/spawn.js';
5
- /**
6
- * 子 agent 的系统提示后缀:角色与输出格式约束
7
- */
8
- const SKILL_INIT_SUFFIX = `You are a project exploration agent. Your task is to deeply understand this project and generate a concise "Project Skill" document.
9
-
10
- ## ⚠️ Complementary to Snapshot — CRITICAL
11
- The following information is ALREADY provided by Project Snapshot — **do NOT repeat**:
12
- - ✗ Project one-liner description (快照已有)
13
- - ✗ Module names and file locations (快照 srcTree 已有)
14
- - ✗ Tech stack names and versions (快照 techStack 已有)
15
- - ✗ Build/test commands (快照 keyCommands 已有)
16
- - ✗ File lists and directory structure (快照 srcTree 已有)
17
-
18
- **Your job is to capture INSIGHTS only:**
19
- - **WHY** — design decisions, trade-offs, reasons for choosing X over Y
20
- - **HOW** — module behaviors, data flows, call chains, non-obvious interactions
21
- - **GOTCHAS** — pitfalls, edge cases, non-intuitive behaviors
22
- - **CONVENTIONS** — naming patterns, code style, unwritten rules
23
-
24
- ## Exploration Strategy
25
- 1. Use \`codegraph\` to trace call chains and understand module interactions
26
- 2. Read core modules to understand **behaviors** (not just names)
27
- 3. Look for complex logic, error handling patterns, unusual configs
28
- 4. Identify naming conventions, common abstractions
29
-
30
- ## Output Format
31
- Output the skill document between these exact delimiters:
32
-
33
- \`\`\`skill-start
34
- ## 架构要点
35
- ### 核心模块及职责
36
- - **\`path/to/module\`** — 行为描述(做什么 + 怎么做)
37
- ...
38
-
39
- ### 关键设计决策
40
- - 决策:原因
41
- ...
42
-
43
- ## 项目约定
44
- - 约定(具体的,有例子的)
45
- ...
46
-
47
- ## 开发流程
48
- - 命令 + 注意事项/坑点(命令本身快照已有,这里只写注意什么)
49
- ...
50
-
51
- ## 常见坑点
52
- - 坑:解法
53
- ...
54
- \`\`\`skill-end
55
-
56
- ## Rules — MUST FOLLOW
57
- 1. 禁止列出文件名清单或目录结构
58
- 2. 禁止列出依赖名和版本
59
- 3. 禁止写项目概述/一句话描述
60
- 4. 禁止只写命令本身(快照已有),只写注意事项
61
- 5. 每个模块描述必须包含行为(做什么 + 怎么协作),不只是名称
62
- 6. **总长度目标 3000-4000 字符,硬上限 4000**
63
- 7. 写可操作的内容,用具体路径和例子
64
- 8. 如果某个 section 没有实质内容,省略它
65
- `;
66
- /**
67
- * 生成初始项目 Skill(使用子 agent 深度探索)
68
- * @param existingSkill 已有的 skill 内容(如果有,子 agent 会在其基础上优化)
69
- * @param signal AbortSignal
70
- */
71
- export async function generateInitialSkill(existingSkill, signal) {
72
- const hasExisting = !!existingSkill;
73
- let prompt;
74
- if (hasExisting) {
75
- prompt = `以下是当前的项目 Skill 内容,请深度探索项目并优化/完善它:
76
-
77
- \`\`\`markdown
78
- ${existingSkill}
79
- \`\`\`
80
-
81
- ## 优化方向
82
- 1. 补充缺失的信息(架构要点、设计决策、调用链等)
83
- 2. 修正过时或不准确的内容
84
- 3. 让描述更具体(用实际路径和例子)
85
- 4. 删除冗余或空泛的描述
86
- 5. 如果发现新的坑点或约定,添加进去
87
-
88
- 记住:保持精简,总长度控制在 4000-5000 字符。输出完整的优化后版本。`;
89
- }
90
- else {
91
- prompt = `请深度探索当前项目,生成一份项目专属 Skill 文档。
92
-
93
- ## 探索步骤
94
- 1. 先用 glob 扫描项目结构(**/*.ts, **/*.json 等)
95
- 2. 读取 package.json、README.md、tsconfig.json 等关键文件
96
- 3. 用 codegraph 或 grep 找到主入口、核心模块、关键接口
97
- 4. 理解架构模式、数据流向、调用关系
98
- 5. 识别命名约定、代码风格、测试策略
99
- 6. 找出可能的坑点(复杂配置、特殊依赖、构建陷阱)
100
-
101
- ## 输出
102
- 探索完成后,将完整的 skill 文档输出在 \`\`\`skill-start 和 \`\`\`skill-end 之间。
103
-
104
- 记住:内容要精简、可操作、有具体路径和例子。总长度控制在 4000-5000 字符。`;
105
- }
106
- try {
107
- const result = await spawnAgent({
108
- prompt,
109
- systemPromptSuffix: SKILL_INIT_SUFFIX,
110
- tools: ['read_file', 'glob', 'grep', 'codegraph'],
111
- maxSteps: 30,
112
- signal,
113
- });
114
- if (!result.completed) {
115
- return {
116
- ok: false,
117
- error: '子 agent 未完成(可能中断或超时)',
118
- transcript: result.transcript,
119
- };
120
- }
121
- if (!result.summary) {
122
- return {
123
- ok: false,
124
- error: '子 agent 未返回内容',
125
- transcript: result.transcript,
126
- };
127
- }
128
- // 从 summary 中提取 skill 内容
129
- const skillMatch = result.summary.match(/```skill-start\n([\s\S]*?)\n```skill-end/);
130
- if (!skillMatch || !skillMatch[1]) {
131
- // 如果没有找到 delimiters,尝试从最后一段 markdown 代码块提取
132
- const codeBlockMatch = result.summary.match(/```(?:markdown|md)?\n([\s\S]*?)\n```/);
133
- if (codeBlockMatch && codeBlockMatch[1]) {
134
- return {
135
- ok: true,
136
- content: codeBlockMatch[1].trim(),
137
- transcript: result.transcript,
138
- };
139
- }
140
- // 如果连代码块都没有,直接用 summary(但去掉开头的探索日志)
141
- const lines = result.summary.split('\n');
142
- const skillStartIdx = lines.findIndex((l) => l.startsWith('## '));
143
- if (skillStartIdx > 0) {
144
- return {
145
- ok: true,
146
- content: lines.slice(skillStartIdx).join('\n').trim(),
147
- transcript: result.transcript,
148
- };
149
- }
150
- return {
151
- ok: false,
152
- error: '无法从子 agent 输出中提取 skill 内容',
153
- transcript: result.transcript,
154
- };
155
- }
156
- const content = skillMatch[1].trim();
157
- // 检查长度
158
- if (content.length > 6000) {
159
- return {
160
- ok: false,
161
- error: `生成内容过长 (${content.length} 字符),请手动精简后重试`,
162
- transcript: result.transcript,
163
- };
164
- }
165
- return {
166
- ok: true,
167
- content,
168
- transcript: result.transcript,
169
- };
170
- }
171
- catch (e) {
172
- return {
173
- ok: false,
174
- error: e instanceof Error ? e.message : '未知错误',
175
- transcript: '',
176
- };
177
- }
178
- }