/** * Ticket ask classifier (#827) — is an owner message a new ask, or a * follow-up on the work already in progress? * * Ticket intake used to append every message in a thread with an open ticket * to that ticket, and to drop request-phrased questions ("可以去研究一下 X 吗") * as noise. On 2026-09-26 that turned 15+ distinct asks into 6 tickets. This * classifier gives intake one deterministic answer per message: * * - `new_ask` — a new unit of work with its own deliverable → its own ticket * - `question` — a pure information question → a lightweight `question` * ticket with no acceptance step * - `follow_up` — an answer, approval, clarification, correction, delivery * instruction or status ping about the current work → append * - `not_ask` — nothing to act on (top level only) * * **Scoring.** Each message gets an ask score and a follow score from the * patterns in {@link TICKET_CONSTANTS.ASK}. It is a new ask only when the ask * score reaches {@link TICKET_CONSTANTS.ASK.MIN_ASK_SCORE} AND is strictly * greater than the follow score. Ties append: over-splitting (ticket spam, an * extra review nudge per ticket) is the worse failure for the owner, and an * agent can split a ticket afterwards. * * No LLM, no network: the same text always gets the same verdict, and every * verdict carries the signals that produced it. * * @module services/v3/ticket-ask-classifier */ /** What a message is, for ticket intake. */ export type AskVerdict = 'new_ask' | 'question' | 'follow_up' | 'not_ask'; /** Result of {@link classifyOwnerMessage}. */ export interface AskClassification { verdict: AskVerdict; /** Ask score (request verb 2, info question / new topic / idea +1 each) */ ask: number; /** Follow-up score (the strongest follow-up signal) */ follow: number; /** Names of the signals that fired, for logs and tests */ signals: string[]; } /** Context the verdict depends on. */ export interface AskContext { /** * The message is a reply in a thread that already has a ticket. In a * thread the question is "new ask or follow-up"; at top level it is "ask or * not" (status pings and chatter are `not_ask`). */ inThread: boolean; } /** * Length of a text counting wide (CJK) characters twice: a Chinese sentence * says as much in 7 characters as an English one in 14. * * @param text - Trimmed text * @returns Weighted length */ export declare function weightedTextLength(text: string): number; /** * The words of a message, without what Slack wraps around them: mention * codes, `[Slack File: …]` / `[Slack Image: …]` / `[Hint: …]` lines and * markdown image embeds. Links keep their URL. * * @param text - Raw message text * @returns Cleaned, trimmed text */ export declare function askText(text: string): string; /** * Classify one owner message. Pure and deterministic. * * @param rawText - The message as the owner wrote it * @param ctx - Whether it is a reply in a ticket's thread * @returns The verdict, both scores and the signals that fired * * @example * ```typescript * classifyOwnerMessage('可以去研究一下opus做视频那个吗', { inThread: true }).verdict; // 'new_ask' * classifyOwnerMessage('好的 开issue可以的', { inThread: true }).verdict; // 'follow_up' * classifyOwnerMessage('这个团队都有几个人', { inThread: false }).verdict; // 'question' * ``` */ export declare function classifyOwnerMessage(rawText: string, ctx: AskContext): AskClassification; //# sourceMappingURL=ticket-ask-classifier.d.ts.map