/** * Org task matching: which role should own a task (`org_task` with * `assignee: "auto"`) and which of a role's pool skills fit it, without a * decision model. * * The keyword scorer drops stopwords, weighs each word by how rare it is * among the candidates (a word every role mentions — "the", the org's shared * rules skill, "worktree" — tells nothing apart), counts title words double * and the title's leading verb (titles are imperative) double again, and * requires a minimum score. Equal scores are broken by role specificity * (deeper in the reporting tree, then the narrower description), never by * the order roles are declared in; a tie that survives that is reported as * ambiguous instead of being guessed. Words the task rules out ("deploy it, * not the tests", "without CI") do not count: pick-rank.cjs * withoutExclusions() drops them from the title and brief first. */ import { type PickOptions } from '../decision/jev.js'; import type { OrgDef, OrgRole } from './types.js'; /** How much of a task brief the pickers read. */ export declare const PICK_BRIEF_CHARS = 600; /** The lowest keyword score that assigns a role / suggests a skill. */ export declare const MIN_ROLE_SCORE = 1.5; export declare const MIN_SKILL_SCORE = 1.5; export declare function matchTokens(text: string | undefined): string[]; export interface MatchDoc { id: string; /** Identity text (id, title / name): a hit here weighs 3. */ strong: string; /** Descriptive text: a hit here weighs 1. */ weak: string; } export interface MatchScore { id: string; score: number; matched: string[]; } export interface TaskText { title: string; brief?: string; } /** Every doc with a positive score, highest first (equal scores keep input * order — callers that must not favour order break ties themselves). */ export declare function scoreDocs(task: TaskText, docs: MatchDoc[]): MatchScore[]; /** A finished task as the prior reads it (an OrgTask row fits). */ export interface TaskOutcome { title: string; assignee: string; status: string; loadedSkills?: string[]; } /** Observations a role/skill needs before its history moves a ranking. */ export declare const PRIOR_MIN_OBS = 3; /** Largest relative move of the prior: factors lie in [1 - span, 1 + span]. */ export declare const PRIOR_SPAN = 0.15; /** Beta(1,1)-smoothed success rate mapped to [1 - PRIOR_SPAN, 1 + PRIOR_SPAN]; * 1 below PRIOR_MIN_OBS outcomes. */ export declare function outcomeFactor(success: number, failure: number): number; /** Per-role factor from how each role did on past tasks similar to `task` * (done = success, failed = failure). */ export declare function roleOutcomePrior(task: TaskText, history: TaskOutcome[]): (roleId: string) => number; /** Per-skill factor from the outcomes of the tasks that loaded it. */ export declare function skillOutcomePrior(history: TaskOutcome[]): (skill: string) => number; type PickableRole = Pick & Partial>; /** Provenance of an 'auto' assignment, recorded on the task (OrgTask.pick). */ export interface TaskPick { method: 'only' | 'jev' | 'keyword'; /** Jev's confidence in the chosen role. */ confidence?: number; /** The chosen role's keyword score. */ score?: number; /** The best candidates considered (Jev probability or keyword score). */ candidates: { id: string; score?: number; }[]; } export interface RolePick { /** null = no role fits (or the best ones tie); the caller must name one. */ role: string | null; method: 'only' | 'jev' | 'keyword' | 'none'; /** Jev's confidence in `role`. */ confidence?: number; /** The keyword score of `role`. */ score?: number; /** The best-scoring candidates, for the audit trail (at most 3). */ candidates: { id: string; score?: number; }[]; reason?: 'no-candidates' | 'no-match' | 'ambiguous'; } /** Keyword pick over `candidates`; `all` supplies the reporting tree. Roles * that clear MIN_ROLE_SCORE rank by score × `prior` (roleOutcomePrior), so * history breaks near-ties but cannot move a role past a 1.35× gap. */ export declare function keywordRole(task: TaskText, candidates: PickableRole[], all?: PickableRole[], load?: (roleId: string) => number, prior?: (roleId: string) => number): RolePick; /** The role that should own a task: agent roles only, never the caller; * Jev first (accepted answers only), then the keyword pick, which `history` * (the org's finished tasks) biases toward roles that did well on similar * ones. */ export declare function pickTaskRole(task: TaskText, roles: PickableRole[], opts?: PickOptions & { caller?: string; load?: (roleId: string) => number; history?: TaskOutcome[]; }): Promise; /** Up to `max` pool skills whose name/tags/description fit the task; those * that clear MIN_SKILL_SCORE rank by score × `prior` (skillOutcomePrior). */ export declare function keywordSkills(task: TaskText, skills: { id: string; description?: string; text?: string; }[], max?: number, prior?: (skill: string) => number): string[]; /** Resolves `assignee: "auto"` on org_task (SessionOpts.pickAssignee). * pickTaskRole (task-match.ts) falls back to a deterministic keyword match * over role titles/responsibilities whenever no decision model answers, so * this is wired unconditionally: gating it behind decisionModelConfigured() * once left a literal "auto" assignee stranded as 'ready' forever * (round1-issue1). Candidates are the agent roles other than the caller; * `load` (open tasks per role) only breaks ties between interchangeable * roles; `history` (the run's tasks) lets the keyword pick lean toward roles * that finished similar tasks. */ export declare function resolveAutoAssignee(def: Pick, load?: (roleId: string) => number, history?: () => TaskOutcome[]): (title: string, brief?: string, caller?: string) => Promise; export {}; //# sourceMappingURL=task-match.d.ts.map