@redaksjon/protokoll-engine 0.1.19 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index33.js +1 -1
- package/dist/index34.js +2 -2
- package/dist/index35.js +4 -4
- package/dist/index36.js +1 -1
- package/dist/index53.js +5 -48
- package/dist/index53.js.map +1 -1
- package/dist/index54.js +46 -34
- package/dist/index54.js.map +1 -1
- package/dist/index55.js +35 -280
- package/dist/index55.js.map +1 -1
- package/dist/index56.js +258 -137
- package/dist/index56.js.map +1 -1
- package/dist/index57.js +142 -60
- package/dist/index57.js.map +1 -1
- package/dist/index58.js +73 -70
- package/dist/index58.js.map +1 -1
- package/dist/index59.js +73 -3
- package/dist/index59.js.map +1 -1
- package/dist/index60.js +148 -5
- package/dist/index60.js.map +1 -1
- package/dist/index61.js +4 -4
- package/dist/index62.js +5 -148
- package/dist/index62.js.map +1 -1
- package/package.json +3 -3
package/dist/index55.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index55.js","sources":["../src/util/sounds-like-database.ts"],"sourcesContent":["/**\n * Sounds-Like Database\n *\n * Aggregates sounds_like mappings from multiple sources (projects, people, terms)\n * and provides efficient lookup and collision detection for entity correction.\n *\n * Part of the simple-replace optimization (Phase 1).\n */\n\nimport * as Logging from '@/logging';\nimport * as yaml from 'js-yaml';\nimport * as fs from 'fs/promises';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport type { ContextInstance } from '@redaksjon/context';\n\n/**\n * Represents a single sounds_like mapping entry\n */\nexport interface SoundsLikeMapping {\n /** What Whisper typically hears (e.g., \"protocol\", \"observation\") */\n soundsLike: string;\n\n /** Correct text to replace with (e.g., \"Protokoll\", \"Observasjon\") */\n correctText: string;\n\n /** Type of entity (project, person, or term) */\n entityType: 'project' | 'person' | 'term';\n\n /** Unique identifier for the entity */\n entityId: string;\n\n /** Only apply replacement in these project contexts (null = apply everywhere) */\n scopedToProjects?: string[] | null;\n\n /** Collision risk level: none (Tier 1), low/medium (Tier 2), high (Tier 3) */\n collisionRisk: 'none' | 'low' | 'medium' | 'high';\n\n /** Tier classification (1 = always safe, 2 = project-scoped, 3 = ambiguous) */\n tier: 1 | 2 | 3;\n\n /** Minimum confidence required for Tier 2 replacements */\n minConfidence?: number;\n}\n\n/**\n * Collision information for a sounds_like value\n */\nexport interface Collision {\n /** The sounds_like value that has collisions */\n soundsLike: string;\n\n /** All mappings that share this sounds_like value */\n mappings: SoundsLikeMapping[];\n\n /** Number of conflicting mappings */\n count: number;\n}\n\n/**\n * Database of sounds_like mappings with collision detection\n */\nexport interface SoundsLikeDatabase {\n /** All loaded mappings */\n mappings: SoundsLikeMapping[];\n\n /** Tier 1 mappings (always safe to apply) */\n tier1: SoundsLikeMapping[];\n\n /** Tier 2 mappings (require project-scoping) */\n tier2: Map<string, SoundsLikeMapping[]>; // Keyed by project ID\n\n /** Tier 3 mappings (too ambiguous, skip) */\n tier3: SoundsLikeMapping[];\n\n /** Detected collisions */\n collisions: Map<string, Collision>;\n\n /** Common terms that should not be replaced */\n commonTerms: Set<string>;\n\n /** Generic terms to always skip (Tier 3) */\n genericTerms: Set<string>;\n}\n\n/**\n * Configuration for the sounds-like database\n */\nexport interface DatabaseConfig {\n /** Protokoll context directories to load from */\n protokollContextPaths?: string[];\n\n /** Confidence threshold for Tier 2 replacements */\n tier2Confidence?: number;\n\n /** Enable collision detection */\n detectCollisions?: boolean;\n\n /** Custom common terms list */\n commonTerms?: string[];\n\n /** Custom generic terms list */\n genericTerms?: string[];\n\n /**\n * An already-loaded context instance. When provided, entity data is read\n * directly from it (covering projects, people, and terms) instead of\n * rescanning the filesystem.\n */\n contextInstance?: ContextInstance;\n}\n\n/**\n * Instance interface for the sounds-like database\n */\nexport interface Instance {\n /** Load all sounds_like mappings from sources */\n load(): Promise<SoundsLikeDatabase>;\n\n /** Get all Tier 1 (always safe) mappings */\n getTier1Mappings(): SoundsLikeMapping[];\n\n /** Get Tier 2 (project-scoped) mappings for a specific project */\n getTier2MappingsForProject(projectId: string): SoundsLikeMapping[];\n\n /** Check if a sounds_like value has collisions */\n hasCollision(soundsLike: string): boolean;\n\n /** Get collision info for a sounds_like value */\n getCollision(soundsLike: string): Collision | undefined;\n\n /** Get all collisions */\n getAllCollisions(): Collision[];\n\n /** Classify a mapping into a tier based on collision risk */\n classifyTier(mapping: Partial<SoundsLikeMapping>): 1 | 2 | 3;\n}\n\ninterface ProtokolProject {\n id: string;\n name: string;\n type: 'project';\n sounds_like?: string[];\n classification?: {\n context_type?: 'work' | 'personal' | 'mixed';\n };\n active?: boolean;\n}\n\n/**\n * Create a sounds-like database instance\n */\nexport const create = (config?: DatabaseConfig): Instance => {\n const logger = Logging.getLogger();\n\n const database: SoundsLikeDatabase = {\n mappings: [],\n tier1: [],\n tier2: new Map(),\n tier3: [],\n collisions: new Map(),\n commonTerms: new Set(config?.commonTerms ?? DEFAULT_COMMON_TERMS),\n genericTerms: new Set(config?.genericTerms ?? DEFAULT_GENERIC_TERMS),\n };\n\n /**\n * Find protokoll context directories\n */\n const findProtokolDirectories = async (): Promise<string[]> => {\n if (config?.protokollContextPaths) {\n return config.protokollContextPaths;\n }\n\n const homeDir = os.homedir();\n const primaryPath = path.join(homeDir, '.protokoll', 'context');\n\n const dirs: string[] = [];\n\n // Check primary protokoll directory\n try {\n await fs.access(primaryPath);\n dirs.push(primaryPath);\n logger.debug(`Found protokoll context at: ${primaryPath}`);\n } catch {\n logger.debug(`No protokoll context found at: ${primaryPath}`);\n }\n\n return dirs;\n };\n\n /**\n * Load projects from protokoll context\n */\n const loadProjectsFromProtokoll = async (): Promise<SoundsLikeMapping[]> => {\n logger.debug('Loading projects from protokoll context');\n\n const contextDirs = await findProtokolDirectories();\n\n if (contextDirs.length === 0) {\n logger.warn('No protokoll context directories found');\n return [];\n }\n\n const mappings: SoundsLikeMapping[] = [];\n\n for (const contextDir of contextDirs) {\n const projectsDir = path.join(contextDir, 'projects');\n\n try {\n const files = await fs.readdir(projectsDir);\n\n for (const file of files) {\n if (!file.endsWith('.yaml') && !file.endsWith('.yml')) continue;\n\n try {\n const content = await fs.readFile(path.join(projectsDir, file), 'utf-8');\n const parsed = yaml.load(content) as Partial<ProtokolProject>;\n\n if (!parsed || !parsed.id || !parsed.name) {\n logger.debug(`Skipping invalid project file: ${file}`);\n continue;\n }\n\n // Skip inactive projects\n if (parsed.active === false) {\n logger.debug(`Skipping inactive project: ${parsed.id}`);\n continue;\n }\n\n // Process sounds_like entries\n if (parsed.sounds_like && parsed.sounds_like.length > 0) {\n for (const soundsLike of parsed.sounds_like) {\n mappings.push({\n soundsLike: soundsLike.toLowerCase(),\n correctText: parsed.name,\n entityType: 'project',\n entityId: parsed.id,\n scopedToProjects: null, // Will be determined by collision detection\n collisionRisk: 'none', // Will be determined by collision detection\n tier: 1, // Will be determined by collision detection\n });\n }\n logger.debug(`Loaded ${parsed.sounds_like.length} sounds_like entries for project: ${parsed.id}`);\n }\n } catch (error: any) {\n logger.warn(`Failed to parse project file ${file}: ${error.message}`);\n }\n }\n } catch (error: any) {\n logger.debug(`Could not read projects directory ${projectsDir}: ${error.message}`);\n }\n }\n\n logger.info(`Loaded ${mappings.length} sounds_like mappings from protokoll projects`);\n return mappings;\n };\n\n /**\n * Detect collisions in mappings\n */\n const detectCollisions = (mappings: SoundsLikeMapping[]): Map<string, Collision> => {\n const collisionMap = new Map<string, SoundsLikeMapping[]>();\n\n // Group by sounds_like value (case-insensitive)\n for (const mapping of mappings) {\n const key = mapping.soundsLike.toLowerCase();\n if (!collisionMap.has(key)) {\n collisionMap.set(key, []);\n }\n collisionMap.get(key)!.push(mapping);\n }\n\n // Identify actual collisions (multiple mappings for same sounds_like)\n const collisions = new Map<string, Collision>();\n for (const [soundsLike, conflictMappings] of collisionMap) {\n if (conflictMappings.length > 1) {\n collisions.set(soundsLike, {\n soundsLike,\n mappings: conflictMappings,\n count: conflictMappings.length,\n });\n logger.debug(`Collision detected for \"${soundsLike}\": ${conflictMappings.length} mappings`);\n }\n }\n\n logger.info(`Detected ${collisions.size} collisions in sounds_like mappings`);\n return collisions;\n };\n\n /**\n * Classify a mapping into a tier based on collision risk\n */\n const classifyTier = (mapping: Partial<SoundsLikeMapping>): 1 | 2 | 3 => {\n if (!mapping.soundsLike) {\n return 3; // Invalid, treat as ambiguous\n }\n\n const soundsLikeLower = mapping.soundsLike.toLowerCase();\n\n // Tier 3: Generic terms (always skip)\n if (database.genericTerms.has(soundsLikeLower)) {\n return 3;\n }\n\n // Tier 2: Common terms (require project-scoping)\n if (database.commonTerms.has(soundsLikeLower)) {\n return 2;\n }\n\n // Tier 2: Has collision with other mappings\n if (database.collisions.has(soundsLikeLower)) {\n return 2;\n }\n\n // Tier 1: Unique, no collisions, not a common term\n return 1;\n };\n\n /**\n * Assign tiers and collision info to all mappings\n */\n const assignTiersAndCollisions = (mappings: SoundsLikeMapping[]): void => {\n for (const mapping of mappings) {\n // Classify tier\n mapping.tier = classifyTier(mapping);\n\n // Determine collision risk\n if (database.collisions.has(mapping.soundsLike.toLowerCase())) {\n mapping.collisionRisk = 'high';\n } else if (database.commonTerms.has(mapping.soundsLike.toLowerCase())) {\n mapping.collisionRisk = 'medium';\n } else if (mapping.tier === 2) {\n mapping.collisionRisk = 'low';\n } else {\n mapping.collisionRisk = 'none';\n }\n\n // Scope to projects for Tier 2\n if (mapping.tier === 2 && mapping.entityType === 'project') {\n mapping.scopedToProjects = [mapping.entityId];\n mapping.minConfidence = config?.tier2Confidence ?? 0.6;\n }\n\n logger.debug(\n `Classified \"${mapping.soundsLike}\" → \"${mapping.correctText}\" ` +\n `(${mapping.entityType}:${mapping.entityId}) as Tier ${mapping.tier} ` +\n `(risk: ${mapping.collisionRisk})`\n );\n }\n };\n\n /**\n * Organize mappings by tier\n */\n const organizeMappingsByTier = (mappings: SoundsLikeMapping[]): void => {\n database.tier1 = [];\n database.tier2 = new Map();\n database.tier3 = [];\n\n for (const mapping of mappings) {\n if (mapping.tier === 1) {\n database.tier1.push(mapping);\n } else if (mapping.tier === 2) {\n // Organize Tier 2 by project ID for efficient lookup\n if (mapping.entityType === 'project') {\n if (!database.tier2.has(mapping.entityId)) {\n database.tier2.set(mapping.entityId, []);\n }\n database.tier2.get(mapping.entityId)!.push(mapping);\n } else {\n // For non-project entities in Tier 2, add to a generic bucket\n if (!database.tier2.has('_generic')) {\n database.tier2.set('_generic', []);\n }\n database.tier2.get('_generic')!.push(mapping);\n }\n } else {\n database.tier3.push(mapping);\n }\n }\n\n logger.info(\n `Organized mappings: Tier 1=${database.tier1.length}, ` +\n `Tier 2=${Array.from(database.tier2.values()).reduce((sum, arr) => sum + arr.length, 0)}, ` +\n `Tier 3=${database.tier3.length}`\n );\n };\n\n /**\n * Load all sounds_like mappings from a ContextInstance (projects, people, terms).\n * Preferred over filesystem scanning when the context is already loaded.\n */\n const loadEntitiesFromContext = (ctx: ContextInstance): SoundsLikeMapping[] => {\n const mappings: SoundsLikeMapping[] = [];\n\n // Helper: emit one mapping per sounds_like value plus the canonical name itself.\n // Including the name means an entity spelled correctly in the transcript is still\n // recognised and associated as an entity reference.\n const addMappings = (\n name: string,\n entityId: string,\n entityType: 'project' | 'person' | 'term',\n soundsLike: string[],\n active?: boolean\n ) => {\n if (active === false) return;\n\n const seen = new Set<string>();\n const push = (raw: string) => {\n const key = raw.toLowerCase().trim();\n if (!key || seen.has(key)) return;\n seen.add(key);\n mappings.push({\n soundsLike: key,\n correctText: name,\n entityType,\n entityId,\n scopedToProjects: null,\n collisionRisk: 'none',\n tier: 1,\n });\n };\n\n // Always include the canonical name so exact spellings are matched\n push(name);\n for (const sl of soundsLike) {\n push(sl);\n }\n };\n\n // Projects\n for (const project of ctx.getAllProjects()) {\n addMappings(project.name, project.id, 'project', project.sounds_like ?? [], project.active);\n }\n\n // People\n for (const person of ctx.getAllPeople()) {\n addMappings(person.name, person.id, 'person', person.sounds_like ?? []);\n }\n\n // Terms\n for (const term of ctx.getAllTerms()) {\n addMappings(term.name, term.id, 'term', term.sounds_like ?? []);\n }\n\n const projectCount = ctx.getAllProjects().filter(p => p.active !== false).length;\n const peopleCount = ctx.getAllPeople().length;\n const termCount = ctx.getAllTerms().length;\n logger.info(\n `Loaded ${mappings.length} sounds_like mappings from context instance ` +\n `(${projectCount} projects, ${peopleCount} people, ${termCount} terms)`\n );\n\n return mappings;\n };\n\n /**\n * Load all sounds_like mappings\n */\n const load = async (): Promise<SoundsLikeDatabase> => {\n logger.info('Loading sounds_like database');\n\n let allMappings: SoundsLikeMapping[];\n\n if (config?.contextInstance) {\n // Prefer context instance - covers projects, people, and terms in one pass\n allMappings = loadEntitiesFromContext(config.contextInstance);\n } else {\n // Fall back to filesystem scanning (projects only)\n const projectMappings = await loadProjectsFromProtokoll();\n allMappings = projectMappings;\n }\n\n database.mappings = allMappings;\n\n // Detect collisions\n if (config?.detectCollisions !== false) {\n database.collisions = detectCollisions(allMappings);\n }\n\n // Assign tiers and collision info\n assignTiersAndCollisions(allMappings);\n\n // Organize by tier for efficient lookup\n organizeMappingsByTier(allMappings);\n\n logger.info(`Sounds_like database loaded: ${allMappings.length} total mappings`);\n\n return database;\n };\n\n /**\n * Get Tier 1 mappings (always safe)\n */\n const getTier1Mappings = (): SoundsLikeMapping[] => {\n return database.tier1;\n };\n\n /**\n * Get Tier 2 mappings for a specific project\n */\n const getTier2MappingsForProject = (projectId: string): SoundsLikeMapping[] => {\n const projectMappings = database.tier2.get(projectId) ?? [];\n const genericMappings = database.tier2.get('_generic') ?? [];\n return [...projectMappings, ...genericMappings];\n };\n\n /**\n * Check if a sounds_like value has collisions\n */\n const hasCollision = (soundsLike: string): boolean => {\n return database.collisions.has(soundsLike.toLowerCase());\n };\n\n /**\n * Get collision info\n */\n const getCollision = (soundsLike: string): Collision | undefined => {\n return database.collisions.get(soundsLike.toLowerCase());\n };\n\n /**\n * Get all collisions\n */\n const getAllCollisions = (): Collision[] => {\n return Array.from(database.collisions.values());\n };\n\n return {\n load,\n getTier1Mappings,\n getTier2MappingsForProject,\n hasCollision,\n getCollision,\n getAllCollisions,\n classifyTier,\n };\n};\n\n/**\n * Default common terms that indicate Tier 2 (project-scoped) replacements\n */\nconst DEFAULT_COMMON_TERMS = [\n 'protocol',\n 'observation',\n 'composition',\n 'gateway',\n 'service',\n 'system',\n 'platform',\n];\n\n/**\n * Default generic terms that should never be replaced (Tier 3)\n */\nconst DEFAULT_GENERIC_TERMS = [\n 'meeting',\n 'update',\n 'work',\n 'project',\n 'task',\n 'issue',\n 'discussion',\n 'review',\n 'the',\n 'a',\n 'an',\n];\n"],"names":["Logging.getLogger"],"mappings":";;;;;;AAwJO,MAAM,MAAA,GAAS,CAAC,MAAA,KAAsC;AACzD,EAAA,MAAM,MAAA,GAASA,SAAQ,EAAU;AAEjC,EAAA,MAAM,QAAA,GAA+B;AAAA,IACjC,UAAU,EAAC;AAAA,IACX,OAAO,EAAC;AAAA,IACR,KAAA,sBAAW,GAAA,EAAI;AAAA,IACf,OAAO,EAAC;AAAA,IACR,UAAA,sBAAgB,GAAA,EAAI;AAAA,IACpB,WAAA,EAAa,IAAI,GAAA,CAAI,MAAA,EAAQ,eAAe,oBAAoB,CAAA;AAAA,IAChE,YAAA,EAAc,IAAI,GAAA,CAAI,MAAA,EAAQ,gBAAgB,qBAAqB;AAAA,GACvE;AAKA,EAAA,MAAM,0BAA0B,YAA+B;AAC3D,IAAA,IAAI,QAAQ,qBAAA,EAAuB;AAC/B,MAAA,OAAO,MAAA,CAAO,qBAAA;AAAA,IAClB;AAEA,IAAA,MAAM,OAAA,GAAU,GAAG,OAAA,EAAQ;AAC3B,IAAA,MAAM,WAAA,GAAc,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,cAAc,SAAS,CAAA;AAE9D,IAAA,MAAM,OAAiB,EAAC;AAGxB,IAAA,IAAI;AACA,MAAA,MAAM,EAAA,CAAG,OAAO,WAAW,CAAA;AAC3B,MAAA,IAAA,CAAK,KAAK,WAAW,CAAA;AACrB,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,4BAAA,EAA+B,WAAW,CAAA,CAAE,CAAA;AAAA,IAC7D,CAAA,CAAA,MAAQ;AACJ,MAAA,MAAA,CAAO,KAAA,CAAM,CAAA,+BAAA,EAAkC,WAAW,CAAA,CAAE,CAAA;AAAA,IAChE;AAEA,IAAA,OAAO,IAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,4BAA4B,YAA0C;AACxE,IAAA,MAAA,CAAO,MAAM,yCAAyC,CAAA;AAEtD,IAAA,MAAM,WAAA,GAAc,MAAM,uBAAA,EAAwB;AAElD,IAAA,IAAI,WAAA,CAAY,WAAW,CAAA,EAAG;AAC1B,MAAA,MAAA,CAAO,KAAK,wCAAwC,CAAA;AACpD,MAAA,OAAO,EAAC;AAAA,IACZ;AAEA,IAAA,MAAM,WAAgC,EAAC;AAEvC,IAAA,KAAA,MAAW,cAAc,WAAA,EAAa;AAClC,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,IAAA,CAAK,UAAA,EAAY,UAAU,CAAA;AAEpD,MAAA,IAAI;AACA,QAAA,MAAM,KAAA,GAAQ,MAAM,EAAA,CAAG,OAAA,CAAQ,WAAW,CAAA;AAE1C,QAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACtB,UAAA,IAAI,CAAC,KAAK,QAAA,CAAS,OAAO,KAAK,CAAC,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG;AAEvD,UAAA,IAAI;AACA,YAAA,MAAM,OAAA,GAAU,MAAM,EAAA,CAAG,QAAA,CAAS,KAAK,IAAA,CAAK,WAAA,EAAa,IAAI,CAAA,EAAG,OAAO,CAAA;AACvE,YAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAEhC,YAAA,IAAI,CAAC,MAAA,IAAU,CAAC,OAAO,EAAA,IAAM,CAAC,OAAO,IAAA,EAAM;AACvC,cAAA,MAAA,CAAO,KAAA,CAAM,CAAA,+BAAA,EAAkC,IAAI,CAAA,CAAE,CAAA;AACrD,cAAA;AAAA,YACJ;AAGA,YAAA,IAAI,MAAA,CAAO,WAAW,KAAA,EAAO;AACzB,cAAA,MAAA,CAAO,KAAA,CAAM,CAAA,2BAAA,EAA8B,MAAA,CAAO,EAAE,CAAA,CAAE,CAAA;AACtD,cAAA;AAAA,YACJ;AAGA,YAAA,IAAI,MAAA,CAAO,WAAA,IAAe,MAAA,CAAO,WAAA,CAAY,SAAS,CAAA,EAAG;AACrD,cAAA,KAAA,MAAW,UAAA,IAAc,OAAO,WAAA,EAAa;AACzC,gBAAA,QAAA,CAAS,IAAA,CAAK;AAAA,kBACV,UAAA,EAAY,WAAW,WAAA,EAAY;AAAA,kBACnC,aAAa,MAAA,CAAO,IAAA;AAAA,kBACpB,UAAA,EAAY,SAAA;AAAA,kBACZ,UAAU,MAAA,CAAO,EAAA;AAAA,kBACjB,gBAAA,EAAkB,IAAA;AAAA;AAAA,kBAClB,aAAA,EAAe,MAAA;AAAA;AAAA,kBACf,IAAA,EAAM;AAAA;AAAA,iBACT,CAAA;AAAA,cACL;AACA,cAAA,MAAA,CAAO,KAAA,CAAM,UAAU,MAAA,CAAO,WAAA,CAAY,MAAM,CAAA,kCAAA,EAAqC,MAAA,CAAO,EAAE,CAAA,CAAE,CAAA;AAAA,YACpG;AAAA,UACJ,SAAS,KAAA,EAAY;AACjB,YAAA,MAAA,CAAO,KAAK,CAAA,6BAAA,EAAgC,IAAI,CAAA,EAAA,EAAK,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,UACxE;AAAA,QACJ;AAAA,MACJ,SAAS,KAAA,EAAY;AACjB,QAAA,MAAA,CAAO,MAAM,CAAA,kCAAA,EAAqC,WAAW,CAAA,EAAA,EAAK,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,MACrF;AAAA,IACJ;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,CAAA,OAAA,EAAU,QAAA,CAAS,MAAM,CAAA,6CAAA,CAA+C,CAAA;AACpF,IAAA,OAAO,QAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,gBAAA,GAAmB,CAAC,QAAA,KAA0D;AAChF,IAAA,MAAM,YAAA,uBAAmB,GAAA,EAAiC;AAG1D,IAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC5B,MAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,UAAA,CAAW,WAAA,EAAY;AAC3C,MAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA,EAAG;AACxB,QAAA,YAAA,CAAa,GAAA,CAAI,GAAA,EAAK,EAAE,CAAA;AAAA,MAC5B;AACA,MAAA,YAAA,CAAa,GAAA,CAAI,GAAG,CAAA,CAAG,IAAA,CAAK,OAAO,CAAA;AAAA,IACvC;AAGA,IAAA,MAAM,UAAA,uBAAiB,GAAA,EAAuB;AAC9C,IAAA,KAAA,MAAW,CAAC,UAAA,EAAY,gBAAgB,CAAA,IAAK,YAAA,EAAc;AACvD,MAAA,IAAI,gBAAA,CAAiB,SAAS,CAAA,EAAG;AAC7B,QAAA,UAAA,CAAW,IAAI,UAAA,EAAY;AAAA,UACvB,UAAA;AAAA,UACA,QAAA,EAAU,gBAAA;AAAA,UACV,OAAO,gBAAA,CAAiB;AAAA,SAC3B,CAAA;AACD,QAAA,MAAA,CAAO,MAAM,CAAA,wBAAA,EAA2B,UAAU,CAAA,GAAA,EAAM,gBAAA,CAAiB,MAAM,CAAA,SAAA,CAAW,CAAA;AAAA,MAC9F;AAAA,IACJ;AAEA,IAAA,MAAA,CAAO,IAAA,CAAK,CAAA,SAAA,EAAY,UAAA,CAAW,IAAI,CAAA,mCAAA,CAAqC,CAAA;AAC5E,IAAA,OAAO,UAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,YAAA,GAAe,CAAC,OAAA,KAAmD;AACrE,IAAA,IAAI,CAAC,QAAQ,UAAA,EAAY;AACrB,MAAA,OAAO,CAAA;AAAA,IACX;AAEA,IAAA,MAAM,eAAA,GAAkB,OAAA,CAAQ,UAAA,CAAW,WAAA,EAAY;AAGvD,IAAA,IAAI,QAAA,CAAS,YAAA,CAAa,GAAA,CAAI,eAAe,CAAA,EAAG;AAC5C,MAAA,OAAO,CAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAA,CAAS,WAAA,CAAY,GAAA,CAAI,eAAe,CAAA,EAAG;AAC3C,MAAA,OAAO,CAAA;AAAA,IACX;AAGA,IAAA,IAAI,QAAA,CAAS,UAAA,CAAW,GAAA,CAAI,eAAe,CAAA,EAAG;AAC1C,MAAA,OAAO,CAAA;AAAA,IACX;AAGA,IAAA,OAAO,CAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,wBAAA,GAA2B,CAAC,QAAA,KAAwC;AACtE,IAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAE5B,MAAA,OAAA,CAAQ,IAAA,GAAO,aAAa,OAAO,CAAA;AAGnC,MAAA,IAAI,SAAS,UAAA,CAAW,GAAA,CAAI,QAAQ,UAAA,CAAW,WAAA,EAAa,CAAA,EAAG;AAC3D,QAAA,OAAA,CAAQ,aAAA,GAAgB,MAAA;AAAA,MAC5B,CAAA,MAAA,IAAW,SAAS,WAAA,CAAY,GAAA,CAAI,QAAQ,UAAA,CAAW,WAAA,EAAa,CAAA,EAAG;AACnE,QAAA,OAAA,CAAQ,aAAA,GAAgB,QAAA;AAAA,MAC5B,CAAA,MAAA,IAAW,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG;AAC3B,QAAA,OAAA,CAAQ,aAAA,GAAgB,KAAA;AAAA,MAC5B,CAAA,MAAO;AACH,QAAA,OAAA,CAAQ,aAAA,GAAgB,MAAA;AAAA,MAC5B;AAGA,MAAA,IAAI,OAAA,CAAQ,IAAA,KAAS,CAAA,IAAK,OAAA,CAAQ,eAAe,SAAA,EAAW;AACxD,QAAA,OAAA,CAAQ,gBAAA,GAAmB,CAAC,OAAA,CAAQ,QAAQ,CAAA;AAC5C,QAAA,OAAA,CAAQ,aAAA,GAAgB,QAAQ,eAAA,IAAmB,GAAA;AAAA,MACvD;AAEA,MAAA,MAAA,CAAO,KAAA;AAAA,QACH,eAAe,OAAA,CAAQ,UAAU,CAAA,KAAA,EAAQ,OAAA,CAAQ,WAAW,CAAA,GAAA,EACxD,OAAA,CAAQ,UAAU,CAAA,CAAA,EAAI,QAAQ,QAAQ,CAAA,UAAA,EAAa,QAAQ,IAAI,CAAA,QAAA,EACzD,QAAQ,aAAa,CAAA,CAAA;AAAA,OACnC;AAAA,IACJ;AAAA,EACJ,CAAA;AAKA,EAAA,MAAM,sBAAA,GAAyB,CAAC,QAAA,KAAwC;AACpE,IAAA,QAAA,CAAS,QAAQ,EAAC;AAClB,IAAA,QAAA,CAAS,KAAA,uBAAY,GAAA,EAAI;AACzB,IAAA,QAAA,CAAS,QAAQ,EAAC;AAElB,IAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC5B,MAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACpB,QAAA,QAAA,CAAS,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,MAC/B,CAAA,MAAA,IAAW,OAAA,CAAQ,IAAA,KAAS,CAAA,EAAG;AAE3B,QAAA,IAAI,OAAA,CAAQ,eAAe,SAAA,EAAW;AAClC,UAAA,IAAI,CAAC,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA,EAAG;AACvC,YAAA,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAAA,UAC3C;AACA,UAAA,QAAA,CAAS,MAAM,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA,CAAG,KAAK,OAAO,CAAA;AAAA,QACtD,CAAA,MAAO;AAEH,UAAA,IAAI,CAAC,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,UAAU,CAAA,EAAG;AACjC,YAAA,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,UAAA,EAAY,EAAE,CAAA;AAAA,UACrC;AACA,UAAA,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,UAAU,CAAA,CAAG,KAAK,OAAO,CAAA;AAAA,QAChD;AAAA,MACJ,CAAA,MAAO;AACH,QAAA,QAAA,CAAS,KAAA,CAAM,KAAK,OAAO,CAAA;AAAA,MAC/B;AAAA,IACJ;AAEA,IAAA,MAAA,CAAO,IAAA;AAAA,MACH,CAAA,2BAAA,EAA8B,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,SAAA,EACzC,MAAM,IAAA,CAAK,QAAA,CAAS,KAAA,CAAM,MAAA,EAAQ,CAAA,CAAE,OAAO,CAAC,GAAA,EAAK,GAAA,KAAQ,GAAA,GAAM,GAAA,CAAI,MAAA,EAAQ,CAAC,CAAC,CAAA,SAAA,EAC7E,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA;AAAA,KACnC;AAAA,EACJ,CAAA;AAMA,EAAA,MAAM,uBAAA,GAA0B,CAAC,GAAA,KAA8C;AAC3E,IAAA,MAAM,WAAgC,EAAC;AAKvC,IAAA,MAAM,cAAc,CAChB,IAAA,EACA,QAAA,EACA,UAAA,EACA,YACA,MAAA,KACC;AACD,MAAA,IAAI,WAAW,KAAA,EAAO;AAEtB,MAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,MAAA,MAAM,IAAA,GAAO,CAAC,GAAA,KAAgB;AAC1B,QAAA,MAAM,GAAA,GAAM,GAAA,CAAI,WAAA,EAAY,CAAE,IAAA,EAAK;AACnC,QAAA,IAAI,CAAC,GAAA,IAAO,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,EAAG;AAC3B,QAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,QAAA,QAAA,CAAS,IAAA,CAAK;AAAA,UACV,UAAA,EAAY,GAAA;AAAA,UACZ,WAAA,EAAa,IAAA;AAAA,UACb,UAAA;AAAA,UACA,QAAA;AAAA,UACA,gBAAA,EAAkB,IAAA;AAAA,UAClB,aAAA,EAAe,MAAA;AAAA,UACf,IAAA,EAAM;AAAA,SACT,CAAA;AAAA,MACL,CAAA;AAGA,MAAA,IAAA,CAAK,IAAI,CAAA;AACT,MAAA,KAAA,MAAW,MAAM,UAAA,EAAY;AACzB,QAAA,IAAA,CAAK,EAAE,CAAA;AAAA,MACX;AAAA,IACJ,CAAA;AAGA,IAAA,KAAA,MAAW,OAAA,IAAW,GAAA,CAAI,cAAA,EAAe,EAAG;AACxC,MAAA,WAAA,CAAY,OAAA,CAAQ,IAAA,EAAM,OAAA,CAAQ,EAAA,EAAI,SAAA,EAAW,QAAQ,WAAA,IAAe,EAAC,EAAG,OAAA,CAAQ,MAAM,CAAA;AAAA,IAC9F;AAGA,IAAA,KAAA,MAAW,MAAA,IAAU,GAAA,CAAI,YAAA,EAAa,EAAG;AACrC,MAAA,WAAA,CAAY,MAAA,CAAO,MAAM,MAAA,CAAO,EAAA,EAAI,UAAU,MAAA,CAAO,WAAA,IAAe,EAAE,CAAA;AAAA,IAC1E;AAGA,IAAA,KAAA,MAAW,IAAA,IAAQ,GAAA,CAAI,WAAA,EAAY,EAAG;AAClC,MAAA,WAAA,CAAY,IAAA,CAAK,MAAM,IAAA,CAAK,EAAA,EAAI,QAAQ,IAAA,CAAK,WAAA,IAAe,EAAE,CAAA;AAAA,IAClE;AAEA,IAAA,MAAM,YAAA,GAAe,IAAI,cAAA,EAAe,CAAE,OAAO,CAAA,CAAA,KAAK,CAAA,CAAE,MAAA,KAAW,KAAK,CAAA,CAAE,MAAA;AAC1E,IAAA,MAAM,WAAA,GAAc,GAAA,CAAI,YAAA,EAAa,CAAE,MAAA;AACvC,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,WAAA,EAAY,CAAE,MAAA;AACpC,IAAA,MAAA,CAAO,IAAA;AAAA,MACH,CAAA,OAAA,EAAU,SAAS,MAAM,CAAA,6CAAA,EACrB,YAAY,CAAA,WAAA,EAAc,WAAW,YAAY,SAAS,CAAA,OAAA;AAAA,KAClE;AAEA,IAAA,OAAO,QAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,OAAO,YAAyC;AAClD,IAAA,MAAA,CAAO,KAAK,8BAA8B,CAAA;AAE1C,IAAA,IAAI,WAAA;AAEJ,IAAA,IAAI,QAAQ,eAAA,EAAiB;AAEzB,MAAA,WAAA,GAAc,uBAAA,CAAwB,OAAO,eAAe,CAAA;AAAA,IAChE,CAAA,MAAO;AAEH,MAAA,MAAM,eAAA,GAAkB,MAAM,yBAAA,EAA0B;AACxD,MAAA,WAAA,GAAc,eAAA;AAAA,IAClB;AAEA,IAAA,QAAA,CAAS,QAAA,GAAW,WAAA;AAGpB,IAAA,IAAI,MAAA,EAAQ,qBAAqB,KAAA,EAAO;AACpC,MAAA,QAAA,CAAS,UAAA,GAAa,iBAAiB,WAAW,CAAA;AAAA,IACtD;AAGA,IAAA,wBAAA,CAAyB,WAAW,CAAA;AAGpC,IAAA,sBAAA,CAAuB,WAAW,CAAA;AAElC,IAAA,MAAA,CAAO,IAAA,CAAK,CAAA,6BAAA,EAAgC,WAAA,CAAY,MAAM,CAAA,eAAA,CAAiB,CAAA;AAE/E,IAAA,OAAO,QAAA;AAAA,EACX,CAAA;AAKA,EAAA,MAAM,mBAAmB,MAA2B;AAChD,IAAA,OAAO,QAAA,CAAS,KAAA;AAAA,EACpB,CAAA;AAKA,EAAA,MAAM,0BAAA,GAA6B,CAAC,SAAA,KAA2C;AAC3E,IAAA,MAAM,kBAAkB,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,SAAS,KAAK,EAAC;AAC1D,IAAA,MAAM,kBAAkB,QAAA,CAAS,KAAA,CAAM,GAAA,CAAI,UAAU,KAAK,EAAC;AAC3D,IAAA,OAAO,CAAC,GAAG,eAAA,EAAiB,GAAG,eAAe,CAAA;AAAA,EAClD,CAAA;AAKA,EAAA,MAAM,YAAA,GAAe,CAAC,UAAA,KAAgC;AAClD,IAAA,OAAO,QAAA,CAAS,UAAA,CAAW,GAAA,CAAI,UAAA,CAAW,aAAa,CAAA;AAAA,EAC3D,CAAA;AAKA,EAAA,MAAM,YAAA,GAAe,CAAC,UAAA,KAA8C;AAChE,IAAA,OAAO,QAAA,CAAS,UAAA,CAAW,GAAA,CAAI,UAAA,CAAW,aAAa,CAAA;AAAA,EAC3D,CAAA;AAKA,EAAA,MAAM,mBAAmB,MAAmB;AACxC,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,QAAQ,CAAA;AAAA,EAClD,CAAA;AAEA,EAAA,OAAO;AAAA,IACH,IAAA;AAAA,IACA,gBAAA;AAAA,IACA,0BAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA;AAAA,IACA,gBAAA;AAAA,IACA;AAAA,GACJ;AACJ;AAKA,MAAM,oBAAA,GAAuB;AAAA,EACzB,UAAA;AAAA,EACA,aAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,QAAA;AAAA,EACA;AACJ,CAAA;AAKA,MAAM,qBAAA,GAAwB;AAAA,EAC1B,SAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,GAAA;AAAA,EACA;AACJ,CAAA;;;;"}
|
|
1
|
+
{"version":3,"file":"index55.js","sources":["../src/util/general.ts"],"sourcesContent":["// Utility function for deep merging two objects.\nexport function deepMerge(target: any, source: any): any {\n for (const key in source) {\n if (Object.prototype.hasOwnProperty.call(source, key)) {\n // Block prototype-polluting keys\n if (key === '__proto__' || key === 'constructor') {\n continue;\n }\n if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {\n if (!target[key]) {\n target[key] = {};\n }\n deepMerge(target[key], source[key]);\n } else {\n target[key] = source[key];\n }\n }\n }\n return target;\n}\n\n//Recursive implementation of jSON.stringify;\nexport const stringifyJSON = function (obj: any): string {\n\n const arrOfKeyVals: string[] = [];\n const arrVals: string[] = [];\n let objKeys: string[] = [];\n\n /*********CHECK FOR PRIMITIVE TYPES**********/\n if (typeof obj === 'number' || typeof obj === 'boolean' || obj === null)\n return '' + obj;\n else if (typeof obj === 'string')\n return '\"' + obj + '\"';\n\n /*********CHECK FOR ARRAY**********/\n else if (Array.isArray(obj)) {\n //check for empty array\n if (obj[0] === undefined)\n return '[]';\n else {\n obj.forEach(function (el) {\n arrVals.push(stringifyJSON(el));\n });\n return '[' + arrVals + ']';\n }\n }\n /*********CHECK FOR OBJECT**********/\n else if (obj instanceof Object) {\n //get object keys\n objKeys = Object.keys(obj);\n //set key output;\n objKeys.forEach(function (key) {\n const keyOut = '\"' + key + '\":';\n const keyValOut = obj[key];\n //skip functions and undefined properties\n if (keyValOut instanceof Function || keyValOut === undefined)\n arrOfKeyVals.push('');\n else if (typeof keyValOut === 'string')\n arrOfKeyVals.push(keyOut + '\"' + keyValOut + '\"');\n else if (typeof keyValOut === 'boolean' || typeof keyValOut === 'number' || keyValOut === null)\n arrOfKeyVals.push(keyOut + keyValOut);\n //check for nested objects, call recursively until no more objects\n else if (keyValOut instanceof Object) {\n arrOfKeyVals.push(keyOut + stringifyJSON(keyValOut));\n }\n });\n return '{' + arrOfKeyVals + '}';\n }\n return '';\n};"],"names":[],"mappings":"AAsBO,MAAM,aAAA,GAAgB,SAAU,GAAA,EAAkB;AAErD,EAAA,MAAM,eAAyB,EAAC;AAChC,EAAA,MAAM,UAAoB,EAAC;AAC3B,EAAA,IAAI,UAAoB,EAAC;AAGzB,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,IAAY,OAAO,GAAA,KAAQ,aAAa,GAAA,KAAQ,IAAA;AAC/D,IAAA,OAAO,EAAA,GAAK,GAAA;AAAA,OAAA,IACP,OAAO,GAAA,KAAQ,QAAA;AACpB,IAAA,OAAO,MAAM,GAAA,GAAM,GAAA;AAAA,OAAA,IAGd,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA,EAAG;AAEzB,IAAA,IAAI,GAAA,CAAI,CAAC,CAAA,KAAM,MAAA;AACX,MAAA,OAAO,IAAA;AAAA,SACN;AACD,MAAA,GAAA,CAAI,OAAA,CAAQ,SAAU,EAAA,EAAI;AACtB,QAAA,OAAA,CAAQ,IAAA,CAAK,aAAA,CAAc,EAAE,CAAC,CAAA;AAAA,MAClC,CAAC,CAAA;AACD,MAAA,OAAO,MAAM,OAAA,GAAU,GAAA;AAAA,IAC3B;AAAA,EACJ,CAAA,MAAA,IAES,eAAe,MAAA,EAAQ;AAE5B,IAAA,OAAA,GAAU,MAAA,CAAO,KAAK,GAAG,CAAA;AAEzB,IAAA,OAAA,CAAQ,OAAA,CAAQ,SAAU,GAAA,EAAK;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,GAAA,GAAM,IAAA;AAC3B,MAAA,MAAM,SAAA,GAAY,IAAI,GAAG,CAAA;AAEzB,MAAA,IAAI,SAAA,YAAqB,YAAY,SAAA,KAAc,MAAA;AAC/C,QAAA,YAAA,CAAa,KAAK,EAAE,CAAA;AAAA,WAAA,IACf,OAAO,SAAA,KAAc,QAAA;AAC1B,QAAA,YAAA,CAAa,IAAA,CAAK,MAAA,GAAS,GAAA,GAAM,SAAA,GAAY,GAAG,CAAA;AAAA,WAAA,IAC3C,OAAO,SAAA,KAAc,SAAA,IAAa,OAAO,SAAA,KAAc,YAAY,SAAA,KAAc,IAAA;AACtF,QAAA,YAAA,CAAa,IAAA,CAAK,SAAS,SAAS,CAAA;AAAA,WAAA,IAE/B,qBAAqB,MAAA,EAAQ;AAClC,QAAA,YAAA,CAAa,IAAA,CAAK,MAAA,GAAS,aAAA,CAAc,SAAS,CAAC,CAAA;AAAA,MACvD;AAAA,IACJ,CAAC,CAAA;AACD,IAAA,OAAO,MAAM,YAAA,GAAe,GAAA;AAAA,EAChC;AACA,EAAA,OAAO,EAAA;AACX;;;;"}
|
package/dist/index56.js
CHANGED
|
@@ -1,163 +1,284 @@
|
|
|
1
1
|
import { getLogger } from './index47.js';
|
|
2
|
+
import * as yaml from 'js-yaml';
|
|
3
|
+
import * as fs from 'fs/promises';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as os from 'node:os';
|
|
2
6
|
|
|
3
7
|
const create = (config) => {
|
|
4
8
|
const logger = getLogger();
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
9
|
+
const database = {
|
|
10
|
+
mappings: [],
|
|
11
|
+
tier1: [],
|
|
12
|
+
tier2: /* @__PURE__ */ new Map(),
|
|
13
|
+
tier3: [],
|
|
14
|
+
collisions: /* @__PURE__ */ new Map(),
|
|
15
|
+
commonTerms: new Set(config?.commonTerms ?? DEFAULT_COMMON_TERMS),
|
|
16
|
+
genericTerms: new Set(config?.genericTerms ?? DEFAULT_GENERIC_TERMS)
|
|
17
|
+
};
|
|
18
|
+
const findProtokolDirectories = async () => {
|
|
19
|
+
if (config?.protokollContextPaths) {
|
|
20
|
+
return config.protokollContextPaths;
|
|
21
|
+
}
|
|
22
|
+
const homeDir = os.homedir();
|
|
23
|
+
const primaryPath = path.join(homeDir, ".protokoll", "context");
|
|
24
|
+
const dirs = [];
|
|
25
|
+
try {
|
|
26
|
+
await fs.access(primaryPath);
|
|
27
|
+
dirs.push(primaryPath);
|
|
28
|
+
logger.debug(`Found protokoll context at: ${primaryPath}`);
|
|
29
|
+
} catch {
|
|
30
|
+
logger.debug(`No protokoll context found at: ${primaryPath}`);
|
|
31
|
+
}
|
|
32
|
+
return dirs;
|
|
33
|
+
};
|
|
34
|
+
const loadProjectsFromProtokoll = async () => {
|
|
35
|
+
logger.debug("Loading projects from protokoll context");
|
|
36
|
+
const contextDirs = await findProtokolDirectories();
|
|
37
|
+
if (contextDirs.length === 0) {
|
|
38
|
+
logger.warn("No protokoll context directories found");
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
const mappings = [];
|
|
42
|
+
for (const contextDir of contextDirs) {
|
|
43
|
+
const projectsDir = path.join(contextDir, "projects");
|
|
44
|
+
try {
|
|
45
|
+
const files = await fs.readdir(projectsDir);
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) continue;
|
|
48
|
+
try {
|
|
49
|
+
const content = await fs.readFile(path.join(projectsDir, file), "utf-8");
|
|
50
|
+
const parsed = yaml.load(content);
|
|
51
|
+
if (!parsed || !parsed.id || !parsed.name) {
|
|
52
|
+
logger.debug(`Skipping invalid project file: ${file}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (parsed.active === false) {
|
|
56
|
+
logger.debug(`Skipping inactive project: ${parsed.id}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (parsed.sounds_like && parsed.sounds_like.length > 0) {
|
|
60
|
+
for (const soundsLike of parsed.sounds_like) {
|
|
61
|
+
mappings.push({
|
|
62
|
+
soundsLike: soundsLike.toLowerCase(),
|
|
63
|
+
correctText: parsed.name,
|
|
64
|
+
entityType: "project",
|
|
65
|
+
entityId: parsed.id,
|
|
66
|
+
scopedToProjects: null,
|
|
67
|
+
// Will be determined by collision detection
|
|
68
|
+
collisionRisk: "none",
|
|
69
|
+
// Will be determined by collision detection
|
|
70
|
+
tier: 1
|
|
71
|
+
// Will be determined by collision detection
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
logger.debug(`Loaded ${parsed.sounds_like.length} sounds_like entries for project: ${parsed.id}`);
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.warn(`Failed to parse project file ${file}: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
logger.debug(`Could not read projects directory ${projectsDir}: ${error.message}`);
|
|
32
82
|
}
|
|
33
83
|
}
|
|
34
|
-
logger.
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
return true;
|
|
84
|
+
logger.info(`Loaded ${mappings.length} sounds_like mappings from protokoll projects`);
|
|
85
|
+
return mappings;
|
|
38
86
|
};
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return null;
|
|
87
|
+
const detectCollisions = (mappings) => {
|
|
88
|
+
const collisionMap = /* @__PURE__ */ new Map();
|
|
89
|
+
for (const mapping of mappings) {
|
|
90
|
+
const key = mapping.soundsLike.toLowerCase();
|
|
91
|
+
if (!collisionMap.has(key)) {
|
|
92
|
+
collisionMap.set(key, []);
|
|
93
|
+
}
|
|
94
|
+
collisionMap.get(key).push(mapping);
|
|
95
|
+
}
|
|
96
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const [soundsLike, conflictMappings] of collisionMap) {
|
|
98
|
+
if (conflictMappings.length > 1) {
|
|
99
|
+
collisions.set(soundsLike, {
|
|
100
|
+
soundsLike,
|
|
101
|
+
mappings: conflictMappings,
|
|
102
|
+
count: conflictMappings.length
|
|
103
|
+
});
|
|
104
|
+
logger.debug(`Collision detected for "${soundsLike}": ${conflictMappings.length} mappings`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
logger.info(`Detected ${collisions.size} collisions in sounds_like mappings`);
|
|
108
|
+
return collisions;
|
|
62
109
|
};
|
|
63
|
-
const
|
|
64
|
-
if (!
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const beforeText = surroundingText.substring(0, indexInText).trimEnd();
|
|
79
|
-
const isAtSentenceStart = beforeText.length === 0 || /[.!?]\s*$/.test(beforeText);
|
|
80
|
-
if (isAtSentenceStart) {
|
|
81
|
-
return "unknown";
|
|
82
|
-
}
|
|
83
|
-
return "proper-noun";
|
|
110
|
+
const classifyTier = (mapping) => {
|
|
111
|
+
if (!mapping.soundsLike) {
|
|
112
|
+
return 3;
|
|
113
|
+
}
|
|
114
|
+
const soundsLikeLower = mapping.soundsLike.toLowerCase();
|
|
115
|
+
if (database.genericTerms.has(soundsLikeLower)) {
|
|
116
|
+
return 3;
|
|
117
|
+
}
|
|
118
|
+
if (database.commonTerms.has(soundsLikeLower)) {
|
|
119
|
+
return 2;
|
|
120
|
+
}
|
|
121
|
+
if (database.collisions.has(soundsLikeLower)) {
|
|
122
|
+
return 2;
|
|
123
|
+
}
|
|
124
|
+
return 1;
|
|
84
125
|
};
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
126
|
+
const assignTiersAndCollisions = (mappings) => {
|
|
127
|
+
for (const mapping of mappings) {
|
|
128
|
+
mapping.tier = classifyTier(mapping);
|
|
129
|
+
if (database.collisions.has(mapping.soundsLike.toLowerCase())) {
|
|
130
|
+
mapping.collisionRisk = "high";
|
|
131
|
+
} else if (database.commonTerms.has(mapping.soundsLike.toLowerCase())) {
|
|
132
|
+
mapping.collisionRisk = "medium";
|
|
133
|
+
} else if (mapping.tier === 2) {
|
|
134
|
+
mapping.collisionRisk = "low";
|
|
135
|
+
} else {
|
|
136
|
+
mapping.collisionRisk = "none";
|
|
137
|
+
}
|
|
138
|
+
if (mapping.tier === 2 && mapping.entityType === "project") {
|
|
139
|
+
mapping.scopedToProjects = [mapping.entityId];
|
|
140
|
+
mapping.minConfidence = config?.tier2Confidence ?? 0.6;
|
|
141
|
+
}
|
|
142
|
+
logger.debug(
|
|
143
|
+
`Classified "${mapping.soundsLike}" → "${mapping.correctText}" (${mapping.entityType}:${mapping.entityId}) as Tier ${mapping.tier} (risk: ${mapping.collisionRisk})`
|
|
144
|
+
);
|
|
93
145
|
}
|
|
94
|
-
|
|
95
|
-
|
|
146
|
+
};
|
|
147
|
+
const organizeMappingsByTier = (mappings) => {
|
|
148
|
+
database.tier1 = [];
|
|
149
|
+
database.tier2 = /* @__PURE__ */ new Map();
|
|
150
|
+
database.tier3 = [];
|
|
151
|
+
for (const mapping of mappings) {
|
|
96
152
|
if (mapping.tier === 1) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (mapping.tier === 2) {
|
|
105
|
-
if (shouldApplyTier2(mapping, classification)) {
|
|
106
|
-
return {
|
|
107
|
-
shouldReplace: true,
|
|
108
|
-
mapping,
|
|
109
|
-
reason: `Tier 2 mapping (project: ${classification.project}, confidence: ${classification.confidence})`,
|
|
110
|
-
confidence: classification.confidence ?? 0.5
|
|
111
|
-
};
|
|
153
|
+
database.tier1.push(mapping);
|
|
154
|
+
} else if (mapping.tier === 2) {
|
|
155
|
+
if (mapping.entityType === "project") {
|
|
156
|
+
if (!database.tier2.has(mapping.entityId)) {
|
|
157
|
+
database.tier2.set(mapping.entityId, []);
|
|
158
|
+
}
|
|
159
|
+
database.tier2.get(mapping.entityId).push(mapping);
|
|
112
160
|
} else {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
161
|
+
if (!database.tier2.has("_generic")) {
|
|
162
|
+
database.tier2.set("_generic", []);
|
|
163
|
+
}
|
|
164
|
+
database.tier2.get("_generic").push(mapping);
|
|
118
165
|
}
|
|
166
|
+
} else {
|
|
167
|
+
database.tier3.push(mapping);
|
|
119
168
|
}
|
|
120
|
-
return {
|
|
121
|
-
shouldReplace: false,
|
|
122
|
-
reason: "Tier 3 mapping (too ambiguous)",
|
|
123
|
-
confidence: 1
|
|
124
|
-
};
|
|
125
169
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
classification
|
|
170
|
+
logger.info(
|
|
171
|
+
`Organized mappings: Tier 1=${database.tier1.length}, Tier 2=${Array.from(database.tier2.values()).reduce((sum, arr) => sum + arr.length, 0)}, Tier 3=${database.tier3.length}`
|
|
129
172
|
);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
173
|
+
};
|
|
174
|
+
const loadEntitiesFromContext = (ctx) => {
|
|
175
|
+
const mappings = [];
|
|
176
|
+
const addMappings = (name, entityId, entityType, soundsLike, active) => {
|
|
177
|
+
if (active === false) return;
|
|
178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
179
|
+
const push = (raw) => {
|
|
180
|
+
const key = raw.toLowerCase().trim();
|
|
181
|
+
if (!key || seen.has(key)) return;
|
|
182
|
+
seen.add(key);
|
|
183
|
+
mappings.push({
|
|
184
|
+
soundsLike: key,
|
|
185
|
+
correctText: name,
|
|
186
|
+
entityType,
|
|
187
|
+
entityId,
|
|
188
|
+
scopedToProjects: null,
|
|
189
|
+
collisionRisk: "none",
|
|
190
|
+
tier: 1
|
|
191
|
+
});
|
|
136
192
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (hint === "common-term") {
|
|
141
|
-
return {
|
|
142
|
-
shouldReplace: false,
|
|
143
|
-
reason: "Capitalization hint suggests common term",
|
|
144
|
-
confidence: 0.7
|
|
145
|
-
};
|
|
193
|
+
push(name);
|
|
194
|
+
for (const sl of soundsLike) {
|
|
195
|
+
push(sl);
|
|
146
196
|
}
|
|
147
|
-
}
|
|
148
|
-
return {
|
|
149
|
-
shouldReplace: false,
|
|
150
|
-
reason: "Collision could not be resolved",
|
|
151
|
-
confidence: 0.5
|
|
152
197
|
};
|
|
198
|
+
for (const project of ctx.getAllProjects()) {
|
|
199
|
+
addMappings(project.name, project.id, "project", project.sounds_like ?? [], project.active);
|
|
200
|
+
}
|
|
201
|
+
for (const person of ctx.getAllPeople()) {
|
|
202
|
+
addMappings(person.name, person.id, "person", person.sounds_like ?? []);
|
|
203
|
+
}
|
|
204
|
+
for (const term of ctx.getAllTerms()) {
|
|
205
|
+
addMappings(term.name, term.id, "term", term.sounds_like ?? []);
|
|
206
|
+
}
|
|
207
|
+
const projectCount = ctx.getAllProjects().filter((p) => p.active !== false).length;
|
|
208
|
+
const peopleCount = ctx.getAllPeople().length;
|
|
209
|
+
const termCount = ctx.getAllTerms().length;
|
|
210
|
+
logger.info(
|
|
211
|
+
`Loaded ${mappings.length} sounds_like mappings from context instance (${projectCount} projects, ${peopleCount} people, ${termCount} terms)`
|
|
212
|
+
);
|
|
213
|
+
return mappings;
|
|
214
|
+
};
|
|
215
|
+
const load = async () => {
|
|
216
|
+
logger.info("Loading sounds_like database");
|
|
217
|
+
let allMappings;
|
|
218
|
+
if (config?.contextInstance) {
|
|
219
|
+
allMappings = loadEntitiesFromContext(config.contextInstance);
|
|
220
|
+
} else {
|
|
221
|
+
const projectMappings = await loadProjectsFromProtokoll();
|
|
222
|
+
allMappings = projectMappings;
|
|
223
|
+
}
|
|
224
|
+
database.mappings = allMappings;
|
|
225
|
+
if (config?.detectCollisions !== false) {
|
|
226
|
+
database.collisions = detectCollisions(allMappings);
|
|
227
|
+
}
|
|
228
|
+
assignTiersAndCollisions(allMappings);
|
|
229
|
+
organizeMappingsByTier(allMappings);
|
|
230
|
+
logger.info(`Sounds_like database loaded: ${allMappings.length} total mappings`);
|
|
231
|
+
return database;
|
|
232
|
+
};
|
|
233
|
+
const getTier1Mappings = () => {
|
|
234
|
+
return database.tier1;
|
|
235
|
+
};
|
|
236
|
+
const getTier2MappingsForProject = (projectId) => {
|
|
237
|
+
const projectMappings = database.tier2.get(projectId) ?? [];
|
|
238
|
+
const genericMappings = database.tier2.get("_generic") ?? [];
|
|
239
|
+
return [...projectMappings, ...genericMappings];
|
|
240
|
+
};
|
|
241
|
+
const hasCollision = (soundsLike) => {
|
|
242
|
+
return database.collisions.has(soundsLike.toLowerCase());
|
|
243
|
+
};
|
|
244
|
+
const getCollision = (soundsLike) => {
|
|
245
|
+
return database.collisions.get(soundsLike.toLowerCase());
|
|
246
|
+
};
|
|
247
|
+
const getAllCollisions = () => {
|
|
248
|
+
return Array.from(database.collisions.values());
|
|
153
249
|
};
|
|
154
250
|
return {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
251
|
+
load,
|
|
252
|
+
getTier1Mappings,
|
|
253
|
+
getTier2MappingsForProject,
|
|
254
|
+
hasCollision,
|
|
255
|
+
getCollision,
|
|
256
|
+
getAllCollisions,
|
|
257
|
+
classifyTier
|
|
159
258
|
};
|
|
160
259
|
};
|
|
260
|
+
const DEFAULT_COMMON_TERMS = [
|
|
261
|
+
"protocol",
|
|
262
|
+
"observation",
|
|
263
|
+
"composition",
|
|
264
|
+
"gateway",
|
|
265
|
+
"service",
|
|
266
|
+
"system",
|
|
267
|
+
"platform"
|
|
268
|
+
];
|
|
269
|
+
const DEFAULT_GENERIC_TERMS = [
|
|
270
|
+
"meeting",
|
|
271
|
+
"update",
|
|
272
|
+
"work",
|
|
273
|
+
"project",
|
|
274
|
+
"task",
|
|
275
|
+
"issue",
|
|
276
|
+
"discussion",
|
|
277
|
+
"review",
|
|
278
|
+
"the",
|
|
279
|
+
"a",
|
|
280
|
+
"an"
|
|
281
|
+
];
|
|
161
282
|
|
|
162
283
|
export { create };
|
|
163
284
|
//# sourceMappingURL=index56.js.map
|
package/dist/index56.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index56.js","sources":["../src/util/collision-detector.ts"],"sourcesContent":["/**\n * Collision Detector\n *\n * Provides utilities for detecting and resolving collisions in sounds_like mappings.\n * Helps determine when it's safe to apply a replacement and when context is needed.\n *\n * Part of the simple-replace optimization (Phase 1).\n */\n\nimport * as Logging from '@/logging';\nimport { SoundsLikeMapping, Collision } from './sounds-like-database';\n\n/**\n * Classification result for an entity\n */\nexport interface Classification {\n /** Identified project ID */\n project?: string;\n\n /** Classification confidence (0-1) */\n confidence?: number;\n\n /** Additional classification metadata */\n [key: string]: any;\n}\n\n/**\n * Replacement decision\n */\nexport interface ReplacementDecision {\n /** Whether to apply the replacement */\n shouldReplace: boolean;\n\n /** The mapping to use (if shouldReplace is true) */\n mapping?: SoundsLikeMapping;\n\n /** Reason for the decision */\n reason: string;\n\n /** Confidence in this decision (0-1) */\n confidence: number;\n}\n\n/**\n * Context for collision resolution\n */\nexport interface CollisionContext {\n /** Classification of the transcription */\n classification: Classification;\n\n /** The sounds_like value being considered */\n soundsLike: string;\n\n /** Available mappings for this sounds_like */\n availableMappings: SoundsLikeMapping[];\n\n /** Surrounding text context (optional) */\n surroundingText?: string;\n}\n\n/**\n * Instance interface for collision detector\n */\nexport interface Instance {\n /**\n * Decide whether to apply a replacement given a collision context\n */\n decideReplacement(context: CollisionContext): ReplacementDecision;\n\n /**\n * Check if a Tier 2 mapping should be applied given classification\n */\n shouldApplyTier2(mapping: SoundsLikeMapping, classification: Classification): boolean;\n\n /**\n * Resolve a collision by selecting the best mapping\n */\n resolveCollision(collision: Collision, classification: Classification): SoundsLikeMapping | null;\n\n /**\n * Detect capitalization hints in context\n */\n detectCapitalizationHint(soundsLike: string, surroundingText: string): 'proper-noun' | 'common-term' | 'unknown';\n}\n\n/**\n * Configuration for collision detector\n */\nexport interface CollisionDetectorConfig {\n /** Minimum confidence for Tier 2 replacements (default: 0.6) */\n tier2MinConfidence?: number;\n\n /** High confidence threshold for aggressive replacement (default: 0.8) */\n tier2HighConfidence?: number;\n\n /** Enable capitalization hints (default: true) */\n useCapitalizationHints?: boolean;\n\n /** Enable surrounding text analysis (default: false, future feature) */\n useSurroundingText?: boolean;\n}\n\n/**\n * Create a collision detector instance\n */\nexport const create = (config?: CollisionDetectorConfig): Instance => {\n const logger = Logging.getLogger();\n\n const tier2MinConfidence = config?.tier2MinConfidence ?? 0.6;\n // const tier2HighConfidence = config?.tier2HighConfidence ?? 0.8; // Reserved for future use\n const useCapitalizationHints = config?.useCapitalizationHints ?? true;\n\n /**\n * Check if a Tier 2 mapping should be applied\n */\n const shouldApplyTier2 = (\n mapping: SoundsLikeMapping,\n classification: Classification\n ): boolean => {\n // Must be Tier 2\n if (mapping.tier !== 2) {\n return false;\n }\n\n // Check confidence threshold\n const confidence = classification.confidence ?? 0;\n const minConfidence = mapping.minConfidence ?? tier2MinConfidence;\n\n if (confidence < minConfidence) {\n logger.debug(\n `Skipping Tier 2 replacement for \"${mapping.soundsLike}\": ` +\n `confidence ${confidence} < ${minConfidence}`\n );\n return false;\n }\n\n // For project-scoped mappings, check if project matches\n if (mapping.scopedToProjects && mapping.scopedToProjects.length > 0) {\n const classifiedProject = classification.project;\n\n if (!classifiedProject) {\n logger.debug(\n `Skipping Tier 2 replacement for \"${mapping.soundsLike}\": ` +\n `no project in classification`\n );\n return false;\n }\n\n if (!mapping.scopedToProjects.includes(classifiedProject)) {\n logger.debug(\n `Skipping Tier 2 replacement for \"${mapping.soundsLike}\": ` +\n `project \"${classifiedProject}\" not in scope [${mapping.scopedToProjects.join(', ')}]`\n );\n return false;\n }\n }\n\n logger.debug(\n `Applying Tier 2 replacement for \"${mapping.soundsLike}\" → \"${mapping.correctText}\" ` +\n `(project: ${classification.project}, confidence: ${confidence})`\n );\n return true;\n };\n\n /**\n * Resolve a collision by selecting the best mapping\n */\n const resolveCollision = (\n collision: Collision,\n classification: Classification\n ): SoundsLikeMapping | null => {\n const { soundsLike, mappings } = collision;\n\n logger.debug(`Resolving collision for \"${soundsLike}\" (${mappings.length} candidates)`);\n\n // Filter to Tier 1 and applicable Tier 2 mappings\n const tier1Mappings = mappings.filter(m => m.tier === 1);\n const tier2Mappings = mappings.filter(m => m.tier === 2 && shouldApplyTier2(m, classification));\n\n // Prefer Tier 1 if available (always safe)\n if (tier1Mappings.length === 1) {\n logger.debug(`Resolved collision: using Tier 1 mapping \"${tier1Mappings[0].correctText}\"`);\n return tier1Mappings[0];\n }\n\n // If multiple Tier 1 mappings, this is ambiguous (shouldn't happen in practice)\n if (tier1Mappings.length > 1) {\n logger.warn(`Multiple Tier 1 mappings for \"${soundsLike}\", skipping replacement`);\n return null;\n }\n\n // Try Tier 2 mappings that match classification\n if (tier2Mappings.length === 1) {\n logger.debug(`Resolved collision: using Tier 2 mapping \"${tier2Mappings[0].correctText}\"`);\n return tier2Mappings[0];\n }\n\n // If multiple Tier 2 mappings match, this is ambiguous\n if (tier2Mappings.length > 1) {\n logger.debug(`Multiple Tier 2 mappings match for \"${soundsLike}\", skipping replacement`);\n return null;\n }\n\n // No applicable mappings\n logger.debug(`No applicable mappings for collision \"${soundsLike}\"`);\n return null;\n };\n\n /**\n * Detect capitalization hints in surrounding text\n */\n const detectCapitalizationHint = (\n soundsLike: string,\n surroundingText: string\n ): 'proper-noun' | 'common-term' | 'unknown' => {\n if (!useCapitalizationHints || !surroundingText) {\n return 'unknown';\n }\n\n // Find the sounds_like in the surrounding text\n const regex = new RegExp(`\\\\b${soundsLike}\\\\b`, 'i');\n const match = surroundingText.match(regex);\n\n if (!match) {\n return 'unknown';\n }\n\n const matchedText = match[0];\n\n // Check if it's capitalized\n const isCapitalized = matchedText[0] === matchedText[0].toUpperCase();\n\n if (!isCapitalized) {\n // Lowercase in text → likely common term\n return 'common-term';\n }\n\n // Capitalized - check if it's at sentence start\n const indexInText = surroundingText.indexOf(matchedText);\n\n // Look back for sentence boundaries\n const beforeText = surroundingText.substring(0, indexInText).trimEnd();\n const isAtSentenceStart = beforeText.length === 0 || /[.!?]\\s*$/.test(beforeText);\n\n if (isAtSentenceStart) {\n // Capitalized at sentence start → ambiguous\n return 'unknown';\n }\n\n // Capitalized mid-sentence → likely proper noun\n return 'proper-noun';\n };\n\n /**\n * Decide whether to apply a replacement\n */\n const decideReplacement = (context: CollisionContext): ReplacementDecision => {\n const { classification, soundsLike, availableMappings, surroundingText } = context;\n\n // No mappings available\n if (availableMappings.length === 0) {\n return {\n shouldReplace: false,\n reason: 'No mappings available',\n confidence: 1.0,\n };\n }\n\n // Single mapping - straightforward\n if (availableMappings.length === 1) {\n const mapping = availableMappings[0];\n\n // Tier 1: Always apply\n if (mapping.tier === 1) {\n return {\n shouldReplace: true,\n mapping,\n reason: 'Tier 1 mapping (always safe)',\n confidence: 1.0,\n };\n }\n\n // Tier 2: Check conditions\n if (mapping.tier === 2) {\n if (shouldApplyTier2(mapping, classification)) {\n return {\n shouldReplace: true,\n mapping,\n reason: `Tier 2 mapping (project: ${classification.project}, confidence: ${classification.confidence})`,\n confidence: classification.confidence ?? 0.5,\n };\n } else {\n return {\n shouldReplace: false,\n reason: 'Tier 2 conditions not met',\n confidence: 0.5,\n };\n }\n }\n\n // Tier 3: Skip\n return {\n shouldReplace: false,\n reason: 'Tier 3 mapping (too ambiguous)',\n confidence: 1.0,\n };\n }\n\n // Multiple mappings - collision scenario\n const resolvedMapping = resolveCollision(\n { soundsLike, mappings: availableMappings, count: availableMappings.length },\n classification\n );\n\n if (resolvedMapping) {\n return {\n shouldReplace: true,\n mapping: resolvedMapping,\n reason: `Collision resolved (${availableMappings.length} candidates)`,\n confidence: classification.confidence ?? 0.5,\n };\n }\n\n // Use capitalization hint as fallback (future enhancement)\n if (surroundingText && useCapitalizationHints) {\n const hint = detectCapitalizationHint(soundsLike, surroundingText);\n\n if (hint === 'common-term') {\n return {\n shouldReplace: false,\n reason: 'Capitalization hint suggests common term',\n confidence: 0.7,\n };\n }\n }\n\n // Could not resolve collision\n return {\n shouldReplace: false,\n reason: 'Collision could not be resolved',\n confidence: 0.5,\n };\n };\n\n return {\n decideReplacement,\n shouldApplyTier2,\n resolveCollision,\n detectCapitalizationHint,\n };\n};\n"],"names":["Logging.getLogger"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index56.js","sources":["../src/util/sounds-like-database.ts"],"sourcesContent":["/**\n * Sounds-Like Database\n *\n * Aggregates sounds_like mappings from multiple sources (projects, people, terms)\n * and provides efficient lookup and collision detection for entity correction.\n *\n * Part of the simple-replace optimization (Phase 1).\n */\n\nimport * as Logging from '@/logging';\nimport * as yaml from 'js-yaml';\nimport * as fs from 'fs/promises';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport type { ContextInstance } from '@redaksjon/context';\n\n/**\n * Represents a single sounds_like mapping entry\n */\nexport interface SoundsLikeMapping {\n /** What Whisper typically hears (e.g., \"protocol\", \"observation\") */\n soundsLike: string;\n\n /** Correct text to replace with (e.g., \"Protokoll\", \"Observasjon\") */\n correctText: string;\n\n /** Type of entity (project, person, or term) */\n entityType: 'project' | 'person' | 'term';\n\n /** Unique identifier for the entity */\n entityId: string;\n\n /** Only apply replacement in these project contexts (null = apply everywhere) */\n scopedToProjects?: string[] | null;\n\n /** Collision risk level: none (Tier 1), low/medium (Tier 2), high (Tier 3) */\n collisionRisk: 'none' | 'low' | 'medium' | 'high';\n\n /** Tier classification (1 = always safe, 2 = project-scoped, 3 = ambiguous) */\n tier: 1 | 2 | 3;\n\n /** Minimum confidence required for Tier 2 replacements */\n minConfidence?: number;\n}\n\n/**\n * Collision information for a sounds_like value\n */\nexport interface Collision {\n /** The sounds_like value that has collisions */\n soundsLike: string;\n\n /** All mappings that share this sounds_like value */\n mappings: SoundsLikeMapping[];\n\n /** Number of conflicting mappings */\n count: number;\n}\n\n/**\n * Database of sounds_like mappings with collision detection\n */\nexport interface SoundsLikeDatabase {\n /** All loaded mappings */\n mappings: SoundsLikeMapping[];\n\n /** Tier 1 mappings (always safe to apply) */\n tier1: SoundsLikeMapping[];\n\n /** Tier 2 mappings (require project-scoping) */\n tier2: Map<string, SoundsLikeMapping[]>; // Keyed by project ID\n\n /** Tier 3 mappings (too ambiguous, skip) */\n tier3: SoundsLikeMapping[];\n\n /** Detected collisions */\n collisions: Map<string, Collision>;\n\n /** Common terms that should not be replaced */\n commonTerms: Set<string>;\n\n /** Generic terms to always skip (Tier 3) */\n genericTerms: Set<string>;\n}\n\n/**\n * Configuration for the sounds-like database\n */\nexport interface DatabaseConfig {\n /** Protokoll context directories to load from */\n protokollContextPaths?: string[];\n\n /** Confidence threshold for Tier 2 replacements */\n tier2Confidence?: number;\n\n /** Enable collision detection */\n detectCollisions?: boolean;\n\n /** Custom common terms list */\n commonTerms?: string[];\n\n /** Custom generic terms list */\n genericTerms?: string[];\n\n /**\n * An already-loaded context instance. When provided, entity data is read\n * directly from it (covering projects, people, and terms) instead of\n * rescanning the filesystem.\n */\n contextInstance?: ContextInstance;\n}\n\n/**\n * Instance interface for the sounds-like database\n */\nexport interface Instance {\n /** Load all sounds_like mappings from sources */\n load(): Promise<SoundsLikeDatabase>;\n\n /** Get all Tier 1 (always safe) mappings */\n getTier1Mappings(): SoundsLikeMapping[];\n\n /** Get Tier 2 (project-scoped) mappings for a specific project */\n getTier2MappingsForProject(projectId: string): SoundsLikeMapping[];\n\n /** Check if a sounds_like value has collisions */\n hasCollision(soundsLike: string): boolean;\n\n /** Get collision info for a sounds_like value */\n getCollision(soundsLike: string): Collision | undefined;\n\n /** Get all collisions */\n getAllCollisions(): Collision[];\n\n /** Classify a mapping into a tier based on collision risk */\n classifyTier(mapping: Partial<SoundsLikeMapping>): 1 | 2 | 3;\n}\n\ninterface ProtokolProject {\n id: string;\n name: string;\n type: 'project';\n sounds_like?: string[];\n classification?: {\n context_type?: 'work' | 'personal' | 'mixed';\n };\n active?: boolean;\n}\n\n/**\n * Create a sounds-like database instance\n */\nexport const create = (config?: DatabaseConfig): Instance => {\n const logger = Logging.getLogger();\n\n const database: SoundsLikeDatabase = {\n mappings: [],\n tier1: [],\n tier2: new Map(),\n tier3: [],\n collisions: new Map(),\n commonTerms: new Set(config?.commonTerms ?? DEFAULT_COMMON_TERMS),\n genericTerms: new Set(config?.genericTerms ?? DEFAULT_GENERIC_TERMS),\n };\n\n /**\n * Find protokoll context directories\n */\n const findProtokolDirectories = async (): Promise<string[]> => {\n if (config?.protokollContextPaths) {\n return config.protokollContextPaths;\n }\n\n const homeDir = os.homedir();\n const primaryPath = path.join(homeDir, '.protokoll', 'context');\n\n const dirs: string[] = [];\n\n // Check primary protokoll directory\n try {\n await fs.access(primaryPath);\n dirs.push(primaryPath);\n logger.debug(`Found protokoll context at: ${primaryPath}`);\n } catch {\n logger.debug(`No protokoll context found at: ${primaryPath}`);\n }\n\n return dirs;\n };\n\n /**\n * Load projects from protokoll context\n */\n const loadProjectsFromProtokoll = async (): Promise<SoundsLikeMapping[]> => {\n logger.debug('Loading projects from protokoll context');\n\n const contextDirs = await findProtokolDirectories();\n\n if (contextDirs.length === 0) {\n logger.warn('No protokoll context directories found');\n return [];\n }\n\n const mappings: SoundsLikeMapping[] = [];\n\n for (const contextDir of contextDirs) {\n const projectsDir = path.join(contextDir, 'projects');\n\n try {\n const files = await fs.readdir(projectsDir);\n\n for (const file of files) {\n if (!file.endsWith('.yaml') && !file.endsWith('.yml')) continue;\n\n try {\n const content = await fs.readFile(path.join(projectsDir, file), 'utf-8');\n const parsed = yaml.load(content) as Partial<ProtokolProject>;\n\n if (!parsed || !parsed.id || !parsed.name) {\n logger.debug(`Skipping invalid project file: ${file}`);\n continue;\n }\n\n // Skip inactive projects\n if (parsed.active === false) {\n logger.debug(`Skipping inactive project: ${parsed.id}`);\n continue;\n }\n\n // Process sounds_like entries\n if (parsed.sounds_like && parsed.sounds_like.length > 0) {\n for (const soundsLike of parsed.sounds_like) {\n mappings.push({\n soundsLike: soundsLike.toLowerCase(),\n correctText: parsed.name,\n entityType: 'project',\n entityId: parsed.id,\n scopedToProjects: null, // Will be determined by collision detection\n collisionRisk: 'none', // Will be determined by collision detection\n tier: 1, // Will be determined by collision detection\n });\n }\n logger.debug(`Loaded ${parsed.sounds_like.length} sounds_like entries for project: ${parsed.id}`);\n }\n } catch (error: any) {\n logger.warn(`Failed to parse project file ${file}: ${error.message}`);\n }\n }\n } catch (error: any) {\n logger.debug(`Could not read projects directory ${projectsDir}: ${error.message}`);\n }\n }\n\n logger.info(`Loaded ${mappings.length} sounds_like mappings from protokoll projects`);\n return mappings;\n };\n\n /**\n * Detect collisions in mappings\n */\n const detectCollisions = (mappings: SoundsLikeMapping[]): Map<string, Collision> => {\n const collisionMap = new Map<string, SoundsLikeMapping[]>();\n\n // Group by sounds_like value (case-insensitive)\n for (const mapping of mappings) {\n const key = mapping.soundsLike.toLowerCase();\n if (!collisionMap.has(key)) {\n collisionMap.set(key, []);\n }\n collisionMap.get(key)!.push(mapping);\n }\n\n // Identify actual collisions (multiple mappings for same sounds_like)\n const collisions = new Map<string, Collision>();\n for (const [soundsLike, conflictMappings] of collisionMap) {\n if (conflictMappings.length > 1) {\n collisions.set(soundsLike, {\n soundsLike,\n mappings: conflictMappings,\n count: conflictMappings.length,\n });\n logger.debug(`Collision detected for \"${soundsLike}\": ${conflictMappings.length} mappings`);\n }\n }\n\n logger.info(`Detected ${collisions.size} collisions in sounds_like mappings`);\n return collisions;\n };\n\n /**\n * Classify a mapping into a tier based on collision risk\n */\n const classifyTier = (mapping: Partial<SoundsLikeMapping>): 1 | 2 | 3 => {\n if (!mapping.soundsLike) {\n return 3; // Invalid, treat as ambiguous\n }\n\n const soundsLikeLower = mapping.soundsLike.toLowerCase();\n\n // Tier 3: Generic terms (always skip)\n if (database.genericTerms.has(soundsLikeLower)) {\n return 3;\n }\n\n // Tier 2: Common terms (require project-scoping)\n if (database.commonTerms.has(soundsLikeLower)) {\n return 2;\n }\n\n // Tier 2: Has collision with other mappings\n if (database.collisions.has(soundsLikeLower)) {\n return 2;\n }\n\n // Tier 1: Unique, no collisions, not a common term\n return 1;\n };\n\n /**\n * Assign tiers and collision info to all mappings\n */\n const assignTiersAndCollisions = (mappings: SoundsLikeMapping[]): void => {\n for (const mapping of mappings) {\n // Classify tier\n mapping.tier = classifyTier(mapping);\n\n // Determine collision risk\n if (database.collisions.has(mapping.soundsLike.toLowerCase())) {\n mapping.collisionRisk = 'high';\n } else if (database.commonTerms.has(mapping.soundsLike.toLowerCase())) {\n mapping.collisionRisk = 'medium';\n } else if (mapping.tier === 2) {\n mapping.collisionRisk = 'low';\n } else {\n mapping.collisionRisk = 'none';\n }\n\n // Scope to projects for Tier 2\n if (mapping.tier === 2 && mapping.entityType === 'project') {\n mapping.scopedToProjects = [mapping.entityId];\n mapping.minConfidence = config?.tier2Confidence ?? 0.6;\n }\n\n logger.debug(\n `Classified \"${mapping.soundsLike}\" → \"${mapping.correctText}\" ` +\n `(${mapping.entityType}:${mapping.entityId}) as Tier ${mapping.tier} ` +\n `(risk: ${mapping.collisionRisk})`\n );\n }\n };\n\n /**\n * Organize mappings by tier\n */\n const organizeMappingsByTier = (mappings: SoundsLikeMapping[]): void => {\n database.tier1 = [];\n database.tier2 = new Map();\n database.tier3 = [];\n\n for (const mapping of mappings) {\n if (mapping.tier === 1) {\n database.tier1.push(mapping);\n } else if (mapping.tier === 2) {\n // Organize Tier 2 by project ID for efficient lookup\n if (mapping.entityType === 'project') {\n if (!database.tier2.has(mapping.entityId)) {\n database.tier2.set(mapping.entityId, []);\n }\n database.tier2.get(mapping.entityId)!.push(mapping);\n } else {\n // For non-project entities in Tier 2, add to a generic bucket\n if (!database.tier2.has('_generic')) {\n database.tier2.set('_generic', []);\n }\n database.tier2.get('_generic')!.push(mapping);\n }\n } else {\n database.tier3.push(mapping);\n }\n }\n\n logger.info(\n `Organized mappings: Tier 1=${database.tier1.length}, ` +\n `Tier 2=${Array.from(database.tier2.values()).reduce((sum, arr) => sum + arr.length, 0)}, ` +\n `Tier 3=${database.tier3.length}`\n );\n };\n\n /**\n * Load all sounds_like mappings from a ContextInstance (projects, people, terms).\n * Preferred over filesystem scanning when the context is already loaded.\n */\n const loadEntitiesFromContext = (ctx: ContextInstance): SoundsLikeMapping[] => {\n const mappings: SoundsLikeMapping[] = [];\n\n // Helper: emit one mapping per sounds_like value plus the canonical name itself.\n // Including the name means an entity spelled correctly in the transcript is still\n // recognised and associated as an entity reference.\n const addMappings = (\n name: string,\n entityId: string,\n entityType: 'project' | 'person' | 'term',\n soundsLike: string[],\n active?: boolean\n ) => {\n if (active === false) return;\n\n const seen = new Set<string>();\n const push = (raw: string) => {\n const key = raw.toLowerCase().trim();\n if (!key || seen.has(key)) return;\n seen.add(key);\n mappings.push({\n soundsLike: key,\n correctText: name,\n entityType,\n entityId,\n scopedToProjects: null,\n collisionRisk: 'none',\n tier: 1,\n });\n };\n\n // Always include the canonical name so exact spellings are matched\n push(name);\n for (const sl of soundsLike) {\n push(sl);\n }\n };\n\n // Projects\n for (const project of ctx.getAllProjects()) {\n addMappings(project.name, project.id, 'project', project.sounds_like ?? [], project.active);\n }\n\n // People\n for (const person of ctx.getAllPeople()) {\n addMappings(person.name, person.id, 'person', person.sounds_like ?? []);\n }\n\n // Terms\n for (const term of ctx.getAllTerms()) {\n addMappings(term.name, term.id, 'term', term.sounds_like ?? []);\n }\n\n const projectCount = ctx.getAllProjects().filter(p => p.active !== false).length;\n const peopleCount = ctx.getAllPeople().length;\n const termCount = ctx.getAllTerms().length;\n logger.info(\n `Loaded ${mappings.length} sounds_like mappings from context instance ` +\n `(${projectCount} projects, ${peopleCount} people, ${termCount} terms)`\n );\n\n return mappings;\n };\n\n /**\n * Load all sounds_like mappings\n */\n const load = async (): Promise<SoundsLikeDatabase> => {\n logger.info('Loading sounds_like database');\n\n let allMappings: SoundsLikeMapping[];\n\n if (config?.contextInstance) {\n // Prefer context instance - covers projects, people, and terms in one pass\n allMappings = loadEntitiesFromContext(config.contextInstance);\n } else {\n // Fall back to filesystem scanning (projects only)\n const projectMappings = await loadProjectsFromProtokoll();\n allMappings = projectMappings;\n }\n\n database.mappings = allMappings;\n\n // Detect collisions\n if (config?.detectCollisions !== false) {\n database.collisions = detectCollisions(allMappings);\n }\n\n // Assign tiers and collision info\n assignTiersAndCollisions(allMappings);\n\n // Organize by tier for efficient lookup\n organizeMappingsByTier(allMappings);\n\n logger.info(`Sounds_like database loaded: ${allMappings.length} total mappings`);\n\n return database;\n };\n\n /**\n * Get Tier 1 mappings (always safe)\n */\n const getTier1Mappings = (): SoundsLikeMapping[] => {\n return database.tier1;\n };\n\n /**\n * Get Tier 2 mappings for a specific project\n */\n const getTier2MappingsForProject = (projectId: string): SoundsLikeMapping[] => {\n const projectMappings = database.tier2.get(projectId) ?? [];\n const genericMappings = database.tier2.get('_generic') ?? [];\n return [...projectMappings, ...genericMappings];\n };\n\n /**\n * Check if a sounds_like value has collisions\n */\n const hasCollision = (soundsLike: string): boolean => {\n return database.collisions.has(soundsLike.toLowerCase());\n };\n\n /**\n * Get collision info\n */\n const getCollision = (soundsLike: string): Collision | undefined => {\n return database.collisions.get(soundsLike.toLowerCase());\n };\n\n /**\n * Get all collisions\n */\n const getAllCollisions = (): Collision[] => {\n return Array.from(database.collisions.values());\n };\n\n return {\n load,\n getTier1Mappings,\n getTier2MappingsForProject,\n hasCollision,\n getCollision,\n getAllCollisions,\n classifyTier,\n };\n};\n\n/**\n * Default common terms that indicate Tier 2 (project-scoped) replacements\n */\nconst DEFAULT_COMMON_TERMS = [\n 'protocol',\n 'observation',\n 'composition',\n 'gateway',\n 'service',\n 'system',\n 'platform',\n];\n\n/**\n * Default generic terms that should never be replaced (Tier 3)\n */\nconst DEFAULT_GENERIC_TERMS = [\n 'meeting',\n 'update',\n 'work',\n 'project',\n 'task',\n 'issue',\n 'discussion',\n 'review',\n 'the',\n 'a',\n 'an',\n];\n"],"names":["Logging.getLogger"],"mappings":"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"}
|