cordis-plugin-sofagent-evolve
经验沉淀——think.md 反思 + Dream Cycle + evolve + instinct→skill + refine(seam: turn/end)——桥接 @sofagent/think generateThinkEntry——DSH(DeepSeek Harness)cordis plugin。sofagent 约束层在 DeepSeek Harness 生态的插件形态。
Install
npx skills add https://github.com/KongFangXun/sofagent/tree/main/engine/dsh-plugins/cordis-plugin-sofagent-evolve
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install kongfangxun-sofagent@llmmart
git clone https://github.com/KongFangXun/sofagent.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole kongfangxun/sofagent collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
cordis-plugin-sofagent-evolve
经验沉淀——think.md 反思 + Dream Cycle + evolve + instinct→skill + refine(seam: session/event)——桥接 @sofagent/think generateThinkEntry
用途
装上之后:任务收尾自动写一条 think.md 反思条目。什么时候用:同一个坑反复踩、想让经验留下来被下次检索。
接入点(seam: session/event):桥接 @sofagent/think,缺依赖时该能力静默跳过;接入形态(声明 / 实现)见 SEAMS.md。
本插件随 sofagent 主线版本发布(SkillHub 通道:skillhub install cordis-plugin-sofagent-evolve 安装与检索;npm 通道未开通)。版本号与 sofagent 主线对齐。
相关链接
- sofagent 主仓:https://github.com/KongFangXun/sofagent
- 开发日志:docs/changelog/v1.4/v1.4.0.md(DSH 插件家族)
Files (sofagent)
-
src
-
index.test.ts 8.8 KB
// ============================================================ // cordis-plugin-evolve · 插件单测(v1.4.0 交付五 · v1.5.0 章十补接线面) // ============================================================ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import pluginDefault, { pluginMeta, capability, invoke } from './index'; import { parseDiff, getDataDir } from '@sofagent/core'; import { runRules } from '@sofagent/audit'; import { generateThinkEntry } from '@sofagent/think'; // 章十:沉淀链三源(parseDiff → runRules → generateThinkEntry)打桩成确定性替身—— // 接线正确性 = 三源被调用的**顺序与参数**,与引擎内部实现无关(引擎自有其单测)。 vi.mock('@sofagent/core', () => ({ parseDiff: vi.fn(() => ['src/a.ts']), getDataDir: vi.fn(() => '/data/dir'), })); vi.mock('@sofagent/audit', () => ({ runRules: vi.fn(async () => [{ rule: 'no-archaeology', pass: true }]), })); vi.mock('@sofagent/think', () => ({ // 返回值须有定义:既有元数据用例用 `invoke()` 走同一桥接面(返回 undefined 会被判失败) generateThinkEntry: vi.fn(async () => ({ ok: true, entry: 'think-entry' })), })); describe('cordis-plugin-sofagent-evolve', () => { it('插件元数据完整(id/version/description/seam)', () => { expect(pluginMeta.id).toBe('cordis-plugin-sofagent-evolve'); expect(pluginMeta.version).toBe(require('../package.json').version); // SSOT 对齐:版本跟随主版本(读 package.json) expect(pluginMeta.description.length).toBeGreaterThan(10); expect(pluginMeta.seam.length).toBeGreaterThan(0); }); it('能力说明非空', () => { expect(capability.length).toBeGreaterThan(0); }); it('invoke 可调用(成功或降级,不挂死)', async () => { const r = await invoke().catch((e) => e); expect(r).toBeDefined(); }); }); // ───────────────────────────────────────────────────────────────────────────── // v1.5.0 章十 · seam 事件接线(seam 从声明到实现) // 断言面:① 订阅真实存在 ② disposer 收进复合卸载契约 ③ turn/end **真沉淀** // ④ 其余会话事件 / 空 diff / 判定源缺席 → 零副作用(fail-open) // 🔴 关键形状:订阅点是 `session/event`(`turn/end` 是会话事件类型,由过滤条件承载), // 宿主/仓内都没有 ctx 级 `turn/end` 派发点——`ctx.on('turn/end')` 会永不触发。 // ───────────────────────────────────────────────────────────────────────────── describe('章十 · seam 事件接线', () => { const MANIFEST = require('../../plugins.json') as { plugins: Array<{ id: string; seamHandlers?: string[]; bridges?: Array<{ pkg: string; api: string }>; }>; }; const entry = MANIFEST.plugins.find((p) => p.id === 'cordis-plugin-sofagent-evolve'); const declared: string[] = entry?.seamHandlers ?? []; const ONE = ['session/event']; const errSpy = () => vi.spyOn(console, 'error').mockImplementation(() => {}); beforeEach(() => { vi.clearAllMocks(); delete process.env.SOFAGENT_TASK; delete process.env.SOFAGENT_PROJECT_ROOT; vi.mocked(parseDiff).mockImplementation(() => ['src/a.ts']); vi.mocked(getDataDir).mockImplementation(() => '/data/dir'); vi.mocked(runRules).mockImplementation(async () => [{ rule: 'no-archaeology', pass: true }]); vi.mocked(generateThinkEntry).mockImplementation(async () => ({ ok: true, entry: 'think-entry' })); }); afterEach(() => { vi.restoreAllMocks(); delete process.env.SOFAGENT_TASK; delete process.env.SOFAGENT_PROJECT_ROOT; }); /** 宿主 ctx 替身:记录订阅名 + 保留 disposer */ function hostCtx() { const subscribed = new Map<string, (...a: unknown[]) => unknown>(); const disposed: string[] = []; const on = vi.fn((event: string, handler: (...a: unknown[]) => unknown) => { subscribed.set(event, handler); return () => { disposed.push(event); }; }); return { ctx: { provide: vi.fn(() => () => undefined), on }, subscribed, disposed }; } const sessionOf = (cwd?: string) => ({ header: cwd === undefined ? {} : { cwd } }); it('seamHandlers 与 plugins.json 声明同集合(三处对账的插件侧)', () => { expect(declared.slice().sort()).toEqual([...ONE].sort()); }); it('订阅名是 session/event 而非 turn/end(后者不是 ctx 派发点)', () => { expect(declared).toContain('session/event'); expect(declared).not.toContain('turn/end'); }); it('bridges 声明覆盖沉淀链三源(parseDiff / runRules / generateThinkEntry)', () => { const keys = (entry?.bridges ?? []).map((b) => `${b.pkg}.${b.api}`).sort(); expect(keys).toEqual( [ '@sofagent/audit.runRules', '@sofagent/core.getDataDir', '@sofagent/core.parseDiff', '@sofagent/think.generateThinkEntry', ].sort(), ); }); it('apply 经 ctx.on 订阅 session/event;复合 disposer 逐个反注册', async () => { const spy = errSpy(); const { ctx, subscribed, disposed } = hostCtx(); const disposer = (pluginDefault.apply as (c: unknown) => unknown)(ctx) as | (() => Promise<void>) | undefined; expect([...subscribed.keys()].sort()).toEqual([...ONE].sort()); expect(typeof disposer).toBe('function'); await disposer!(); expect(disposed.slice().sort()).toEqual([...ONE].sort()); spy.mockRestore(); }); it('turn/end + 有变更:parseDiff → runRules → generateThinkEntry 逐参透传', async () => { const spy = errSpy(); process.env.SOFAGENT_TASK = 'T-SEAM'; const { ctx, subscribed } = hostCtx(); (pluginDefault.apply as (c: unknown) => unknown)(ctx); await subscribed.get('session/event')!(sessionOf('/proj'), { type: 'turn/end', turn: 3, reason: 'stop', }); expect(parseDiff).toHaveBeenCalledWith('HEAD', '/proj'); // session.header.cwd 优先 expect(runRules).toHaveBeenCalledWith({ diffFiles: ['src/a.ts'], silent: true }); expect(generateThinkEntry).toHaveBeenCalledWith( ['src/a.ts'], [{ rule: 'no-archaeology', pass: true }], 'T-SEAM', { dataDir: '/data/dir' }, ); spy.mockRestore(); }); it('其余会话事件:零副作用(不进沉淀链)', async () => { const spy = errSpy(); const { ctx, subscribed } = hostCtx(); (pluginDefault.apply as (c: unknown) => unknown)(ctx); const handler = subscribed.get('session/event')!; await handler(sessionOf('/proj'), { type: 'turn/start', turn: 1 }); await handler(sessionOf('/proj'), { type: 'message/added' }); await handler(sessionOf('/proj'), undefined); expect(parseDiff).not.toHaveBeenCalled(); expect(runRules).not.toHaveBeenCalled(); expect(generateThinkEntry).not.toHaveBeenCalled(); spy.mockRestore(); }); it('turn/end 但工作区无变更:不跑规则、不写沉淀(零副作用)', async () => { const spy = errSpy(); vi.mocked(parseDiff).mockImplementation(() => []); const { ctx, subscribed } = hostCtx(); (pluginDefault.apply as (c: unknown) => unknown)(ctx); await subscribed.get('session/event')!(sessionOf('/proj'), { type: 'turn/end', turn: 2 }); expect(parseDiff).toHaveBeenCalledTimes(1); expect(runRules).not.toHaveBeenCalled(); expect(generateThinkEntry).not.toHaveBeenCalled(); spy.mockRestore(); }); it('diff 取数失败:不抛、不进入后续链路(fail-open,不影响 Turn 收尾)', async () => { const spy = errSpy(); vi.mocked(parseDiff).mockImplementation(() => { throw new Error('git 不可用'); }); const { ctx, subscribed } = hostCtx(); (pluginDefault.apply as (c: unknown) => unknown)(ctx); await expect( subscribed.get('session/event')!(sessionOf('/proj'), { type: 'turn/end', turn: 1 }), ).resolves.toBeUndefined(); expect(runRules).not.toHaveBeenCalled(); expect(generateThinkEntry).not.toHaveBeenCalled(); spy.mockRestore(); }); it('数据目录解析失败:沉淀照跑(dataDir 交引擎兜底),不中断链', async () => { const spy = errSpy(); vi.mocked(getDataDir).mockImplementation(() => { throw new Error('数据目录不可达'); }); const { ctx, subscribed } = hostCtx(); (pluginDefault.apply as (c: unknown) => unknown)(ctx); await subscribed.get('session/event')!(sessionOf('/proj'), { type: 'turn/end', turn: 1 }); expect(generateThinkEntry).toHaveBeenCalledWith(['src/a.ts'], expect.anything(), undefined, { dataDir: undefined, }); spy.mockRestore(); }); }); -
index.ts 4.7 KB
// cordis-plugin-sofagent-evolve · DSH 反向插件(v1.5.2:98 行样板收敛到 @sofagent/dsh-plugin-kit) // seam 挂载:session/event # 语义:会话事件流中的 Turn 结束(turn/end)→ 经验沉淀(think.md 反思) // 清单生成源 = engine/dsh-plugins/plugins.json(生成 package.json 的 description/sofagent/dsh 段与 cordis.patch.yml);本文件的 seam 字面量由生成器 --check 与之对账。 import { createSofagentPlugin, seamHelpers, type SeamHandler, type SeamHelpers, } from '@sofagent/dsh-plugin-kit'; /** 一次性日志表(接线自证 / 降级提示只打一次) */ const logged = new Set<string>(); function logOnce(helpers: SeamHelpers, key: string, message: string): void { if (logged.has(key)) return; logged.add(key); helpers.log(message); } const errMsg = (err: unknown): string => (err instanceof Error ? err.message : String(err)); /** 项目根:会话 header 的 cwd 优先,缺省进程工作目录 */ function cwdOf(session: unknown): string { const cwd = (session as { header?: { cwd?: unknown } } | null | undefined)?.header?.cwd; if (typeof cwd === 'string' && cwd !== '') return cwd; return process.env.SOFAGENT_PROJECT_ROOT ?? process.cwd(); } /** 数据目录:交给 @sofagent/core 自己的解析(与 MCP/CLI 同源) */ async function resolveDataDir(helpers: SeamHelpers): Promise<string | undefined> { try { const dir = await helpers.call('@sofagent/core', 'getDataDir'); return typeof dir === 'string' && dir !== '' ? dir : undefined; } catch (err) { helpers.log(`数据目录解析不可用(交引擎默认值):${errMsg(err)}`); return undefined; } } /** * seam 事件处理器(v1.5.0 章十)——`turn/end` 的**实现**(此前只有声明)。 * * 🔴 订阅点是 `session/event`,不是 `turn/end`:宿主把 `turn/end` 作为**会话事件 * 类型**发布(`session.append('turn/end', { turn, reason })` → 广播到 * `session/event`,listener 收到 `(session, event)`),仓内/宿主均无 ctx 级 * `turn/end` 派发点——`ctx.on('turn/end')` 会订阅到一个永不触发的事件名。 * 故 seam 名与订阅名都写真实可订阅的 `session/event`,收尾语义由过滤条件承载。 * * 判定逻辑零改动:diff 取数 / 规则运行 / 沉淀写入全部是既有 @public API * (`parseDiff` → `runRules` → `generateThinkEntry`),插件只做串联。 * 空 diff 即返回(`generateThinkEntry` 自身也判空)——工作区干净时零副作用。 */ const seamHandlers: Record<string, SeamHandler> = { 'session/event': async (...args: unknown[]) => { const helpers = seamHelpers(args); const [session, event] = args; const e = event as { type?: unknown } | null | undefined; if (e?.type !== 'turn/end') return; // 收尾过滤:其余会话事件不参与沉淀 try { const root = cwdOf(session); const diffFiles = await helpers.call('@sofagent/core', 'parseDiff', 'HEAD', root); if (!Array.isArray(diffFiles) || diffFiles.length === 0) return; // 无变更不沉淀 const results = await helpers.call('@sofagent/audit', 'runRules', { diffFiles, silent: true, }); await helpers.call( '@sofagent/think', 'generateThinkEntry', diffFiles, results, process.env.SOFAGENT_TASK, { dataDir: await resolveDataDir(helpers) }, ); logOnce(helpers, 'turn-end-sedimented', 'Turn 收尾经验沉淀已执行(think.md 反思条目)'); } catch (err) { logOnce(helpers, 'turn-end-unavailable', `经验沉淀不可用(本次跳过,不影响 Turn 收尾):${errMsg(err)}`); } }, }; /** 插件声明(本文件唯一手写处;适配层红线由 kit 承担:ctx 鸭子类型 + 宿主 API 缺席降级不抛) */ const kit = createSofagentPlugin( { id: 'cordis-plugin-sofagent-evolve', seam: 'session/event', seamSemantics: '会话事件流中的 Turn 结束(turn/end)→ 经验沉淀(think.md 反思)', capability: '进化能力(经验自动沉淀)', bridgePkg: '@sofagent/think', bridgeApi: 'generateThinkEntry', description: '经验沉淀——think.md 反思 + Dream Cycle + evolve + instinct→skill + refine', seamHandlers, }, require('../package.json') as { version?: string }, ); export const pluginMeta = kit.pluginMeta; // 插件元数据(DSH profile/注册表消费) export const capability = kit.capability; // 依赖的 sofagent 能力说明(DSH skill 引导链展示) export const invoke = kit.invoke; // 桥接 @sofagent/* 公共 API(懒加载 + 降级不抛) export default kit.plugin; // DSH Cordis 插件契约(apply 三段式由 kit 提供)
-
-
cordis.patch.yml 592 B
# sofagent cordis-plugin-sofagent-evolve bundle patch v1.5.2——注册为 DSH profile layer - insert: - id: sofagent-evolve name: 'cordis-plugin-sofagent-evolve' inject: [settings, dynamicCordisRunner] config: seam: "session/event" # 语义:会话事件流中的 Turn 结束(turn/end)→ 经验沉淀(think.md 反思) description: "给宿主加上经验沉淀——任务收尾自动写 think.md 反思条目——桥接 @sofagent/think generateThinkEntry / @sofagent/core parseDiff / @sofagent/audit runRules / @sofagent/core getDataDir" -
package.json 1.7 KB
{ "name": "cordis-plugin-sofagent-evolve", "version": "1.5.2", "engines": { "node": ">=18" }, "description": "给宿主加上经验沉淀——任务收尾自动写 think.md 反思条目(seam: session/event)——桥接 @sofagent/think generateThinkEntry / @sofagent/core parseDiff / @sofagent/audit runRules / @sofagent/core getDataDir", "license": "MIT", "author": "KongFangXun", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { "build": "tsc", "test": "vitest run src/index.test.ts" }, "keywords": [ "sofagent", "dsh", "cordis-plugin" ], "sofagent": { "type": "dsh-plugin", "family": "cordis", "seam": "session/event", "seamSemantics": "会话事件流中的 Turn 结束(turn/end)→ 经验沉淀(think.md 反思)", "seamHandlers": [ "session/event" ] }, "//optionalDependencies": "v1.4.5 T6 (R4):src/index.ts 惰性 await import(懒加载 + 缺依赖降级不抛;v1.4.8 起该样板由 @sofagent/dsh-plugin-kit 统一封装;v1.4.9 P2 起厚插件多包 bridges 按序解析、部分可用即部分成功),此前未声明任何依赖——对齐 root package.json F-18 optionalDependencies 先例。", "optionalDependencies": { "@sofagent/think": "1.5.2", "@sofagent/core": "1.5.2", "@sofagent/audit": "1.5.2" }, "devDependencies": { "typescript": "^7.0.2", "vitest": "^5.0.0" }, "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }, "files": [ "dist/", "cordis.patch.yml", "SKILL.md", "!dist/**/*.js.map", "!dist/**/*.d.ts.map", "!dist/**/*.test.*" ], "dependencies": { "@sofagent/dsh-plugin-kit": "1.5.2" } } -
SKILL.md 1.3 KB
--- name: cordis-plugin-sofagent-evolve slug: cordis-plugin-sofagent-evolve version: 1.5.2 displayName: cordis-plugin-sofagent-evolve description: > 经验沉淀——think.md 反思 + Dream Cycle + evolve + instinct→skill + refine(seam: session/event)——桥接 @sofagent/think generateThinkEntry——DSH(DeepSeek Harness)cordis plugin。sofagent 约束层在 DeepSeek Harness 生态的插件形态。 --- # cordis-plugin-sofagent-evolve 经验沉淀——think.md 反思 + Dream Cycle + evolve + instinct→skill + refine(seam: session/event)——桥接 @sofagent/think generateThinkEntry ## 用途 **装上之后**:任务收尾自动写一条 think.md 反思条目。**什么时候用**:同一个坑反复踩、想让经验留下来被下次检索。 **接入点**(seam: session/event):桥接 `@sofagent/think`,缺依赖时该能力静默跳过;接入形态(声明 / 实现)见 [SEAMS.md](../SEAMS.md)。 本插件随 sofagent 主线版本发布(SkillHub 通道:`skillhub install cordis-plugin-sofagent-evolve` 安装与检索;npm 通道未开通)。版本号与 sofagent 主线对齐。 ## 相关链接 - sofagent 主仓:https://github.com/KongFangXun/sofagent - 开发日志:docs/changelog/v1.4/v1.4.0.md(DSH 插件家族) -
tsconfig.json 185 B
{ "extends": "../tsconfig.base.json", "compilerOptions": { "rootDir": "src", "outDir": "dist" }, "include": [ "src" ], "exclude": [ "src/**/*.test.ts" ] }
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.