refactor: remove closer role from coding and coding-tdd workflows
CI / test (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
What: Remove the closer role from coding and coding-tdd workflows.
Why: The closer role only produced a summary after reviewer approved,
adding no value — reviewer approval is sufficient to end the workflow.
Changes:
- Delete CloserMeta/TddCloserMeta types and defaultCloser implementations
- Moderator now routes directly to END after reviewer approves
- Update all tests, .js and .d.ts artifacts accordingly
- Remove closer from index exports
团子 🐰
This commit is contained in:
+2
-2
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* 小橘 🍊 (NEKO Team)
|
||||
*/
|
||||
export { type ArchitectMeta, type CloserMeta, type CoderMeta, type CodingRoles, createCodingWorkflow, type ReviewerMeta, } from './workflows/coding.js';
|
||||
export { type AutoTesterMeta, type CreateTddCodingWorkflowOpts, createTddCodingWorkflow, type ManualTesterMeta, type TddCloserMeta, type TddCoderMeta, type TddCodingRoles, type TddReviewerMeta, type TestCoderMeta, type TestPlannerMeta, type TestReviewerMeta, } from './workflows/coding-tdd.js';
|
||||
export { type ArchitectMeta, type CoderMeta, type CodingRoles, createCodingWorkflow, type ReviewerMeta, } from './workflows/coding.js';
|
||||
export { type AutoTesterMeta, type CreateTddCodingWorkflowOpts, createTddCodingWorkflow, type ManualTesterMeta, type TddCoderMeta, type TddCodingRoles, type TddReviewerMeta, type TestCoderMeta, type TestPlannerMeta, type TestReviewerMeta, } from './workflows/coding-tdd.js';
|
||||
export { type AnalystMeta, createReportWorkflow, type RendererMeta, type ReportRoles, } from './workflows/report.js';
|
||||
export { checkCursorHealth, type CursorHealthOptions, type CursorHealthResult, } from './workflows/cursor-health.js';
|
||||
export { createWerewolfWorkflow, type CreateWerewolfWorkflowOpts, type DaySpeechMeta, filterChainForPlayer, type GameEndMeta, type GameState, type HunterShotMeta, type Identity, parseGameState, type Player, type SeerCheckMeta, type VoteMeta, type WerewolfRoles, type WitchActionMeta, type WolfNightMeta, createPlayers, } from './workflows/werewolf.js';
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
export {
|
||||
type ArchitectMeta,
|
||||
type CloserMeta,
|
||||
|
||||
type CoderMeta,
|
||||
type CodingRoles,
|
||||
createCodingWorkflow,
|
||||
@@ -20,7 +20,7 @@ export {
|
||||
type CreateTddCodingWorkflowOpts,
|
||||
createTddCodingWorkflow,
|
||||
type ManualTesterMeta,
|
||||
type TddCloserMeta,
|
||||
|
||||
type TddCoderMeta,
|
||||
type TddCodingRoles,
|
||||
type TddReviewerMeta,
|
||||
|
||||
+1
-4
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* TDD-driven coding workflow — test-planner → … → closer.
|
||||
* TDD-driven coding workflow — test-planner → … → reviewer → END.
|
||||
*
|
||||
* Pure roles + START/END automaton. Trigger: coding-tdd.__start__
|
||||
*
|
||||
@@ -40,7 +40,6 @@ export type TddReviewerMeta = {
|
||||
codeQuality: string;
|
||||
testQuality: string;
|
||||
};
|
||||
export type TddCloserMeta = Record<string, never>;
|
||||
export type TddCodingRoles = {
|
||||
'test-planner': Role<TestPlannerMeta>;
|
||||
'test-reviewer': Role<TestReviewerMeta>;
|
||||
@@ -49,7 +48,6 @@ export type TddCodingRoles = {
|
||||
'auto-tester': Role<AutoTesterMeta>;
|
||||
'manual-tester': Role<ManualTesterMeta>;
|
||||
reviewer: Role<TddReviewerMeta>;
|
||||
closer: Role<TddCloserMeta>;
|
||||
};
|
||||
export type CreateTddCodingWorkflowOpts = {
|
||||
testPlannerFn?: Role<TestPlannerMeta>;
|
||||
@@ -59,6 +57,5 @@ export type CreateTddCodingWorkflowOpts = {
|
||||
autoTesterFn?: Role<AutoTesterMeta>;
|
||||
manualTesterFn?: Role<ManualTesterMeta>;
|
||||
reviewerFn?: Role<TddReviewerMeta>;
|
||||
closerFn?: Role<TddCloserMeta>;
|
||||
};
|
||||
export declare function createTddCodingWorkflow(opts?: CreateTddCodingWorkflowOpts): WorkflowType<TddCodingRoles>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* TDD-driven coding workflow — test-planner → … → closer.
|
||||
* TDD-driven coding workflow — test-planner → … → reviewer → END.
|
||||
*
|
||||
* Pure roles + START/END automaton. Trigger: coding-tdd.__start__
|
||||
*
|
||||
@@ -67,14 +67,7 @@ const defaultTddReviewer = async () => ({
|
||||
testQuality: 'good',
|
||||
},
|
||||
});
|
||||
const defaultCloser = async (chain) => {
|
||||
const startMsg = chain.find((m) => m.role === '__start__');
|
||||
const title = startMsg?.meta?.title ?? 'task';
|
||||
return {
|
||||
content: `[mock] TDD workflow report: ${title}`,
|
||||
meta: {},
|
||||
};
|
||||
};
|
||||
|
||||
function tddCodingModerator(output, _topicId, remainingRounds) {
|
||||
const emergency = remainingRounds !== undefined && remainingRounds <= 1;
|
||||
if (output.role === START)
|
||||
@@ -88,7 +81,7 @@ function tddCodingModerator(output, _topicId, remainingRounds) {
|
||||
return 'test-coder';
|
||||
if (verdict === 'rejected') {
|
||||
if (emergency)
|
||||
return 'closer';
|
||||
return END;
|
||||
return 'test-planner';
|
||||
}
|
||||
return 'test-planner';
|
||||
@@ -101,21 +94,21 @@ function tddCodingModerator(output, _topicId, remainingRounds) {
|
||||
if (output.meta?.pass)
|
||||
return 'manual-tester';
|
||||
if (emergency)
|
||||
return 'closer';
|
||||
return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'manual-tester': {
|
||||
if (output.meta?.pass)
|
||||
return 'reviewer';
|
||||
if (emergency)
|
||||
return 'closer';
|
||||
return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'reviewer': {
|
||||
if (emergency)
|
||||
return 'closer';
|
||||
return END;
|
||||
if (output.meta?.verdict === 'approved')
|
||||
return 'closer';
|
||||
return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'closer':
|
||||
@@ -136,8 +129,7 @@ export function createTddCodingWorkflow(opts) {
|
||||
'auto-tester': opts?.autoTesterFn ?? defaultAutoTester,
|
||||
'manual-tester': opts?.manualTesterFn ?? defaultManualTester,
|
||||
reviewer: opts?.reviewerFn ?? defaultTddReviewer,
|
||||
closer: opts?.closerFn ?? defaultCloser,
|
||||
},
|
||||
},
|
||||
moderator: tddCodingModerator,
|
||||
limits: { maxRounds: 25 },
|
||||
};
|
||||
|
||||
@@ -80,7 +80,6 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'auto-tester',
|
||||
'manual-tester',
|
||||
'reviewer',
|
||||
'closer',
|
||||
]);
|
||||
|
||||
const r = await rule.tick();
|
||||
@@ -135,7 +134,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
).toBe('test-planner');
|
||||
});
|
||||
|
||||
it('moderator: test-reviewer rejected + emergency → closer', () => {
|
||||
it('moderator: test-reviewer rejected + emergency → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -146,7 +145,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'x',
|
||||
1,
|
||||
),
|
||||
).toBe('closer');
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: test-coder → coder', () => {
|
||||
@@ -205,7 +204,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
).toBe('coder');
|
||||
});
|
||||
|
||||
it('moderator: auto-tester fail + emergency → closer', () => {
|
||||
it('moderator: auto-tester fail + emergency → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -216,7 +215,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'x',
|
||||
1,
|
||||
),
|
||||
).toBe('closer');
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: manual-tester pass → reviewer', () => {
|
||||
@@ -243,7 +242,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
).toBe('coder');
|
||||
});
|
||||
|
||||
it('moderator: manual-tester fail + emergency → closer', () => {
|
||||
it('moderator: manual-tester fail + emergency → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -254,10 +253,10 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'x',
|
||||
1,
|
||||
),
|
||||
).toBe('closer');
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: reviewer approved → closer', () => {
|
||||
it('moderator: reviewer approved → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -272,10 +271,10 @@ describe('coding-tdd WorkflowType', () => {
|
||||
},
|
||||
'x',
|
||||
),
|
||||
).toBe('closer');
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: reviewer approved + emergency → closer', () => {
|
||||
it('moderator: reviewer approved + emergency → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -291,7 +290,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'x',
|
||||
1,
|
||||
),
|
||||
).toBe('closer');
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: reviewer rejected → coder', () => {
|
||||
@@ -313,7 +312,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
).toBe('coder');
|
||||
});
|
||||
|
||||
it('moderator: reviewer rejected + emergency → closer', () => {
|
||||
it('moderator: reviewer rejected + emergency → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(
|
||||
wf.moderator(
|
||||
@@ -329,12 +328,7 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'x',
|
||||
1,
|
||||
),
|
||||
).toBe('closer');
|
||||
});
|
||||
|
||||
it('moderator: closer → END', () => {
|
||||
const wf = createTddCodingWorkflow();
|
||||
expect(wf.moderator({ role: 'closer', meta: {} }, 'x')).toBe(END);
|
||||
).toBe(END);
|
||||
});
|
||||
|
||||
it('loop: test-reviewer rejects once then approves', async () => {
|
||||
@@ -370,7 +364,6 @@ describe('coding-tdd WorkflowType', () => {
|
||||
'test-reviewer',
|
||||
]);
|
||||
expect(roles).toContain('test-coder');
|
||||
expect(roles).toContain('closer');
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
@@ -415,7 +408,6 @@ describe('coding-tdd WorkflowType', () => {
|
||||
const secondCoder = roles.indexOf('coder', firstCoder + 1);
|
||||
expect(firstAuto).toBeGreaterThan(firstCoder);
|
||||
expect(secondCoder).toBeGreaterThan(firstAuto);
|
||||
expect(roles).toContain('closer');
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
@@ -452,7 +444,6 @@ describe('coding-tdd WorkflowType', () => {
|
||||
expect(roles.lastIndexOf('coder')).toBeGreaterThan(
|
||||
roles.indexOf('manual-tester'),
|
||||
);
|
||||
expect(roles).toContain('closer');
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
@@ -495,7 +486,6 @@ describe('coding-tdd WorkflowType', () => {
|
||||
}
|
||||
|
||||
expect(roles.filter((r) => r === 'reviewer').length).toBe(2);
|
||||
expect(roles).toContain('closer');
|
||||
} finally {
|
||||
await cleanup();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* TDD-driven coding workflow — test-planner → … → closer.
|
||||
* TDD-driven coding workflow — test-planner → … → reviewer → END.
|
||||
*
|
||||
* Pure roles + START/END automaton. Trigger: coding-tdd.__start__
|
||||
*
|
||||
@@ -57,8 +57,6 @@ export type TddReviewerMeta = {
|
||||
testQuality: string;
|
||||
};
|
||||
|
||||
export type TddCloserMeta = Record<string, never>;
|
||||
|
||||
// ── Roles record ───────────────────────────────────────────────
|
||||
|
||||
export type TddCodingRoles = {
|
||||
@@ -69,7 +67,7 @@ export type TddCodingRoles = {
|
||||
'auto-tester': Role<AutoTesterMeta>;
|
||||
'manual-tester': Role<ManualTesterMeta>;
|
||||
reviewer: Role<TddReviewerMeta>;
|
||||
closer: Role<TddCloserMeta>;
|
||||
|
||||
};
|
||||
|
||||
// ── Default mock implementations ────────────────────────────────
|
||||
@@ -138,15 +136,6 @@ const defaultTddReviewer: Role<TddReviewerMeta> = async () => ({
|
||||
},
|
||||
});
|
||||
|
||||
const defaultCloser: Role<TddCloserMeta> = async (chain) => {
|
||||
const startMsg = chain.find((m) => m.role === '__start__');
|
||||
const title = (startMsg?.meta as { title?: string } | null)?.title ?? 'task';
|
||||
return {
|
||||
content: `[mock] TDD workflow report: ${title}`,
|
||||
meta: {},
|
||||
};
|
||||
};
|
||||
|
||||
// ── Moderator ──────────────────────────────────────────────────
|
||||
|
||||
type TddCodingInput = ModeratorInput<TddCodingRoles>;
|
||||
@@ -167,7 +156,7 @@ function tddCodingModerator(
|
||||
const verdict = output.meta?.verdict;
|
||||
if (verdict === 'approved') return 'test-coder';
|
||||
if (verdict === 'rejected') {
|
||||
if (emergency) return 'closer';
|
||||
if (emergency) return END;
|
||||
return 'test-planner';
|
||||
}
|
||||
return 'test-planner';
|
||||
@@ -178,21 +167,19 @@ function tddCodingModerator(
|
||||
return 'auto-tester';
|
||||
case 'auto-tester': {
|
||||
if (output.meta?.pass) return 'manual-tester';
|
||||
if (emergency) return 'closer';
|
||||
if (emergency) return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'manual-tester': {
|
||||
if (output.meta?.pass) return 'reviewer';
|
||||
if (emergency) return 'closer';
|
||||
if (emergency) return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'reviewer': {
|
||||
if (emergency) return 'closer';
|
||||
if (output.meta?.verdict === 'approved') return 'closer';
|
||||
if (output.meta?.verdict === 'approved') return END;
|
||||
if (emergency) return END;
|
||||
return 'coder';
|
||||
}
|
||||
case 'closer':
|
||||
return END;
|
||||
default:
|
||||
return END;
|
||||
}
|
||||
@@ -206,7 +193,7 @@ export type CreateTddCodingWorkflowOpts = {
|
||||
autoTesterFn?: Role<AutoTesterMeta>;
|
||||
manualTesterFn?: Role<ManualTesterMeta>;
|
||||
reviewerFn?: Role<TddReviewerMeta>;
|
||||
closerFn?: Role<TddCloserMeta>;
|
||||
|
||||
};
|
||||
|
||||
// ── Factory ────────────────────────────────────────────────────
|
||||
@@ -224,7 +211,6 @@ export function createTddCodingWorkflow(
|
||||
'auto-tester': opts?.autoTesterFn ?? defaultAutoTester,
|
||||
'manual-tester': opts?.manualTesterFn ?? defaultManualTester,
|
||||
reviewer: opts?.reviewerFn ?? defaultTddReviewer,
|
||||
closer: opts?.closerFn ?? defaultCloser,
|
||||
},
|
||||
moderator: tddCodingModerator,
|
||||
limits: { maxRounds: 25 },
|
||||
|
||||
+1
-3
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CodingTask WorkflowType — pure roles + START/END automaton.
|
||||
*
|
||||
* Roles: architect → coder → reviewer → closer
|
||||
* Roles: architect → coder → reviewer
|
||||
* Trigger: coding.__start__ (external)
|
||||
* Each role returns { content, meta } — adapter writes events.
|
||||
*
|
||||
@@ -22,12 +22,10 @@ export type ReviewerMeta = {
|
||||
rejectionReason: string[];
|
||||
retryCount: number;
|
||||
};
|
||||
export type CloserMeta = null;
|
||||
export type CodingRoles = {
|
||||
architect: Role<ArchitectMeta>;
|
||||
coder: Role<CoderMeta>;
|
||||
reviewer: Role<ReviewerMeta>;
|
||||
closer: Role<CloserMeta>;
|
||||
};
|
||||
export declare function createCodingWorkflow(opts?: {
|
||||
architectFn?: Role<ArchitectMeta>;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CodingTask WorkflowType — pure roles + START/END automaton.
|
||||
*
|
||||
* Roles: architect → coder → reviewer → closer
|
||||
* Roles: architect → coder → reviewer
|
||||
* Trigger: coding.__start__ (external)
|
||||
* Each role returns { content, meta } — adapter writes events.
|
||||
*
|
||||
@@ -43,14 +43,7 @@ const defaultReviewer = async (chain) => {
|
||||
meta: { verdict: 'approved', rejectionReason: [], retryCount },
|
||||
};
|
||||
};
|
||||
const defaultCloser = async (chain) => {
|
||||
const startMsg = chain.find((m) => m.role === '__start__');
|
||||
const title = startMsg?.meta?.title ?? 'unknown';
|
||||
return {
|
||||
content: `Completed: ${title}`,
|
||||
meta: null,
|
||||
};
|
||||
};
|
||||
|
||||
function codingModerator(output, _topicId, remainingRounds) {
|
||||
if (output.role === START)
|
||||
return 'architect';
|
||||
@@ -61,13 +54,12 @@ function codingModerator(output, _topicId, remainingRounds) {
|
||||
return 'reviewer';
|
||||
case 'reviewer': {
|
||||
if (remainingRounds !== undefined && remainingRounds <= 1)
|
||||
return 'closer';
|
||||
return END;
|
||||
const rejected = output.meta?.verdict === 'rejected';
|
||||
const retryCount = output.meta?.retryCount ?? 0;
|
||||
return rejected && retryCount < 3 ? 'coder' : 'closer';
|
||||
return rejected && retryCount < 3 ? 'coder' : END;
|
||||
}
|
||||
case 'closer':
|
||||
return END;
|
||||
|
||||
}
|
||||
return END;
|
||||
}
|
||||
@@ -79,8 +71,7 @@ export function createCodingWorkflow(opts) {
|
||||
architect: opts?.architectFn ?? defaultArchitect,
|
||||
coder: opts?.coderFn ?? defaultCoder,
|
||||
reviewer: opts?.reviewerFn ?? defaultReviewer,
|
||||
closer: defaultCloser,
|
||||
},
|
||||
},
|
||||
moderator: codingModerator,
|
||||
limits: { maxRounds: 15 },
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { createStore, type PulseStore } from '@uncaged/pulse';
|
||||
import { createCodingWorkflow } from './coding.js';
|
||||
import { createWorkflowRule } from '@uncaged/pulse';
|
||||
import { createWorkflowRule, END } from '@uncaged/pulse';
|
||||
|
||||
describe('CodingTask WorkflowType', () => {
|
||||
let store: PulseStore;
|
||||
@@ -47,7 +47,7 @@ describe('CodingTask WorkflowType', () => {
|
||||
});
|
||||
}
|
||||
|
||||
it('full lifecycle: START → architect → coder → reviewer → closer → END', async () => {
|
||||
it('full lifecycle: START → architect → coder → reviewer → END', async () => {
|
||||
setup();
|
||||
const codingTask = createCodingWorkflow();
|
||||
const rule = createWorkflowRule(codingTask, store, undefined, {
|
||||
@@ -78,10 +78,7 @@ describe('CodingTask WorkflowType', () => {
|
||||
]);
|
||||
|
||||
const r4 = await rule.tick();
|
||||
expect(r4.executed).toMatchObject([{ topicId: 'task-1', role: 'closer' }]);
|
||||
|
||||
const r5 = await rule.tick();
|
||||
expect(r5.executed).toEqual([]);
|
||||
expect(r4.executed).toEqual([]);
|
||||
});
|
||||
|
||||
it('rejection triggers re-coding with rejectionReason', async () => {
|
||||
@@ -129,13 +126,10 @@ describe('CodingTask WorkflowType', () => {
|
||||
|
||||
await rule.tick(); // reviewer (approves, retryCount=1)
|
||||
const r6 = await rule.tick();
|
||||
expect(r6.executed).toMatchObject([{ topicId: 'task-2', role: 'closer' }]);
|
||||
|
||||
const r7 = await rule.tick();
|
||||
expect(r7.executed).toEqual([]);
|
||||
expect(r6.executed).toEqual([]);
|
||||
});
|
||||
|
||||
it('retryCount >= 3 forces transition to closer even if rejected', async () => {
|
||||
it('retryCount >= 3 forces END even if rejected', async () => {
|
||||
setup();
|
||||
|
||||
let reviewRound = 0;
|
||||
@@ -175,15 +169,10 @@ describe('CodingTask WorkflowType', () => {
|
||||
await rule.tick(); // reviewer rejects, retryCount=2
|
||||
|
||||
await rule.tick(); // coder (round 4 — last retry)
|
||||
await rule.tick(); // reviewer rejects, retryCount=3 → forces closer
|
||||
await rule.tick(); // reviewer rejects, retryCount=3 → forces END
|
||||
|
||||
const r = await rule.tick();
|
||||
expect(r.executed).toMatchObject([
|
||||
{ topicId: 'task-retry', role: 'closer' },
|
||||
]);
|
||||
|
||||
const rEnd = await rule.tick();
|
||||
expect(rEnd.executed).toEqual([]);
|
||||
expect(r.executed).toEqual([]);
|
||||
});
|
||||
|
||||
it('adapter writes events, not roles (CAS content is string)', async () => {
|
||||
@@ -250,7 +239,7 @@ describe('CodingTask WorkflowType', () => {
|
||||
expect(wf.limits?.maxRounds).toBe(15);
|
||||
});
|
||||
|
||||
it('moderator: remainingRounds <= 1 forces closer even if reviewer rejected', () => {
|
||||
it('moderator: remainingRounds <= 1 forces END even if reviewer rejected', () => {
|
||||
const wf = createCodingWorkflow();
|
||||
const result = wf.moderator(
|
||||
{
|
||||
@@ -260,7 +249,7 @@ describe('CodingTask WorkflowType', () => {
|
||||
'topic-1',
|
||||
1, // remainingRounds = 1, 紧急收敛
|
||||
);
|
||||
expect(result).toBe('closer');
|
||||
expect(result).toBe(END);
|
||||
});
|
||||
|
||||
it('moderator: remainingRounds > 1 keeps normal retry logic', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* CodingTask WorkflowType — pure roles + START/END automaton.
|
||||
*
|
||||
* Roles: architect → coder → reviewer → closer
|
||||
* Roles: architect → coder → reviewer
|
||||
* Trigger: coding.__start__ (external)
|
||||
* Each role returns { content, meta } — adapter writes events.
|
||||
*
|
||||
@@ -29,15 +29,13 @@ export type ReviewerMeta = {
|
||||
rejectionReason: string[];
|
||||
retryCount: number;
|
||||
};
|
||||
export type CloserMeta = null;
|
||||
|
||||
// ── Roles record ───────────────────────────────────────────────
|
||||
|
||||
export type CodingRoles = {
|
||||
architect: Role<ArchitectMeta>;
|
||||
coder: Role<CoderMeta>;
|
||||
reviewer: Role<ReviewerMeta>;
|
||||
closer: Role<CloserMeta>;
|
||||
|
||||
};
|
||||
|
||||
// ── Default mock implementations ───────────────────────────────
|
||||
@@ -79,15 +77,6 @@ const defaultReviewer: Role<ReviewerMeta> = async (chain) => {
|
||||
};
|
||||
};
|
||||
|
||||
const defaultCloser: Role<CloserMeta> = async (chain) => {
|
||||
const startMsg = chain.find((m) => m.role === '__start__');
|
||||
const title = startMsg?.meta?.title ?? 'unknown';
|
||||
return {
|
||||
content: `Completed: ${title}`,
|
||||
meta: null,
|
||||
};
|
||||
};
|
||||
|
||||
// ── Moderator (type-safe automaton) ────────────────────────────
|
||||
|
||||
type CodingInput = ModeratorInput<CodingRoles>;
|
||||
@@ -105,13 +94,12 @@ function codingModerator(
|
||||
return 'reviewer';
|
||||
case 'reviewer': {
|
||||
if (remainingRounds !== undefined && remainingRounds <= 1)
|
||||
return 'closer';
|
||||
return END;
|
||||
const rejected = output.meta?.verdict === 'rejected';
|
||||
const retryCount = output.meta?.retryCount ?? 0;
|
||||
return rejected && retryCount < 3 ? 'coder' : 'closer';
|
||||
return rejected && retryCount < 3 ? 'coder' : END;
|
||||
}
|
||||
case 'closer':
|
||||
return END;
|
||||
|
||||
}
|
||||
return END;
|
||||
}
|
||||
@@ -129,7 +117,7 @@ export function createCodingWorkflow(opts?: {
|
||||
architect: opts?.architectFn ?? defaultArchitect,
|
||||
coder: opts?.coderFn ?? defaultCoder,
|
||||
reviewer: opts?.reviewerFn ?? defaultReviewer,
|
||||
closer: defaultCloser,
|
||||
|
||||
},
|
||||
moderator: codingModerator,
|
||||
limits: { maxRounds: 15 },
|
||||
|
||||
@@ -44,7 +44,6 @@ describe('Report Workflow', () => {
|
||||
durationMs: 4000,
|
||||
meta: { verdict: 'approved' },
|
||||
},
|
||||
{ id: 5, role: 'closer', offsetMs: 10000, durationMs: 1000 },
|
||||
],
|
||||
});
|
||||
const hash = await store.putObject(timeline);
|
||||
|
||||
Reference in New Issue
Block a user