feat: coding-tdd workflow — auto-generated by meta-workflow
This commit is contained in:
@@ -0,0 +1,238 @@
|
|||||||
|
/**
|
||||||
|
* Meta Workflow e2e — TDD 重构 coding workflow
|
||||||
|
*
|
||||||
|
* 让 meta workflow (architect→coder→reviewer→tester→promoter) 把 coding workflow
|
||||||
|
* 从现有流程改造为 TDD 驱动流程。
|
||||||
|
*
|
||||||
|
* 小橘 🍊 (NEKO Team)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createOpenAiLlmClient } from '../llm-client.js';
|
||||||
|
import { createStore } from '../store.js';
|
||||||
|
import { createMetaArchitectRole } from '../workflows/roles/meta-architect-llm.js';
|
||||||
|
import { createMetaCoderRole } from '../workflows/roles/meta-coder-cursor.js';
|
||||||
|
import { createMetaReviewerRole } from '../workflows/roles/meta-reviewer-cursor.js';
|
||||||
|
import { createMetaTesterRole } from '../workflows/roles/meta-tester.js';
|
||||||
|
import { createMetaPromoterRole } from '../workflows/roles/meta-promoter.js';
|
||||||
|
import { createCursorRunner } from '../workflows/roles/agent-executor.js';
|
||||||
|
import { createWorkflowRule } from '../workflows/workflow-rule-adapter.js';
|
||||||
|
import { createMetaWorkflow } from '../workflows/meta.js';
|
||||||
|
import { readFileSync, mkdtempSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
|
||||||
|
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({
|
||||||
|
architect: createMetaArchitectRole(llm),
|
||||||
|
coder: createMetaCoderRole(cursorRunner, llm, REPO_DIR),
|
||||||
|
reviewer: createMetaReviewerRole(cursorRunner, llm, REPO_DIR),
|
||||||
|
tester: createMetaTesterRole(llm, { repoDir: REPO_DIR }),
|
||||||
|
promoter: createMetaPromoterRole({
|
||||||
|
repoDir: REPO_DIR,
|
||||||
|
remote: 'gitflare',
|
||||||
|
branch: 'main',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const rule = createWorkflowRule(wf, store, undefined, { cooldownMs: 0 });
|
||||||
|
|
||||||
|
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: 小橘 <xiaoju@shazhou.work>
|
||||||
|
- 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 = store.putObject(taskDescription);
|
||||||
|
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 = store.getAfter(0);
|
||||||
|
const lastEvent = events[events.length - 1];
|
||||||
|
if (lastEvent?.kind === 'meta.promoter') {
|
||||||
|
console.log('\n🎉 Workflow completed — promoter finished!');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
store.close();
|
||||||
|
console.log('\n🏁 Done');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error);
|
||||||
Reference in New Issue
Block a user