Files
devclaw-gitea/lib/model-selector.ts
Lauren ten Hoor 57c78f3656 feat: Implement Architect role & design_task tool (#189)
Adds the Architect role for design/architecture investigations with
persistent sessions and structured design proposals.

## New Features

- **Architect role** with opus (complex) and sonnet (standard) levels
- **design_task tool** — Creates To Design issues and dispatches architect
- **Workflow states:** To Design → Designing → Planning
- **Completion rules:** architect:done → Planning, architect:blocked → Refining
- **Auto-level selection** based on complexity keywords

## Files Changed (22 files, 546 additions)

### New Files
- lib/tools/design-task.ts — design_task tool implementation
- lib/tools/design-task.test.ts — 16 tests for architect functionality

### Core Changes
- lib/tiers.ts — ARCHITECT_LEVELS, WorkerRole type, models, emoji
- lib/workflow.ts — toDesign/designing states, completion rules
- lib/projects.ts — architect WorkerState on Project type
- lib/dispatch.ts — architect role support in dispatch pipeline
- lib/services/pipeline.ts — architect completion rules
- lib/model-selector.ts — architect level selection heuristic

### Integration
- index.ts — Register design_task tool, architect config schema
- lib/notify.ts — architect role in notifications
- lib/bootstrap-hook.ts — architect session key parsing
- lib/services/tick.ts — architect in queue processing
- lib/services/heartbeat.ts — architect in health checks
- lib/tools/health.ts — architect in health scans
- lib/tools/status.ts — architect in status dashboard
- lib/tools/work-start.ts — architect role option
- lib/tools/work-finish.ts — architect validation
- lib/tools/project-register.ts — architect labels + role scaffolding
- lib/templates.ts — architect instructions + AGENTS.md updates
- lib/setup/workspace.ts — architect role file scaffolding
- lib/setup/smart-model-selector.ts — architect in model assignment
- lib/setup/llm-model-selector.ts — architect in LLM prompt
2026-02-14 17:08:17 +08:00

101 lines
2.6 KiB
TypeScript

/**
* Model selection for dev/qa tasks.
* Keyword heuristic fallback — used when the orchestrator doesn't specify a level.
* Returns plain level names (junior, medior, senior, reviewer, tester).
*/
export type LevelSelection = {
level: string;
reason: string;
};
// Keywords that indicate simple tasks
const SIMPLE_KEYWORDS = [
"typo",
"fix typo",
"rename",
"update text",
"change color",
"minor",
"small",
"css",
"style",
"copy",
"wording",
];
// Keywords that indicate complex tasks
const COMPLEX_KEYWORDS = [
"architect",
"refactor",
"redesign",
"system-wide",
"migration",
"database schema",
"security",
"performance",
"infrastructure",
"multi-service",
];
/**
* Select appropriate developer level based on task description.
*
* Developer levels:
* - junior: very simple (typos, single-file fixes, CSS tweaks)
* - medior: standard DEV (features, bug fixes, multi-file changes)
* - senior: deep/architectural (system-wide refactoring, novel design)
* - reviewer: QA code inspection and validation
* - tester: QA manual testing
*/
export function selectLevel(
issueTitle: string,
issueDescription: string,
role: "dev" | "qa" | "architect",
): LevelSelection {
if (role === "qa") {
return {
level: "reviewer",
reason: "Default QA level for code inspection and validation",
};
}
if (role === "architect") {
const text = `${issueTitle} ${issueDescription}`.toLowerCase();
const isComplex = COMPLEX_KEYWORDS.some((kw) => text.includes(kw));
return {
level: isComplex ? "opus" : "sonnet",
reason: isComplex
? "Complex design task — using opus for depth"
: "Standard design task — using sonnet",
};
}
const text = `${issueTitle} ${issueDescription}`.toLowerCase();
const wordCount = text.split(/\s+/).length;
// Check for simple task indicators
const isSimple = SIMPLE_KEYWORDS.some((kw) => text.includes(kw));
if (isSimple && wordCount < 100) {
return {
level: "junior",
reason: `Simple task detected (keywords: ${SIMPLE_KEYWORDS.filter((kw) => text.includes(kw)).join(", ")})`,
};
}
// Check for complex task indicators
const isComplex = COMPLEX_KEYWORDS.some((kw) => text.includes(kw));
if (isComplex || wordCount > 500) {
return {
level: "senior",
reason: `Complex task detected (${isComplex ? "keywords: " + COMPLEX_KEYWORDS.filter((kw) => text.includes(kw)).join(", ") : "long description"})`,
};
}
// Default: medior for standard dev work
return {
level: "medior",
reason: "Standard dev task — multi-file changes, features, bug fixes",
};
}