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