import { q as DiscussionMention } from '../types-5lxby8F5.js'; /** * Mention Parser Utility * * Extracts @mentions from text content for discussion tagging. */ /** * Parsed mention from content */ interface ParsedMention { /** The handle without the @ symbol */ identifier: string; /** Character position where the @ starts */ position: number; } /** * Extract @mentions from content string * * @param content - The text content to parse * @returns Array of parsed mentions with identifier and position * * @example * ```typescript * parseMentions('Hey @viktor-petrov check this'); * // Returns: [{ identifier: 'viktor-petrov', position: 4 }] * ``` */ declare function parseMentions(content: string): ParsedMention[]; /** * Mention Resolution Utility * * Resolves @mention identifiers to TeamMember IDs. */ /** * Lookup entry for agent/user resolution. * * `teamMemberId` may be empty string for entries that resolved via a user's * email handle but are not linked to a teammember row (rare; user-only mention). * `userId` is set for human users when known; agents leave it undefined. */ interface AgentLookupEntry { teamMemberId: string; userId?: string; isAgent: boolean; } /** * Resolve parsed mentions to full DiscussionMention objects. * * Hybrid shape: stores BOTH `identifier` (the literal `@handle` typed) and * `teamMemberId` (resolved at write time). The query layer matches either, * so a slug rotation does not invalidate older mentions whose `teamMemberId` * still points at the original row. * * @param parsed - Array of parsed mentions from mentionParser * @param agentLookup - Map of identifier to TeamMember/User info * @returns Array of resolved DiscussionMention objects * * @example * ```typescript * const lookup = new Map([ * ['viktor-petrov', { teamMemberId: 'team_abc', isAgent: true }] * ]); * const resolved = resolveMentions(parsed, lookup); * ``` */ declare function resolveMentions(parsed: ParsedMention[], agentLookup: Map): DiscussionMention[]; /** * Agent Lookup Builder * * Builds the identifier → {teamMemberId, userId, isAgent} map used by * `prepareDiscussionDoc` to resolve `@handle` mentions at write time. * * Pure function: takes already-fetched teammember and user rows and returns * the lookup Map. The d1-worker layer queries Drizzle directly and passes * the arrays in — keeps `db-collections` free of D1/Drizzle dependencies. */ /** * Minimal teammember shape needed for lookup-building. * Matches the columns the d1-worker queries. */ interface TeamMemberLookupRow { id: string; slug?: string | null; userName?: string | null; isAgent?: boolean | null; } /** * Minimal user shape needed for lookup-building. * Email handle (text before `@`) becomes the identifier key. */ interface UserLookupRow { id: string; email?: string | null; } interface BuildAgentLookupInput { teammembers: ReadonlyArray; users: ReadonlyArray; } /** * Build the identifier → AgentLookupEntry map. * * Each teammember contributes: * - Primary key: `slug` (post-PR-272 canonical identifier) * - Fallback key: kebab-cased `userName` (legacy rows where operators sometimes * type `@marcus-chen` from the human-readable display name) * * Each user contributes: * - Email-handle key (e.g. `spencer@thornock.net` → `spencer`) * * Lookup is case-insensitive: keys are lowercased on insert; the parser * already lowercases extracted identifiers. * * Conflict policy: teammember entries are written first. A user's email * handle that collides with a teammember slug WILL NOT overwrite the * teammember entry — teammember wins. */ declare function buildAgentLookup(input: BuildAgentLookupInput): Map; /** * prepareDiscussionDoc * * Write-time hook that parses `@mentions` from a discussion's `content` * field and populates `mentions[]` with the hybrid identifier+teamMemberId * shape (and optional `userId` for human users). * * Idempotent: re-running on a document that already has `mentions[]` * re-parses the content (covers content edits where the mention list * may have changed). * * Forward-only: existing rows are not migrated. Only writes that pass * through this hook get their mentions populated. */ /** * Mutate `doc.mentions` in place based on `doc.content`. * * - Empty / missing content → `mentions` left untouched (no key added). * - Non-empty content → `mentions` is set to the resolved array (replacing any prior value). * * Returns the same doc reference for ergonomic chaining in the caller's loop. */ declare function prepareDiscussionDoc(doc: D, lookup: Map): D; /** * prepareEntityDescriptionDoc * * Write-time hook that parses `@mentions` from a task / milestone / project * `description` field and populates `mentions[]` with the hybrid * identifier+teamMemberId shape (and optional `userId` for human users). * * Sibling to `prepareDiscussionDoc` — same parser, same resolver, just * keyed off `description` instead of `content`. Lives as its own helper * so the d1-worker can fan out write hooks per collection without * branching inside `prepareDiscussionDoc`. * * Idempotent: re-running on a document that already has `mentions[]` * re-parses the description (covers description edits where the mention * list may have changed). * * Forward-only: existing rows are not migrated. Only writes that pass * through this hook get their mentions populated. */ /** * Mutate `doc.mentions` in place based on `doc.description`. * * - Empty / missing description → `mentions` left untouched (no key added). * - Non-empty description → `mentions` is set to the resolved array * (replacing any prior value). * * Returns the same doc reference for ergonomic chaining in the caller's loop. */ declare function prepareEntityDescriptionDoc(doc: D, lookup: Map): D; export { type AgentLookupEntry, type BuildAgentLookupInput, type ParsedMention, type TeamMemberLookupRow, type UserLookupRow, buildAgentLookup, parseMentions, prepareDiscussionDoc, prepareEntityDescriptionDoc, resolveMentions };