Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions src/lib/services/h70-skill-matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,16 +556,32 @@ const TASK_TYPE_TO_SKILLS: Record<string, string[]> = {
'AI Integration': ['llm-architect', 'ai-agents-architect', 'prompt-engineer'],
};

/**
* Multi-word phrase keys precomputed at module load.
*
* `KEYWORD_TO_SKILLS` is a module-level constant whose key set never changes
* at runtime, so the phrase subset is invariant. Recomputing
* `Object.keys(KEYWORD_TO_SKILLS).filter(k => k.includes(' '))` inside
* `extractKeywords` walked all ~180 keys plus an extra phrase-filter pass
* on every call. `matchTaskToSkills` (and therefore `extractKeywords`) is
* invoked once per task by `mission-builder.ts`, `prd-auto-dispatch.ts`, and
* the batch helpers below — a 30-50 task mission paid that scan 30-50 times
* for the exact same result. Caching it here keeps the cost on the module's
* load tick and preserves a stable hot-path cost per task.
*/
const KEYWORD_PHRASES: readonly string[] = Object.freeze(
Object.keys(KEYWORD_TO_SKILLS).filter((k) => k.includes(' '))
);

/**
* Extract keywords from task name/description
*/
function extractKeywords(text: string): string[] {
const normalized = text.toLowerCase();
const keywords: string[] = [];

// Check multi-word phrases first
const phrases = Object.keys(KEYWORD_TO_SKILLS).filter(k => k.includes(' '));
for (const phrase of phrases) {
// Check multi-word phrases first (phrase list precomputed once at module load)
for (const phrase of KEYWORD_PHRASES) {
if (normalized.includes(phrase)) {
keywords.push(phrase);
}
Expand Down