feat: enhance review process and role management

- Refactor reviewPass function to identify states with review checks instead of specific review types.
- Introduce review policies (HUMAN, AGENT, AUTO) to control PR review processes based on developer levels.
- Update projectTick to handle review policies and step routing labels for reviewers and testers.
- Add detailed reviewer instructions to templates for clarity on review responsibilities.
- Implement role:level label management, allowing dynamic creation of labels based on project configuration.
- Enhance task_update tool to support state and level updates, ensuring at least one parameter is provided.
- Update work_finish tool to include reviewer actions (approve, reject) in task completion.
- Modify work_start tool to utilize role-level detection for better level assignment.
- Add tests for new functionalities, including review routing and level detection from labels.
This commit is contained in:
Lauren ten Hoor
2026-02-16 18:09:53 +08:00
parent 1464fa82d2
commit d87b9f68a2
25 changed files with 1134 additions and 294 deletions

View File

@@ -1,64 +1,133 @@
/**
* Integration test for task_update tool.
* Tests for task_update tool — state transitions and level overrides.
*
* Run manually: node --loader ts-node/esm lib/tools/task-update.test.ts
* Run: npx tsx --test lib/tools/task-update.test.ts
*/
import { describe, it } from "node:test";
import assert from "node:assert";
import { DEFAULT_WORKFLOW, getStateLabels, ReviewPolicy, resolveReviewRouting } from "../workflow.js";
import { detectLevelFromLabels, detectRoleLevelFromLabels, detectStepRouting } from "../services/queue-scan.js";
describe("task_update tool", () => {
it("has correct schema", () => {
// Verify the tool signature matches requirements
const requiredParams = ["projectGroupId", "issueId", "state"];
const optionalParams = ["reason"];
// Schema validation would go here in a real test
assert.ok(true, "Schema structure is valid");
// state is now optional — at least one of state or level required
const requiredParams = ["projectGroupId", "issueId"];
assert.strictEqual(requiredParams.length, 2);
});
it("supports all state labels", () => {
const validStates = [
"Planning",
"To Do",
"Doing",
"To Test",
"Testing",
"Done",
"To Improve",
"Refining",
"In Review",
];
// In a real test, we'd verify these against the tool's enum
assert.strictEqual(validStates.length, 9);
const labels = getStateLabels(DEFAULT_WORKFLOW);
assert.strictEqual(labels.length, 12);
assert.ok(labels.includes("Planning"));
assert.ok(labels.includes("Done"));
assert.ok(labels.includes("To Review"));
});
it("validates required parameters", () => {
// Test cases:
// - Missing projectGroupId → Error
// - Missing issueId → Error
// - Missing state → Error
// - Invalid state → Error
// - Valid params → Success
// At least one of state or level required
assert.ok(true, "Parameter validation works");
});
it("handles same-state transitions gracefully", () => {
// When current state === new state, should return success without changes
assert.ok(true, "No-op transitions handled correctly");
});
it("logs to audit trail", () => {
// Verify auditLog is called with correct parameters
assert.ok(true, "Audit logging works");
});
});
// Test scenarios for manual verification:
// 1. task_update({ projectGroupId: "-5239235162", issueId: 28, state: "Planning" })
// → Should transition from "To Do" to "Planning"
// 2. task_update({ projectGroupId: "-5239235162", issueId: 28, state: "Planning", reason: "Needs more discussion" })
// → Should log reason in audit trail
// 3. task_update({ projectGroupId: "-5239235162", issueId: 28, state: "To Do" })
// → Should transition back from "Planning" to "To Do"
describe("detectLevelFromLabels — colon format", () => {
it("should detect level from colon-format labels", () => {
assert.strictEqual(detectLevelFromLabels(["developer:senior", "Doing"]), "senior");
assert.strictEqual(detectLevelFromLabels(["tester:junior", "Testing"]), "junior");
assert.strictEqual(detectLevelFromLabels(["reviewer:medior", "Reviewing"]), "medior");
});
it("should prioritize colon format over dot format", () => {
// Colon format should win since it's checked first
assert.strictEqual(detectLevelFromLabels(["developer:senior", "dev.junior"]), "senior");
});
it("should fall back to dot format", () => {
assert.strictEqual(detectLevelFromLabels(["developer.senior", "Doing"]), "senior");
});
it("should fall back to plain level name", () => {
assert.strictEqual(detectLevelFromLabels(["senior", "Doing"]), "senior");
});
it("should return null when no level found", () => {
assert.strictEqual(detectLevelFromLabels(["Doing", "bug"]), null);
});
});
describe("detectRoleLevelFromLabels", () => {
it("should detect role and level from colon-format labels", () => {
const result = detectRoleLevelFromLabels(["developer:senior", "Doing"]);
assert.deepStrictEqual(result, { role: "developer", level: "senior" });
});
it("should detect tester role", () => {
const result = detectRoleLevelFromLabels(["tester:medior", "Testing"]);
assert.deepStrictEqual(result, { role: "tester", level: "medior" });
});
it("should return null for step routing labels", () => {
// review:human is a step routing label, not a role:level label
const result = detectRoleLevelFromLabels(["review:human", "Doing"]);
assert.strictEqual(result, null);
});
it("should return null when no colon labels present", () => {
assert.strictEqual(detectRoleLevelFromLabels(["Doing", "bug"]), null);
});
});
describe("detectStepRouting", () => {
it("should detect review:human", () => {
assert.strictEqual(detectStepRouting(["review:human", "Doing"], "review"), "human");
});
it("should detect review:agent", () => {
assert.strictEqual(detectStepRouting(["review:agent", "To Review"], "review"), "agent");
});
it("should detect review:skip", () => {
assert.strictEqual(detectStepRouting(["review:skip", "To Review"], "review"), "skip");
});
it("should detect test:skip", () => {
assert.strictEqual(detectStepRouting(["test:skip", "To Test"], "test"), "skip");
});
it("should return null when no matching step label", () => {
assert.strictEqual(detectStepRouting(["developer:senior", "Doing"], "review"), null);
});
it("should be case-insensitive", () => {
assert.strictEqual(detectStepRouting(["Review:Human", "Doing"], "review"), "human");
});
});
describe("resolveReviewRouting", () => {
it("should return review:human for HUMAN policy", () => {
assert.strictEqual(resolveReviewRouting(ReviewPolicy.HUMAN, "junior"), "review:human");
assert.strictEqual(resolveReviewRouting(ReviewPolicy.HUMAN, "senior"), "review:human");
});
it("should return review:agent for AGENT policy", () => {
assert.strictEqual(resolveReviewRouting(ReviewPolicy.AGENT, "junior"), "review:agent");
assert.strictEqual(resolveReviewRouting(ReviewPolicy.AGENT, "senior"), "review:agent");
});
it("should return review:human for AUTO + senior", () => {
assert.strictEqual(resolveReviewRouting(ReviewPolicy.AUTO, "senior"), "review:human");
});
it("should return review:agent for AUTO + non-senior", () => {
assert.strictEqual(resolveReviewRouting(ReviewPolicy.AUTO, "junior"), "review:agent");
assert.strictEqual(resolveReviewRouting(ReviewPolicy.AUTO, "medior"), "review:agent");
});
});