{"version":3,"sources":["../../src/utils/mentionParser.ts","../../src/utils/mentionResolver.ts","../../src/utils/buildAgentLookup.ts","../../src/utils/prepareDiscussionDoc.ts","../../src/utils/prepareEntityDescriptionDoc.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Mention Parser Utility\n *\n * Extracts @mentions from text content for discussion tagging.\n */\n\n/**\n * Parsed mention from content\n */\nexport interface ParsedMention {\n  /** The handle without the @ symbol */\n  identifier: string;\n  /** Character position where the @ starts */\n  position: number;\n}\n\n/**\n * Regex to match @mentions\n * Supports: letters (case-insensitive), numbers, hyphens, underscores\n * Identifiers are normalized to lowercase in the output\n * Examples: @viktor-petrov, @team_xyz789, @CEO\n */\nconst MENTION_REGEX = /@([a-zA-Z0-9_-]+)/g;\n\n/**\n * Extract @mentions from content string\n *\n * @param content - The text content to parse\n * @returns Array of parsed mentions with identifier and position\n *\n * @example\n * ```typescript\n * parseMentions('Hey @viktor-petrov check this');\n * // Returns: [{ identifier: 'viktor-petrov', position: 4 }]\n * ```\n */\nexport function parseMentions(content: string): ParsedMention[] {\n  if (!content) {\n    return [];\n  }\n\n  const mentions: ParsedMention[] = [];\n  let match: RegExpExecArray | null;\n\n  // Reset regex state for fresh matching\n  MENTION_REGEX.lastIndex = 0;\n\n  while ((match = MENTION_REGEX.exec(content)) !== null) {\n    mentions.push({\n      identifier: match[1].toLowerCase(),\n      position: match.index,\n    });\n  }\n\n  return mentions;\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Mention Resolution Utility\n *\n * Resolves @mention identifiers to TeamMember IDs.\n */\n\nimport type { ParsedMention } from './mentionParser'\nimport type { DiscussionMention } from '../types'\n\n/**\n * Lookup entry for agent/user resolution.\n *\n * `teamMemberId` may be empty string for entries that resolved via a user's\n * email handle but are not linked to a teammember row (rare; user-only mention).\n * `userId` is set for human users when known; agents leave it undefined.\n */\nexport interface AgentLookupEntry {\n  teamMemberId: string\n  userId?: string\n  isAgent: boolean\n}\n\n/**\n * Resolve parsed mentions to full DiscussionMention objects.\n *\n * Hybrid shape: stores BOTH `identifier` (the literal `@handle` typed) and\n * `teamMemberId` (resolved at write time). The query layer matches either,\n * so a slug rotation does not invalidate older mentions whose `teamMemberId`\n * still points at the original row.\n *\n * @param parsed - Array of parsed mentions from mentionParser\n * @param agentLookup - Map of identifier to TeamMember/User info\n * @returns Array of resolved DiscussionMention objects\n *\n * @example\n * ```typescript\n * const lookup = new Map([\n *   ['viktor-petrov', { teamMemberId: 'team_abc', isAgent: true }]\n * ]);\n * const resolved = resolveMentions(parsed, lookup);\n * ```\n */\nexport function resolveMentions(\n  parsed: ParsedMention[],\n  agentLookup: Map<string, AgentLookupEntry>\n): DiscussionMention[] {\n  return parsed.map(p => {\n    const entry = agentLookup.get(p.identifier)\n\n    const mention: DiscussionMention = {\n      // If entry exists, check isAgent; if no entry, default to 'agent'\n      type: entry ? (entry.isAgent ? 'agent' : 'user') : 'agent',\n      identifier: p.identifier,\n      position: p.position,\n    }\n\n    // Treat empty-string teamMemberId as \"no resolved teammember\" (user-only entry)\n    if (entry?.teamMemberId) {\n      mention.teamMemberId = entry.teamMemberId\n    }\n    if (entry?.userId) {\n      mention.userId = entry.userId\n    }\n\n    return mention\n  })\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Agent Lookup Builder\n *\n * Builds the identifier → {teamMemberId, userId, isAgent} map used by\n * `prepareDiscussionDoc` to resolve `@handle` mentions at write time.\n *\n * Pure function: takes already-fetched teammember and user rows and returns\n * the lookup Map. The d1-worker layer queries Drizzle directly and passes\n * the arrays in — keeps `db-collections` free of D1/Drizzle dependencies.\n */\n\nimport type { AgentLookupEntry } from './mentionResolver'\n\n/**\n * Minimal teammember shape needed for lookup-building.\n * Matches the columns the d1-worker queries.\n */\nexport interface TeamMemberLookupRow {\n  id: string\n  slug?: string | null\n  userName?: string | null\n  isAgent?: boolean | null\n}\n\n/**\n * Minimal user shape needed for lookup-building.\n * Email handle (text before `@`) becomes the identifier key.\n */\nexport interface UserLookupRow {\n  id: string\n  email?: string | null\n}\n\nexport interface BuildAgentLookupInput {\n  teammembers: ReadonlyArray<TeamMemberLookupRow>\n  users: ReadonlyArray<UserLookupRow>\n}\n\n/**\n * Convert a userName-style display name into a kebab-cased lookup key.\n * Example: \"Marcus Chen\" → \"marcus-chen\".\n *\n * Strips any character that isn't a letter, digit, hyphen, or underscore;\n * collapses runs of separators into a single hyphen; lowercases the result.\n */\nfunction kebabize(input: string): string {\n  return input\n    .toLowerCase()\n    .replace(/[^a-z0-9_-]+/g, '-')\n    .replace(/-+/g, '-')\n    .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Extract the local-part of an email address for use as a mention handle.\n * Example: \"spencer@thornock.net\" → \"spencer\".\n */\nfunction emailHandle(email: string): string {\n  const at = email.indexOf('@')\n  return at === -1 ? email.toLowerCase() : email.slice(0, at).toLowerCase()\n}\n\n/**\n * Build the identifier → AgentLookupEntry map.\n *\n * Each teammember contributes:\n * - Primary key: `slug` (post-PR-272 canonical identifier)\n * - Fallback key: kebab-cased `userName` (legacy rows where operators sometimes\n *   type `@marcus-chen` from the human-readable display name)\n *\n * Each user contributes:\n * - Email-handle key (e.g. `spencer@thornock.net` → `spencer`)\n *\n * Lookup is case-insensitive: keys are lowercased on insert; the parser\n * already lowercases extracted identifiers.\n *\n * Conflict policy: teammember entries are written first. A user's email\n * handle that collides with a teammember slug WILL NOT overwrite the\n * teammember entry — teammember wins.\n */\nexport function buildAgentLookup(input: BuildAgentLookupInput): Map<string, AgentLookupEntry> {\n  const lookup = new Map<string, AgentLookupEntry>()\n\n  for (const tm of input.teammembers) {\n    if (!tm.id) continue\n    const isAgent = !!tm.isAgent\n\n    if (tm.slug) {\n      const key = tm.slug.toLowerCase()\n      if (!lookup.has(key)) {\n        lookup.set(key, { teamMemberId: tm.id, isAgent })\n      }\n    }\n\n    if (tm.userName) {\n      const key = kebabize(tm.userName)\n      if (key && !lookup.has(key)) {\n        lookup.set(key, { teamMemberId: tm.id, isAgent })\n      }\n    }\n  }\n\n  for (const u of input.users) {\n    if (!u.id || !u.email) continue\n    const key = emailHandle(u.email)\n    if (!key || lookup.has(key)) continue\n    // User-only entry: no teammember linkage, but we know the userId.\n    lookup.set(key, { teamMemberId: '', userId: u.id, isAgent: false })\n  }\n\n  return lookup\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * prepareDiscussionDoc\n *\n * Write-time hook that parses `@mentions` from a discussion's `content`\n * field and populates `mentions[]` with the hybrid identifier+teamMemberId\n * shape (and optional `userId` for human users).\n *\n * Idempotent: re-running on a document that already has `mentions[]`\n * re-parses the content (covers content edits where the mention list\n * may have changed).\n *\n * Forward-only: existing rows are not migrated. Only writes that pass\n * through this hook get their mentions populated.\n */\n\nimport type { AgentLookupEntry } from './mentionResolver'\nimport { parseMentions } from './mentionParser'\nimport { resolveMentions } from './mentionResolver'\nimport type { DiscussionMention } from '../types'\n\n/**\n * Mutate `doc.mentions` in place based on `doc.content`.\n *\n * - Empty / missing content → `mentions` left untouched (no key added).\n * - Non-empty content → `mentions` is set to the resolved array (replacing any prior value).\n *\n * Returns the same doc reference for ergonomic chaining in the caller's loop.\n */\nexport function prepareDiscussionDoc<\n  D extends {\n    content?: unknown\n    mentions?: DiscussionMention[]\n  },\n>(doc: D, lookup: Map<string, AgentLookupEntry>): D {\n  if (typeof doc.content !== 'string' || doc.content.length === 0) {\n    return doc\n  }\n\n  const parsed = parseMentions(doc.content)\n  doc.mentions = resolveMentions(parsed, lookup)\n  return doc\n}\n","// Copyright 2026 Epic Digital Interactive Media LLC\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * prepareEntityDescriptionDoc\n *\n * Write-time hook that parses `@mentions` from a task / milestone / project\n * `description` field and populates `mentions[]` with the hybrid\n * identifier+teamMemberId shape (and optional `userId` for human users).\n *\n * Sibling to `prepareDiscussionDoc` — same parser, same resolver, just\n * keyed off `description` instead of `content`. Lives as its own helper\n * so the d1-worker can fan out write hooks per collection without\n * branching inside `prepareDiscussionDoc`.\n *\n * Idempotent: re-running on a document that already has `mentions[]`\n * re-parses the description (covers description edits where the mention\n * list may have changed).\n *\n * Forward-only: existing rows are not migrated. Only writes that pass\n * through this hook get their mentions populated.\n */\n\nimport type { AgentLookupEntry } from './mentionResolver'\nimport { parseMentions } from './mentionParser'\nimport { resolveMentions } from './mentionResolver'\nimport type { DiscussionMention } from '../types'\n\n/**\n * Mutate `doc.mentions` in place based on `doc.description`.\n *\n * - Empty / missing description → `mentions` left untouched (no key added).\n * - Non-empty description → `mentions` is set to the resolved array\n *   (replacing any prior value).\n *\n * Returns the same doc reference for ergonomic chaining in the caller's loop.\n */\nexport function prepareEntityDescriptionDoc<\n  D extends {\n    description?: unknown\n    mentions?: DiscussionMention[]\n  },\n>(doc: D, lookup: Map<string, AgentLookupEntry>): D {\n  if (typeof doc.description !== 'string' || doc.description.length === 0) {\n    return doc\n  }\n\n  const parsed = parseMentions(doc.description)\n  doc.mentions = resolveMentions(parsed, lookup)\n  return doc\n}\n"]}