diff --git a/packages/pulse/src/bin/workflow-daemon.ts b/packages/pulse/src/bin/workflow-daemon.ts index 6a9698f..ef0411e 100644 --- a/packages/pulse/src/bin/workflow-daemon.ts +++ b/packages/pulse/src/bin/workflow-daemon.ts @@ -19,7 +19,6 @@ import { createWorkflowTicker } from '../workflows/index.js'; import { createMetaWorkflow } from '../workflows/meta.js'; import { createCursorRunner } from '../workflows/roles/agent-executor.js'; import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js'; -import { createMetaPromoterRole } from '../workflows/roles/meta-promoter.js'; import { createMetaTesterRole } from '../workflows/roles/meta-tester.js'; import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js'; @@ -79,11 +78,6 @@ const codingRule = createWorkflowRule(codingWf, store, logStore); const metaWf = createMetaWorkflow({ coder: createMetaCoderRole(cursorRunner, llm, ENGINE_DIR), tester: createMetaTesterRole({ repoDir: ENGINE_DIR }), - promoter: createMetaPromoterRole({ - repoDir: ENGINE_DIR, - remote: 'origin', - branch: 'main', - }), }); const metaRule = createWorkflowRule(metaWf, store, logStore); diff --git a/packages/pulse/src/e2e/meta-coding-optimize.ts b/packages/pulse/src/e2e/meta-coding-optimize.ts deleted file mode 100644 index 39f0173..0000000 --- a/packages/pulse/src/e2e/meta-coding-optimize.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * Meta Workflow e2e — let meta-architect analyze, then meta-coder (Cursor) implement. - * Optimizes the existing coding workflow based on architect's spec. - * - * 小橘 🍊 (NEKO Team) - */ - -import { mkdtempSync, readFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { createOpenAiLlmClient } from '../llm-client.js'; -import { createStore } from '../store.js'; -import { createMetaWorkflow } from '../workflows/meta.js'; -import { createCursorRunner } from '../workflows/roles/agent-executor.js'; -import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js'; -import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js'; - -const REPO_DIR = join(import.meta.dir, '../../../..'); - -async function main() { - const dir = mkdtempSync(join(tmpdir(), 'meta-e2e-')); - console.log(`📁 Temp dir: ${dir}`); - - const apiKey = process.env.DASHSCOPE_API_KEY; - if (!apiKey) throw new Error('DASHSCOPE_API_KEY required'); - - const cursorKey = process.env.CURSOR_API_KEY; - if (!cursorKey) throw new Error('CURSOR_API_KEY required'); - - const llm = createOpenAiLlmClient({ - apiKey, - baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', - model: 'qwen-plus', - timeoutMs: 120_000, - }); - - const store = createStore({ - eventsDbPath: join(dir, 'events.db'), - objectsDir: join(dir, 'objects'), - }); - - const cursorRunner = createCursorRunner({ - agentBin: `${process.env.HOME}/.local/bin/agent`, - }); - const coder = createMetaCoderRole(cursorRunner, llm, REPO_DIR); - - // Stub roles we won't run yet - const stubRole = async () => ({ - content: 'stub - not reached', - meta: {} as any, - }); - - const wf = createMetaWorkflow({ - coder, - tester: stubRole as any, - promoter: stubRole as any, - }); - - const rule = createWorkflowRule(wf, store); - - // Read current source for context - const codingSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/coding.ts'), - 'utf-8', - ); - const architectSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/roles/architect-llm.ts'), - 'utf-8', - ); - const coderSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/roles/coder-cursor.ts'), - 'utf-8', - ); - const reviewerSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/roles/reviewer-cursor.ts'), - 'utf-8', - ); - - const taskDescription = `# 优化 coding workflow - -## 问题 -1. architect 只输出笼统 analysis + 猜测 targetFiles,coder 几乎不看 -2. reviewer 不参考 architect 的设计意图 -3. reviewer rejected 后回到 coder,但 coder 看不到 rejection 原因 -4. 没有 retry 上限 - -## 现有源码 - -### coding.ts (moderator + types) -\`\`\`typescript -${codingSrc} -\`\`\` - -### architect-llm.ts -\`\`\`typescript -${architectSrc} -\`\`\` - -### coder-cursor.ts -\`\`\`typescript -${coderSrc} -\`\`\` - -### reviewer-cursor.ts -\`\`\`typescript -${reviewerSrc} -\`\`\` - -## 要求 -1. ArchitectMeta 扩充:targetFiles + changes (per-file) + verification (验收标准) -2. architect prompt 输出结构化 JSON -3. coder 消费 architect 的 changes + verification,rejection 时展示 review 反馈 -4. reviewer 对照 architect 的 changes + verification 审查 -5. ReviewerMeta 加 rejectionReason + retryCount -6. moderator 加 retry 上限 3 次 -7. 更新 mock 默认实现匹配新 meta 类型 -8. 所有引用 ArchitectMeta/ReviewerMeta 的文件都要更新(如 e2e 文件) - -## 约束 -- 不改 workflow-rule-adapter.ts 和 workflow-type.ts -- 28 测试必须全过 -- commit author: 小橘 -`; - - const hash = await store.putObject(taskDescription); - await store.appendEvent({ - occurredAt: Date.now(), - kind: 'meta.__start__', - key: 'optimize-coding', - hash, - }); - - // Step 1: Architect - console.log('🏗️ Step 1: meta-architect (LLM) analyzing...\n'); - const r1 = await rule.tick(); - if (r1.executed.length > 0) { - console.log('✅ Architect done'); - console.log('Spec:', r1.executed[0].content.slice(0, 500), '...\n'); - } else { - console.log('❌ Architect failed'); - await store.close(); - return; - } - - // Step 2: Coder (Cursor Agent) - console.log('💻 Step 2: meta-coder (Cursor Agent) implementing...\n'); - const r2 = await rule.tick(); - if (r2.executed.length > 0) { - console.log('✅ Coder done'); - console.log('Output:', r2.executed[0].content.slice(0, 500)); - console.log('Meta:', JSON.stringify(r2.executed[0].meta, null, 2)); - } else { - console.log('❌ Coder not triggered'); - } - - await store.close(); - console.log('\n🏁 Done'); -} - -main().catch(console.error); diff --git a/packages/pulse/src/e2e/meta-full-e2e.ts b/packages/pulse/src/e2e/meta-full-e2e.ts deleted file mode 100644 index b4116a3..0000000 --- a/packages/pulse/src/e2e/meta-full-e2e.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * Meta Workflow full e2e — simplified coder → tester → promoter flow. - * - * Flow: coder(Agent) → tester(code) → promoter(git) - * On failure: revert coding changes, fix meta, rerun. - * - * 小橘 🍊 (NEKO Team) - */ - -import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { createOpenAiLlmClient } from '../llm-client.js'; -import { createStore } from '../store.js'; -import { createMetaWorkflow } from '../workflows/meta.js'; -import { createCursorRunner } from '../workflows/roles/agent-executor.js'; -import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js'; -import { createMetaPromoterRole } from '../workflows/roles/meta-promoter.js'; -import { createMetaTesterRole } from '../workflows/roles/meta-tester.js'; -import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js'; - -const REPO_DIR = join(import.meta.dir, '../../../..'); - -function readSrc(relPath: string): string { - return readFileSync(join(REPO_DIR, relPath), 'utf-8'); -} - -async function main() { - const dir = mkdtempSync(join(tmpdir(), 'meta-full-')); - console.log(`📁 Temp dir: ${dir}`); - console.log(`📂 Repo: ${REPO_DIR}\n`); - - const apiKey = process.env.DASHSCOPE_API_KEY; - if (!apiKey) throw new Error('DASHSCOPE_API_KEY required'); - - const llm = createOpenAiLlmClient({ - apiKey, - baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', - model: 'qwen-plus', - timeoutMs: 120_000, - }); - - const store = createStore({ - eventsDbPath: join(dir, 'events.db'), - objectsDir: join(dir, 'objects'), - }); - - const cursorRunner = createCursorRunner({ - agentBin: `${process.env.HOME}/.local/bin/agent`, - }); - - const wf = createMetaWorkflow({ - coder: createMetaCoderRole(cursorRunner, llm, REPO_DIR), - tester: createMetaTesterRole({ repoDir: REPO_DIR }), - promoter: createMetaPromoterRole({ - repoDir: REPO_DIR, - remote: 'gitflare', - branch: 'main', - }), - }); - - const rule = createWorkflowRule(wf, store); - - // Build task description with all relevant source - const taskDescription = `# 优化 coding workflow - -## 问题 -1. architect 只输出笼统 analysis + 猜测 targetFiles,coder 几乎不看 -2. reviewer 不参考 architect 的设计意图 -3. reviewer rejected 后回到 coder,但 coder 看不到 rejection 原因 -4. 没有 retry 上限 - -## 现有源码 - -### coding.ts (moderator + types + mock roles) -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/coding.ts')} -\`\`\` - -### architect-llm.ts -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/roles/architect-llm.ts')} -\`\`\` - -### coder-cursor.ts -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/roles/coder-cursor.ts')} -\`\`\` - -### reviewer-cursor.ts -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/roles/reviewer-cursor.ts')} -\`\`\` - -## 要求 -1. ArchitectMeta 扩充:targetFiles + changes(per-file改动列表) + verification(验收标准) -2. architect prompt 输出结构化 JSON,用 tool_choice required -3. coder 消费 architect 的 changes + verification;rejection 时展示上次 review 反馈 -4. reviewer 对照 architect 的 changes + verification 审查 -5. ReviewerMeta 加 rejectionReason(string[]) + retryCount(number) -6. moderator 加 retry 上限 3 次(retryCount >= 3 强制进 closer) -7. 更新所有 mock 默认实现匹配新 meta 类型 -8. 所有引用 ArchitectMeta/ReviewerMeta 的文件都要更新 - -## 约束 -- 不改 workflow-rule-adapter.ts 和 workflow-type.ts -- 28 个 workflow 测试必须全过(toMatchObject 断言) -- commit author: 小橘 -`; - - const hash = await store.putObject(taskDescription); - await store.appendEvent({ - occurredAt: Date.now(), - kind: 'meta.__start__', - key: 'optimize-coding', - hash, - }); - - // Run the full pipeline — adapter handles role chaining - const MAX_TICKS = 10; // safety valve - for (let i = 0; i < MAX_TICKS; i++) { - console.log(`\n── tick ${i + 1} ──────────────────────────`); - const result = await rule.tick(); - - if (result.executed.length === 0) { - console.log('⏹️ No more roles to execute. Workflow complete or stuck.'); - break; - } - - for (const ex of result.executed) { - console.log(`✅ ${ex.role} done`); - console.log(` content: ${ex.content.slice(0, 200)}...`); - console.log(` meta: ${JSON.stringify(ex.meta)}`); - } - - // Check if we hit END - const events = await store.getAfter(0); - const lastEvent = events[events.length - 1]; - if (lastEvent?.kind === 'meta.promoter') { - console.log('\n🎉 Workflow completed — promoter finished!'); - break; - } - } - - await store.close(); - console.log('\n🏁 Done'); -} - -main().catch(console.error); diff --git a/packages/pulse/src/e2e/meta-optimize-coding.ts b/packages/pulse/src/e2e/meta-optimize-coding.ts deleted file mode 100644 index 67a8a5f..0000000 --- a/packages/pulse/src/e2e/meta-optimize-coding.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Meta Workflow live test — optimize coding workflow. - * - * Uses meta-architect (LLM) to analyze the problem and design improvements. - * Skips coder/reviewer/tester/promoter (just architect for now). - * - * 小橘 🍊 (NEKO Team) - */ - -import { mkdtempSync, readFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { createOpenAiLlmClient } from '../llm-client.js'; -import { createStore } from '../store.js'; -import type { - MetaCoderMeta, - MetaPromoterMeta, - MetaTesterMeta, -} from '../workflows/meta.js'; -import { createMetaWorkflow } from '../workflows/meta.js'; -import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js'; - -const REPO_DIR = join(import.meta.dir, '../../../..'); - -async function main() { - const dir = mkdtempSync(join(tmpdir(), 'meta-live-')); - console.log(`📁 Temp dir: ${dir}`); - - const store = createStore({ - eventsDbPath: join(dir, 'events.db'), - objectsDir: join(dir, 'objects'), - }); - - const llm = createOpenAiLlmClient({ - apiKey: process.env.DASHSCOPE_API_KEY!, - baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', - model: 'qwen-plus', - timeoutMs: 120_000, - }); - - const stubRole = async () => ({ - content: 'stub', - meta: {} as any, - }); - - const wf = createMetaWorkflow({ - coder: stubRole as any, - tester: stubRole as any, - promoter: stubRole as any, - }); - - const rule = createWorkflowRule(wf, store); - - // Read current coding workflow source for context - const codingSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/coding.ts'), - 'utf-8', - ); - const architectSrc = readFileSync( - join(REPO_DIR, 'packages/pulse/src/workflows/roles/architect-llm.ts'), - 'utf-8', - ); - - const taskDescription = `# 优化 coding workflow 的 architect role - -## 问题 -当前 architect role 几乎没用: -1. 只输出一个笼统的 analysis 文本 + 猜测的 targetFiles -2. coder 几乎不看 architect 的输出——直接读 task description -3. reviewer 也不参考 architect 的设计意图 -4. 没有 retry 机制:reviewer rejected 后回到 coder,但 coder 看不到 rejection 原因 - -## 现有源码 - -### coding.ts (moderator + types) -\`\`\`typescript -${codingSrc} -\`\`\` - -### architect-llm.ts (architect role) -\`\`\`typescript -${architectSrc} -\`\`\` - -## 期望 -1. architect 输出结构化的改动方案(哪些文件、改什么、验证标准) -2. coder 消费 architect 的方案 -3. reviewer 对照 architect 的方案审查 -4. reviewer rejected 时 coder 能看到具体 rejection 原因 -5. 可选:加 retry 上限(防止无限循环) -`; - - const hash = await store.putObject(taskDescription); - await store.appendEvent({ - occurredAt: Date.now(), - kind: 'meta.__start__', - key: 'optimize-coding', - hash, - }); - - console.log('🚀 Running meta-architect...\n'); - - const result = await rule.tick(); - - if (result.executed.length > 0) { - const arch = result.executed[0]; - console.log( - `\n✅ Architect executed: role=${arch.role}, topic=${arch.topicId}`, - ); - console.log('\n📋 Workflow Spec:'); - console.log(arch.content); - console.log('\n📊 Meta:', JSON.stringify(arch.meta, null, 2)); - } else { - console.log('❌ No role executed'); - } - - await store.close(); -} - -main().catch(console.error); diff --git a/packages/pulse/src/e2e/meta-tdd-coding.ts b/packages/pulse/src/e2e/meta-tdd-coding.ts deleted file mode 100644 index d0df7d9..0000000 --- a/packages/pulse/src/e2e/meta-tdd-coding.ts +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Meta Workflow e2e — TDD 重构 coding workflow - * - * 让 meta workflow (architect→coder→reviewer→tester→promoter) 把 coding workflow - * 从现有流程改造为 TDD 驱动流程。 - * - * 小橘 🍊 (NEKO Team) - */ - -import { mkdtempSync, readFileSync } from 'node:fs'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { createOpenAiLlmClient } from '../llm-client.js'; -import { createStore } from '../store.js'; -import { createMetaWorkflow } from '../workflows/meta.js'; -import { createCursorRunner } from '../workflows/roles/agent-executor.js'; -import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js'; -import { createMetaPromoterRole } from '../workflows/roles/meta-promoter.js'; -import { createMetaTesterRole } from '../workflows/roles/meta-tester.js'; -import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js'; - -const REPO_DIR = join(import.meta.dir, '../../../..'); - -function readSrc(relPath: string): string { - return readFileSync(join(REPO_DIR, relPath), 'utf-8'); -} - -async function main() { - const dir = mkdtempSync(join(tmpdir(), 'meta-tdd-')); - console.log(`📁 Temp dir: ${dir}`); - console.log(`📂 Repo: ${REPO_DIR}\n`); - - const apiKey = process.env.DASHSCOPE_API_KEY; - if (!apiKey) throw new Error('DASHSCOPE_API_KEY required'); - - const llm = createOpenAiLlmClient({ - apiKey, - baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1', - model: 'qwen-plus', - timeoutMs: 120_000, - }); - - const store = createStore({ - eventsDbPath: join(dir, 'events.db'), - objectsDir: join(dir, 'objects'), - }); - - const cursorRunner = createCursorRunner({ - agentBin: `${process.env.HOME}/.local/bin/agent`, - }); - - const wf = createMetaWorkflow({ - coder: createMetaCoderRole(cursorRunner, llm, REPO_DIR), - tester: createMetaTesterRole({ repoDir: REPO_DIR }), - promoter: createMetaPromoterRole({ - repoDir: REPO_DIR, - remote: 'gitflare', - branch: 'main', - }), - }); - - const rule = createWorkflowRule(wf, store, undefined); - - const taskDescription = `# 重构 coding workflow 为 TDD 驱动流程 - -## 目标 -将现有 coding workflow (architect→coder→reviewer→closer) 重构为完整的 TDD 驱动开发流程。 - -## 新流程设计 -\`\`\` -START - → test-planner (LLM) # 从需求出发写测试方案文档 - → test-reviewer (LLM) # review 测试方案是否覆盖需求 - → rejected → test-planner(带反馈修改) - → approved ↓ - → test-coder (Cursor) # 按测试方案写自动化测试用例(红灯) - → coder (Cursor) # 写功能代码 + 部署/使用说明(绿灯) - → auto-tester (代码) # bun test 跑自动化测试 - → fail → coder(迭代代码) - → pass ↓ - → manual-tester (Agent) # Agent 读测试文档照着操作验证 - → fail → coder(迭代代码) - → pass ↓ - → reviewer (Cursor) # review 功能代码 + 测试用例 - → rejected → coder(迭代) - → approved ↓ - → closer → END -\`\`\` - -## 核心设计原则 -1. **测试文档是一等公民** — test-planner 产出的测试文档贯穿全流程 -2. **测试先行** — test-coder 先写红灯测试,coder 再写代码让它绿 -3. **三处消费测试文档** — test-coder(写自动化测试)、manual-tester(手工验证)、reviewer(审查测试质量) -4. **coder 产出含部署说明** — manual-tester 需要知道怎么操作 -5. **Agent 实操验证** — manual-tester 不是 LLM 读代码,是真正的 Agent 按文档操作 - -## 新 Role 定义 - -### test-planner (LLM, tool_choice: required) -- 输入:需求描述(从 __start__ content) -- 输出 meta:{ testPlan: string(markdown 测试方案), scenarios: string[](测试场景列表)} -- 产出完整测试方案文档,包含:功能测试点、边界条件、异常场景、验收标准 - -### test-reviewer (LLM) -- 输入:test-planner 的测试方案 + 原始需求 -- 输出 meta:{ verdict: 'approved'|'rejected', feedback: string } -- 检查测试方案是否充分覆盖需求 - -### test-coder (Cursor Agent) -- 输入:approved 的测试方案 -- 输出 meta:{ testFiles: string[], testCount: number } -- 按测试方案写自动化测试用例(此时功能代码未实现,测试应全部失败 = 红灯) - -### coder (Cursor Agent) -- 输入:测试方案 + 红灯测试代码 + rejection feedback(如有) -- 输出 meta:{ filesChanged: string[], deploymentGuide: string } -- 写功能代码让测试变绿 + 写部署/使用说明供 manual-tester 使用 - -### auto-tester (代码) -- 输入:coder 完成后 -- 输出 meta:{ pass: boolean, failedTests: string[], output: string } -- 执行 \`bun test\`,解析结果 - -### manual-tester (Agent/LLM) -- 输入:test-planner 的测试文档 + coder 的部署说明 -- 输出 meta:{ pass: boolean, issues: string[] } -- 按测试文档逐项验证(当前阶段用 LLM 静态分析,未来接 OC Agent 实操) - -### reviewer (Cursor Agent) -- 输入:功能代码 + 测试代码 + 测试方案 -- 输出 meta:{ verdict: 'approved'|'rejected', comments: string, codeQuality: string, testQuality: string } -- 同时 review 功能代码质量和测试用例质量 - -### closer (代码) -- 输入:reviewer approved -- 输出 meta:null -- 生成完成报告 - -## Moderator 状态机 -\`\`\`typescript -START → 'test-planner' -'test-planner' → 'test-reviewer' -'test-reviewer' → verdict === 'approved' ? 'test-coder' : 'test-planner' -'test-coder' → 'coder' -'coder' → 'auto-tester' -'auto-tester' → pass ? 'manual-tester' : 'coder' -'manual-tester' → pass ? 'reviewer' : 'coder' -'reviewer' → verdict === 'approved' ? 'closer' : 'coder' -'closer' → END - -// 紧急收敛:remainingRounds <= 1 时跳 closer -\`\`\` - -## limits -\`\`\`typescript -limits: { maxRounds: 25 } -\`\`\` - -## 现有源码(参考) - -### coding.ts (当前 workflow 定义) -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/coding.ts')} -\`\`\` - -### coding.test.ts (当前测试) -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/coding.test.ts')} -\`\`\` - -### workflow-type.ts (接口定义,不要修改) -\`\`\`typescript -${readSrc('packages/pulse/src/workflows/workflow-type.ts')} -\`\`\` - -## 实现约束 -- 不修改 workflow-rule-adapter.ts 和 workflow-type.ts -- 新文件:coding-tdd.ts + coding-tdd.test.ts(不覆盖原 coding.ts) -- Role 先用 mock 实现(后续再接真实 LLM/Cursor) -- 测试用 coding.test.ts 为模板,覆盖所有状态转换 -- Build: \`cd packages/pulse && bun run build\` -- Test: \`bun test packages/pulse/src/workflows/\` -- Commit author: 小橘 -- Commit msg: \`feat: TDD coding workflow (coding-tdd) with test-first pipeline\` -- Push: \`git push gitflare main --no-verify\` - -## 验收标准 -1. coding-tdd.ts 导出 createTddCodingWorkflow() -2. 8 个 role 全部有 mock 实现 -3. moderator 状态机正确(含 test-reviewer rejection 回环、auto-tester/manual-tester fail 回环、reviewer rejection 回环) -4. remainingRounds <= 1 紧急收敛 -5. limits.maxRounds = 25 -6. coding-tdd.test.ts 覆盖所有转换路径 -7. 所有现有测试不 break -8. bun run build 通过 -`; - - const hash = await store.putObject(taskDescription); - await store.appendEvent({ - occurredAt: Date.now(), - kind: 'meta.__start__', - key: 'tdd-coding-workflow', - hash, - }); - - const MAX_TICKS = 12; - for (let i = 0; i < MAX_TICKS; i++) { - console.log(`\n── tick ${i + 1} ──────────────────────────`); - const result = await rule.tick(); - - if (result.executed.length === 0) { - console.log('⏹️ No more roles to execute. Workflow complete or stuck.'); - break; - } - - for (const ex of result.executed) { - console.log(`✅ ${ex.role} done`); - console.log(` content: ${ex.content.slice(0, 300)}...`); - console.log(` meta: ${JSON.stringify(ex.meta)}`); - } - - const events = await store.getAfter(0); - const lastEvent = events[events.length - 1]; - if (lastEvent?.kind === 'meta.promoter') { - console.log('\n🎉 Workflow completed — promoter finished!'); - break; - } - } - - await store.close(); - console.log('\n🏁 Done'); -} - -main().catch(console.error); diff --git a/packages/pulse/src/workflows/index.ts b/packages/pulse/src/workflows/index.ts index 88350fd..44c6bd4 100644 --- a/packages/pulse/src/workflows/index.ts +++ b/packages/pulse/src/workflows/index.ts @@ -14,7 +14,6 @@ export { } from './coding.js'; export type { MetaCoderMeta, - MetaPromoterMeta, MetaTesterMeta, } from './meta.js'; export { createMetaWorkflow } from './meta.js'; @@ -36,7 +35,6 @@ export type { } from './roles/llm-role-factory.js'; export { createLlmRole, createToolRole } from './roles/llm-role-factory.js'; export { createMetaCoderRole } from './roles/meta-coder-cursor.js'; -export { createMetaPromoterRole } from './roles/meta-promoter.js'; export { createMetaTesterRole } from './roles/meta-tester.js'; export { createRendererRole } from './roles/renderer-template.js'; export { createReviewerRole } from './roles/reviewer-cursor.js'; diff --git a/packages/pulse/src/workflows/meta.test.ts b/packages/pulse/src/workflows/meta.test.ts index 07bda9f..373d9e8 100644 --- a/packages/pulse/src/workflows/meta.test.ts +++ b/packages/pulse/src/workflows/meta.test.ts @@ -11,7 +11,6 @@ import { createStore } from '../store.js'; import { createMetaWorkflow, type MetaCoderMeta, - type MetaPromoterMeta, type MetaTesterMeta, } from './meta.js'; import { createWorkflowRule } from './workflow-rule-adapter.js'; @@ -26,7 +25,7 @@ function mockStore() { } describe('Meta Workflow', () => { - test('moderator routes: START→coder→tester(pass)→promoter→END', () => { + test('moderator routes: START→coder→tester(pass)→END', () => { const wf = createMetaWorkflow({ coder: async () => ({ content: 'ok', @@ -36,10 +35,6 @@ describe('Meta Workflow', () => { content: 'pass', meta: { pass: true, reason: '编译通过,测试全绿' }, }), - promoter: async () => ({ - content: 'done', - meta: { commitHash: 'abc123', pushed: true }, - }), }); expect(wf.moderator({ role: START, meta: null }, 'x')).toBe('coder'); @@ -54,19 +49,13 @@ describe('Meta Workflow', () => { { role: 'tester', meta: { pass: true, reason: '通过' } }, 'x', ), - ).toBe('promoter'); + ).toBe(END); expect( wf.moderator( { role: 'tester', meta: { pass: false, reason: '失败' } }, 'x', ), ).toBe('coder'); - expect( - wf.moderator( - { role: 'promoter', meta: { commitHash: 'abc', pushed: true } }, - 'x', - ), - ).toBe(END); }); test('mock: full happy path via adapter ticks', async () => { @@ -80,15 +69,10 @@ describe('Meta Workflow', () => { content: 'all pass', meta: { pass: true, reason: '编译通过,测试全绿' }, }), - promoter: async () => ({ - content: 'commit abc', - meta: { commitHash: 'abc', pushed: true }, - }), }); const rule = createWorkflowRule(wf, store); - // Seed START const hash = await store.putObject('build a demo workflow'); await store.appendEvent({ occurredAt: Date.now(), @@ -97,7 +81,6 @@ describe('Meta Workflow', () => { hash, }); - // Tick through all roles const roles: string[] = []; for (let i = 0; i < 5; i++) { const r = await rule.tick(); @@ -105,10 +88,10 @@ describe('Meta Workflow', () => { roles.push(...r.executed.map((a) => a.role)); } - expect(roles).toEqual(['coder', 'tester', 'promoter']); + expect(roles).toEqual(['coder', 'tester']); const events = await store.getAfter(0); - expect(events.length).toBe(4); // __start__ + 3 roles + expect(events.length).toBe(3); // __start__ + coder + tester await store.close(); }); @@ -133,10 +116,6 @@ describe('Meta Workflow', () => { meta: { pass: true, reason: '编译通过,测试全绿' }, }; }, - promoter: async () => ({ - content: 'done', - meta: { commitHash: 'def', pushed: true }, - }), }); const rule = createWorkflowRule(wf, store); @@ -155,8 +134,8 @@ describe('Meta Workflow', () => { roles.push(...r.executed.map((a) => a.role)); } - // coder → tester(failed) → coder → tester(pass) → promoter - expect(roles).toEqual(['coder', 'tester', 'coder', 'tester', 'promoter']); + // coder → tester(fail) → coder → tester(pass) → END + expect(roles).toEqual(['coder', 'tester', 'coder', 'tester']); await store.close(); }); }); diff --git a/packages/pulse/src/workflows/meta.ts b/packages/pulse/src/workflows/meta.ts index 0cd08aa..18b7e99 100644 --- a/packages/pulse/src/workflows/meta.ts +++ b/packages/pulse/src/workflows/meta.ts @@ -2,14 +2,13 @@ * Meta Workflow — workflow for developing workflows. * * Roles: - * coder (Agent) → implement + self-test - * tester (code) → CI-level verification, pure code - * promoter (code) → git commit + push + promote event + * coder (Agent) → implement + self-test + * tester (code) → build + test + commit/push on pass * * Flow: * START → coder → tester - * → tester.pass === true → promoter → END - * → tester.pass === false → coder (retry) + * → pass → END (with commit + push) + * → fail → coder (retry) * * 小橘 🍊 (NEKO Team) */ @@ -34,18 +33,14 @@ export interface MetaTesterMeta { [key: string]: unknown; pass: boolean; reason: string; -} - -export interface MetaPromoterMeta { - [key: string]: unknown; - commitHash: string; - pushed: boolean; + /** Only present when pass=true */ + commitHash?: string; + pushed?: boolean; } export type MetaWorkflowRoles = { coder: Role; tester: Role; - promoter: Role; }; // ── Moderator ────────────────────────────────────────────────── @@ -61,11 +56,8 @@ function metaModerator( return 'tester'; case 'tester': { const meta = input.meta as MetaTesterMeta | null; - const pass = meta?.pass; - return pass ? 'promoter' : 'coder'; + return meta?.pass ? END : 'coder'; } - case 'promoter': - return END; default: return END; } diff --git a/packages/pulse/src/workflows/roles/meta-promoter.ts b/packages/pulse/src/workflows/roles/meta-promoter.ts deleted file mode 100644 index 0ba5239..0000000 --- a/packages/pulse/src/workflows/roles/meta-promoter.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Meta Promoter role — pure code, git commit + push. - * No LLM needed. - * - * 小橘 🍊 (NEKO Team) - */ - -import { execSync } from 'node:child_process'; -import type { MetaPromoterMeta } from '../meta.js'; -import type { Role, RoleResult, WorkflowMessage } from '../workflow-type.js'; - -export function createMetaPromoterRole(opts: { - repoDir: string; - remote?: string; - branch?: string; -}): Role { - const remote = opts.remote ?? 'gitflare'; - const branch = opts.branch ?? 'main'; - - return async ( - chain: WorkflowMessage[], - ): Promise> => { - const startMsg = chain.find((m) => m.role === '__start__'); - let workflowName = 'unknown'; - // 尝试从 task 描述里提取名字(第一行或 JSON 里的 name 字段) - const firstLine = (startMsg?.content ?? '').split('\n')[0]; - workflowName = firstLine.slice(0, 50) || 'unknown'; - - const cwd = opts.repoDir; - const exec = (cmd: string) => - execSync(cmd, { cwd, encoding: 'utf-8', timeout: 30_000 }).trim(); - - // Stage + commit - exec('git add -A'); - const commitMsg = `refactor: simplify meta workflow — coder → tester → promoter`; - exec( - `git commit -m "${commitMsg}" --author="小橘 " --allow-empty`, - ); - const commitHash = exec('git rev-parse --short HEAD'); - - // Push - let pushed = false; - try { - exec(`git push ${remote} ${branch} --no-verify`); - pushed = true; - } catch {} - - return { - content: `Committed: ${commitHash}\nPushed: ${pushed ? 'yes' : 'no'}`, - meta: { commitHash, pushed }, - }; - }; -} diff --git a/packages/pulse/src/workflows/roles/meta-tester.ts b/packages/pulse/src/workflows/roles/meta-tester.ts index 3d4791a..3db291a 100644 --- a/packages/pulse/src/workflows/roles/meta-tester.ts +++ b/packages/pulse/src/workflows/roles/meta-tester.ts @@ -1,27 +1,40 @@ /** - * Meta Tester role — pure code build+test validation. - * No LLM needed, just exit codes and output parsing. + * Meta Tester role — build + test + commit/push on pass. + * No LLM needed, just exit codes and git. * * 小橘 🍊 (NEKO Team) */ import { execSync } from 'node:child_process'; -import { join } from 'node:path'; import type { MetaTesterMeta } from '../meta.js'; import type { Role, RoleResult, WorkflowMessage } from '../workflow-type.js'; export function createMetaTesterRole(opts: { repoDir: string; + /** cwd for build command (defaults to repoDir) */ + buildDir?: string; + /** cwd for test command (defaults to repoDir) */ + testDir?: string; + /** git remote to push to (auto-detect if omitted) */ + remote?: string; + /** git branch (default: main) */ + branch?: string; }): Role { + const branch = opts.branch ?? 'main'; + return async ( chain: WorkflowMessage[], ): Promise> => { + const cwd = opts.repoDir; + const buildCwd = opts.buildDir ?? cwd; + const testCwd = opts.testDir ?? cwd; + // Step 1: Build let buildOk = false; let buildOutput = ''; try { buildOutput = execSync('bun run build 2>&1', { - cwd: join(opts.repoDir, 'packages/pulse'), + cwd: buildCwd, timeout: 60_000, encoding: 'utf-8', }); @@ -35,14 +48,11 @@ export function createMetaTesterRole(opts: { let testOutput = ''; if (buildOk) { try { - testOutput = execSync( - 'bun test packages/pulse/src/workflows/ 2>&1', - { - cwd: opts.repoDir, - timeout: 60_000, - encoding: 'utf-8', - }, - ); + testOutput = execSync('bun test src/workflows/ 2>&1', { + cwd: testCwd, + timeout: 60_000, + encoding: 'utf-8', + }); testOk = true; } catch (err: any) { testOutput = err.stdout ?? err.message; @@ -57,9 +67,57 @@ export function createMetaTesterRole(opts: { ? '测试失败' : '编译失败'; + // Step 3: If pass, commit + push + let commitHash: string | undefined; + let pushed: boolean | undefined; + if (pass) { + const exec = (cmd: string) => + execSync(cmd, { cwd, encoding: 'utf-8', timeout: 30_000 }).trim(); + + try { + // Extract task name from __start__ for commit message + const startMsg = chain.find((m) => m.role === '__start__'); + const firstLine = (startMsg?.content ?? '').split('\n')[0].slice(0, 60); + const commitMsg = firstLine || 'meta workflow auto-commit'; + + exec('git add -A'); + exec( + `git commit -m "${commitMsg}" --author="小橘 " --allow-empty`, + ); + commitHash = exec('git rev-parse --short HEAD'); + + // Try push — auto-detect remote + const remote = opts.remote ?? (() => { + try { + const remotes = exec('git remote').split('\n').filter(Boolean); + return remotes[0] || null; + } catch { return null; } + })(); + + if (remote) { + try { + exec(`git push ${remote} ${branch} --no-verify`); + pushed = true; + } catch { + pushed = false; + } + } else { + pushed = false; + } + } catch (err: any) { + // Commit failed — still pass, just note it + commitHash = undefined; + pushed = false; + } + } + + const summary = pass + ? `${reason}\nCommit: ${commitHash ?? 'none'}\nPushed: ${pushed ? 'yes' : 'no'}` + : `${reason}\n\n---\n${output.slice(0, 2000)}`; + return { - content: `${reason}\n\n---\n${output.slice(0, 2000)}`, - meta: { pass, reason }, + content: summary, + meta: { pass, reason, commitHash, pushed }, }; }; }