mocode-ai 1.2.7 → 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.
- package/dist/agent/core.js +126 -43
- package/dist/config/index.js +31 -6
- package/dist/config/presets.js +36 -1
- package/dist/context/budget.js +5 -16
- package/dist/llm/index.js +42 -4
- package/dist/repl/index.js +57 -13
- package/dist/session/compact.js +329 -40
- package/dist/session/notes.js +63 -0
- package/dist/session/scheduler.js +5 -9
- package/dist/tools/constants.js +21 -0
- package/package.json +1 -1
- package/dist/__trace_manual_test__.js +0 -1
- package/dist/agent/core.d.ts +0 -112
- package/dist/agent/index.d.ts +0 -19
- package/dist/agent/mode.d.ts +0 -29
- package/dist/agent/retry-classifier.js +0 -64
- package/dist/agent/spawn.d.ts +0 -36
- package/dist/attachments/image.d.ts +0 -22
- package/dist/commands/config.d.ts +0 -8
- package/dist/commands/skill.js +0 -230
- package/dist/config/file.d.ts +0 -23
- package/dist/config/index.d.ts +0 -114
- package/dist/config/presets.d.ts +0 -57
- package/dist/context/budget.d.ts +0 -79
- package/dist/context/classifier.d.ts +0 -14
- package/dist/context/encoders/_util.d.ts +0 -11
- package/dist/context/encoders/code.d.ts +0 -2
- package/dist/context/encoders/doc.d.ts +0 -14
- package/dist/context/encoders/graph.d.ts +0 -11
- package/dist/context/encoders/graph.js +0 -81
- package/dist/context/encoders/index.d.ts +0 -2
- package/dist/context/encoders/log.d.ts +0 -2
- package/dist/context/encoders/log.js +0 -2
- package/dist/context/encoders/memory.d.ts +0 -2
- package/dist/context/encoders/passthrough.d.ts +0 -11
- package/dist/context/encoders/search.d.ts +0 -2
- package/dist/context/encoders/summary.d.ts +0 -12
- package/dist/context/encoders/table.d.ts +0 -2
- package/dist/context/encoders/tree.d.ts +0 -2
- package/dist/context/index.d.ts +0 -6
- package/dist/context/lifecycle.d.ts +0 -51
- package/dist/context/pipeline.d.ts +0 -9
- package/dist/context/registry.d.ts +0 -9
- package/dist/context/relevance.d.ts +0 -65
- package/dist/context/types.d.ts +0 -36
- package/dist/foo.js +0 -28
- package/dist/foo.test.js +0 -12
- package/dist/index.d.ts +0 -1
- package/dist/llm/capabilities.d.ts +0 -19
- package/dist/llm/index.d.ts +0 -111
- package/dist/memory/discover.d.ts +0 -15
- package/dist/memory/index.d.ts +0 -13
- package/dist/memory/reflect.d.ts +0 -34
- package/dist/memory/store.d.ts +0 -124
- package/dist/pet/bridge.d.ts +0 -73
- package/dist/pet/protocol.d.ts +0 -83
- package/dist/pet/state.d.ts +0 -28
- package/dist/plan/active.d.ts +0 -23
- package/dist/plan/active.js +0 -45
- package/dist/plan/index.d.ts +0 -13
- package/dist/plan/index.js +0 -33
- package/dist/plan/state.d.ts +0 -2
- package/dist/plan/state.js +0 -11
- package/dist/plan/store.d.ts +0 -78
- package/dist/plan/store.js +0 -429
- package/dist/project-skill/index.js +0 -95
- package/dist/project-skill/initializer.js +0 -178
- package/dist/project-snapshot/index.js +0 -85
- package/dist/project-snapshot/llm-snapshot.js +0 -150
- package/dist/project-snapshot/static-files.js +0 -87
- package/dist/repl/index.d.ts +0 -22
- package/dist/rollback/index.d.ts +0 -79
- package/dist/sandbox/command.d.ts +0 -3
- package/dist/sandbox/index.d.ts +0 -4
- package/dist/sandbox/jail.d.ts +0 -17
- package/dist/sandbox/policy.d.ts +0 -21
- package/dist/sandbox/root.d.ts +0 -5
- package/dist/session/compact.d.ts +0 -92
- package/dist/session/index.d.ts +0 -13
- package/dist/session/notes-plan.js +0 -41
- package/dist/session/persist.d.ts +0 -30
- package/dist/session/scheduler.d.ts +0 -53
- package/dist/skills/discover.d.ts +0 -33
- package/dist/skills/index.d.ts +0 -13
- package/dist/skills/skill-eval.js +0 -345
- package/dist/skills/skill-improve.js +0 -221
- package/dist/skills/stats.js +0 -102
- package/dist/tools/builtins/apply-patch.js +0 -174
- package/dist/tools/builtins/ask-human.d.ts +0 -9
- package/dist/tools/builtins/codegraph.d.ts +0 -2
- package/dist/tools/builtins/codegraph.js +0 -128
- package/dist/tools/builtins/edit-file.d.ts +0 -2
- package/dist/tools/builtins/glob.d.ts +0 -2
- package/dist/tools/builtins/grep.d.ts +0 -2
- package/dist/tools/builtins/index.d.ts +0 -2
- package/dist/tools/builtins/memory-forget.d.ts +0 -2
- package/dist/tools/builtins/memory-list.d.ts +0 -2
- package/dist/tools/builtins/memory-save.d.ts +0 -2
- package/dist/tools/builtins/memory-search.d.ts +0 -2
- package/dist/tools/builtins/memory-update.d.ts +0 -2
- package/dist/tools/builtins/project-skill-update.js +0 -102
- package/dist/tools/builtins/read-file.d.ts +0 -2
- package/dist/tools/builtins/run-command.d.ts +0 -2
- package/dist/tools/builtins/switch-mode.d.ts +0 -2
- package/dist/tools/builtins/switch-mode.js +0 -44
- package/dist/tools/builtins/task.d.ts +0 -2
- package/dist/tools/builtins/todolist.d.ts +0 -2
- package/dist/tools/builtins/todolist.js +0 -411
- package/dist/tools/builtins/use-skill.d.ts +0 -2
- package/dist/tools/builtins/web-fetch.d.ts +0 -2
- package/dist/tools/builtins/web-search.d.ts +0 -2
- package/dist/tools/builtins/write-file.d.ts +0 -2
- package/dist/tools/constants.d.ts +0 -52
- package/dist/tools/registry.d.ts +0 -18
- package/dist/tools/retry.js +0 -137
- package/dist/tools/types.d.ts +0 -42
- package/dist/ui/batch.d.ts +0 -67
- package/dist/ui/clipboard.d.ts +0 -9
- package/dist/ui/content.d.ts +0 -55
- package/dist/ui/diff.d.ts +0 -34
- package/dist/ui/intervention.d.ts +0 -46
- package/dist/ui/layout.d.ts +0 -225
- package/dist/ui/markdown.d.ts +0 -46
- package/dist/ui/mouse.d.ts +0 -29
- package/dist/ui/prompt.d.ts +0 -61
- package/dist/ui/render.d.ts +0 -60
- package/dist/ui/spinner.d.ts +0 -26
- package/dist/ui/theme.d.ts +0 -47
- package/dist/ui/thinking.js +0 -80
package/dist/plan/index.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// plan 子系统 barrel:
|
|
2
|
-
// - store:文件级 CRUD(markdown 解析/序列化,落盘原子)
|
|
3
|
-
// - active:进程级活跃 plan 缓存 + 变更通知(单 plan/会话)
|
|
4
|
-
// - buildActivePlanSection:给 systemPrompt 用的活跃 plan 摘要段
|
|
5
|
-
//
|
|
6
|
-
// 被 repl 依赖(注入 systemPrompt + 状态行 + listener 注册)+ tools/builtins/todolist 依赖。
|
|
7
|
-
export {
|
|
8
|
-
// store
|
|
9
|
-
plansDir, archiveDir, planPath, ensurePlansDir, ensureArchiveDir, newPlanId, parsePlan, serializePlan, readPlan, writePlan, deletePlan, deletePlanAnywhere, updatePlan, listPlans, archivePlan, unarchivePlan, renderPlanForLLM, renderPlanChip, } from './store.js';
|
|
10
|
-
export { getActivePlan, setActivePlan, clearActivePlan, onActivePlanChange, hasActivePlan, getActivePlanSummary, } from './active.js';
|
|
11
|
-
import { getActivePlan } from './active.js';
|
|
12
|
-
import { renderPlanForLLM } from './store.js';
|
|
13
|
-
const ACTIVE_PLAN_HEADER = '## 当前活跃计划';
|
|
14
|
-
/**
|
|
15
|
-
* 拼给 systemPrompt 注入的活跃 plan 摘要段。
|
|
16
|
-
* - 无活跃 plan → 空串(repl 直接跳过拼接,systemPrompt 长度不变)。
|
|
17
|
-
* - 有 → 紧凑 markdown(目标 + 步骤 checkbox + 进度日志末 5 条),header + 内容。
|
|
18
|
-
*
|
|
19
|
-
* 触发:setActivePlan 后由 repl listener 调;也供 buildSystemMessage 直接同步取(repl 入口)。
|
|
20
|
-
* 上限:超 MAX_ACTIVE_PLAN_CHARS 截到尾部(罕见,plan 文件本身就小)。
|
|
21
|
-
*/
|
|
22
|
-
export function buildActivePlanSection() {
|
|
23
|
-
const p = getActivePlan();
|
|
24
|
-
if (!p)
|
|
25
|
-
return '';
|
|
26
|
-
const body = renderPlanForLLM(p);
|
|
27
|
-
const full = `${ACTIVE_PLAN_HEADER}\n${body}`;
|
|
28
|
-
if (full.length <= MAX_ACTIVE_PLAN_CHARS)
|
|
29
|
-
return full;
|
|
30
|
-
return full.slice(0, MAX_ACTIVE_PLAN_CHARS) + '\n…(plan 摘要已截断)';
|
|
31
|
-
}
|
|
32
|
-
/** 注入 systemPrompt 的活跃 plan 摘要上限(字符)。 */
|
|
33
|
-
export const MAX_ACTIVE_PLAN_CHARS = 3000;
|
package/dist/plan/state.d.ts
DELETED
package/dist/plan/state.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// 活跃 plan 的进程级状态(共享叶子)。
|
|
2
|
-
//
|
|
3
|
-
// 职责:
|
|
4
|
-
// - 跟踪「当前会话的活跃 plan」——单 plan/会话(state 缓存,store 是文件源)。
|
|
5
|
-
// - 暴露 onActivePlanChange listener(repl 借此刷 status 行 + history[0])。
|
|
6
|
-
//
|
|
7
|
-
// 与 mode.ts 同形态:零依赖、不落盘、不反向引用业务;被 plan/、tools/builtins/todolist.ts、repl 依赖。
|
|
8
|
-
//
|
|
9
|
-
// 不在子 agent 持久化(子 agent 走 spawn,独立 history,不该继承主 plan——但 plan 文件本身在 sandboxRoot
|
|
10
|
-
// 下,子 agent 仍能 readPlan 看到全部历史 plan;state 缓存不传,只属于主 agent)。
|
|
11
|
-
export { getActivePlan, setActivePlan, clearActivePlan, onActivePlanChange, hasActivePlan, getActivePlanSummary, } from './active.js';
|
package/dist/plan/store.d.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
export type StepStatus = 'pending' | 'in_progress' | 'done' | 'skipped' | 'failed';
|
|
2
|
-
export interface PlanStep {
|
|
3
|
-
/** 1-based 序号(在当前 plan 内稳定,即便中间 add_step 也不重用旧号——见 addStep)。 */
|
|
4
|
-
id: number;
|
|
5
|
-
title: string;
|
|
6
|
-
status: StepStatus;
|
|
7
|
-
}
|
|
8
|
-
export type PlanStatus = 'in_progress' | 'finished' | 'abandoned';
|
|
9
|
-
export interface Plan {
|
|
10
|
-
id: string;
|
|
11
|
-
title: string;
|
|
12
|
-
status: PlanStatus;
|
|
13
|
-
created: string;
|
|
14
|
-
updated: string;
|
|
15
|
-
goal: string;
|
|
16
|
-
steps: PlanStep[];
|
|
17
|
-
/** 进度日志条目(时间戳 + 一行说明),append-only。 */
|
|
18
|
-
log: {
|
|
19
|
-
at: string;
|
|
20
|
-
text: string;
|
|
21
|
-
}[];
|
|
22
|
-
}
|
|
23
|
-
/** plans 目录:<sandboxRoot>/.mocode/plans/。无 sandboxRoot 退到 cwd(防御)。 */
|
|
24
|
-
export declare function plansDir(): string;
|
|
25
|
-
/** 归档目录:<root>/.mocode/plans/archive/。finish 自动归档、用户手动 unarchive 都走这里。 */
|
|
26
|
-
export declare function archiveDir(): string;
|
|
27
|
-
/** 单 plan 文件路径:<root>/.mocode/plans/<id>.md。可选 baseDir:默认 plans,也可指 archive。 */
|
|
28
|
-
export declare function planPath(id: string, baseDir?: string): string;
|
|
29
|
-
/** 确保 plans 目录存在(惰性,工具每次写前调)。 */
|
|
30
|
-
export declare function ensurePlansDir(): void;
|
|
31
|
-
/** 确保归档目录存在(惰性)。 */
|
|
32
|
-
export declare function ensureArchiveDir(): void;
|
|
33
|
-
/** 本地时间戳(无时区后缀),与 filename 的 YYYY-MM-DDTHH-mm-ss 风格一致。
|
|
34
|
-
* 例:2026-07-04T22:54:51.729(本地时,便于人对读 frontmatter / progress log,
|
|
35
|
-
* 且与同 plan id 文件名"2026-07-04T22-54-51"对应)。
|
|
36
|
-
* 不带 Z 后缀 → JS Date 解析会按本地时读(一致 round-trip);
|
|
37
|
-
* 反例:toISOString() 返 2026-07-04T14:54:51.729Z(UTC),跟用户当地差 8 小时,易误解。 */
|
|
38
|
-
export declare function localIsoTimestamp(d?: Date): string;
|
|
39
|
-
/** plan id:YYYY-MM-DDTHH-mm-ss-xxxxxx(本地时间,文件名安全)。xxxxxx = 4 字节随机 hex(防同秒并发撞)。 */
|
|
40
|
-
export declare function newPlanId(now?: Date): string;
|
|
41
|
-
/**
|
|
42
|
-
* 解析 plan 文件为 Plan 对象。失败(无 frontmatter / 段缺失 / 坏 checkbox)→ 退化:
|
|
43
|
-
* - 无 frontmatter:把整文当 title 段
|
|
44
|
-
* - 缺字段:空串 / 空数组
|
|
45
|
-
* - 坏 checkbox:跳过该行,不污染好行
|
|
46
|
-
* 解析成功但 step id 缺序 / 重号:按出现顺序重排为 1..N(保稳定)。
|
|
47
|
-
*/
|
|
48
|
-
export declare function parsePlan(raw: string, fallbackId: string): Plan;
|
|
49
|
-
/** 序列化为 markdown 文本。固定段序:frontmatter → 目标 → 步骤 → 进度日志。 */
|
|
50
|
-
export declare function serializePlan(p: Plan): string;
|
|
51
|
-
/** 读 plan 文件;不存在 → null,坏文件 → fallbackId 最小可用 Plan(不抛,工具契约)。
|
|
52
|
-
* 可选 baseDir:默认 plans,传 archiveDir() 可读归档。 */
|
|
53
|
-
export declare function readPlan(id: string, baseDir?: string): Plan | null;
|
|
54
|
-
/** 写 plan 文件(atomic:写 .tmp 再 rename)。返回是否成功。 */
|
|
55
|
-
export declare function writePlan(p: Plan): boolean;
|
|
56
|
-
/** 删 plan 文件;不存在静默 ok。可选 baseDir:默认 plans,传 archiveDir() 删归档。 */
|
|
57
|
-
export declare function deletePlan(id: string, baseDir?: string): boolean;
|
|
58
|
-
/**
|
|
59
|
-
* 原子读-改-写:读 → mutator 改 → 写。mutator 返回 false 视为"无改动",跳过写。
|
|
60
|
-
* mutator 抛错 → 写失败 → 返 null(不抛,工具契约)。
|
|
61
|
-
*
|
|
62
|
-
* mutator 同步:本 store 走同步 fs,mutator 必须同步(返 Promise 视为"无信号"——返原对象);
|
|
63
|
-
* 设计如此以避免把 fs API 改成 async(同步 fs 在 node 单 tick 原子,无需锁)。
|
|
64
|
-
*/
|
|
65
|
-
export declare function updatePlan(id: string, mutator: (p: Plan) => Plan | false): Plan | null;
|
|
66
|
-
/** 列 plans 目录下所有 plan(按 updated 倒序,最新在前)。解析失败的跳过。
|
|
67
|
-
* scope: 'active'(默认)=仅 plans/;'archived'=仅 archive/;'all'=合并(去重按 id)。 */
|
|
68
|
-
export declare function listPlans(scope?: 'active' | 'archived' | 'all'): Plan[];
|
|
69
|
-
/** 把 plans/<id>.md mv 到 plans/archive/<id>.md(atomic rename)。
|
|
70
|
-
* 源不存在 → false(已归档 / 删了 / 从未存在都算)。 */
|
|
71
|
-
export declare function archivePlan(id: string): boolean;
|
|
72
|
-
/** 把 plans/archive/<id>.md 还原到 plans/<id>.md。源不存在 → false。 */
|
|
73
|
-
export declare function unarchivePlan(id: string): boolean;
|
|
74
|
-
/** 任一处存在就都删(活跃 + 归档)。用于 todolist delete 不关心来源。 */
|
|
75
|
-
export declare function deletePlanAnywhere(id: string): boolean;
|
|
76
|
-
/** 把 Plan 渲成给 LLM 看的紧凑摘要(多行文本)。 */
|
|
77
|
-
export declare function renderPlanForLLM(p: Plan): string;
|
|
78
|
-
export declare function renderPlanChip(p: Plan | null, maxWidth?: number): string;
|
package/dist/plan/store.js
DELETED
|
@@ -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
|
-
}
|