@redaksjon/protokoll-engine 0.1.8-dev.20260221045945.bbdc773 → 0.1.8

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/index55.js CHANGED
@@ -1,284 +1,151 @@
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';
6
-
7
- const create = (config) => {
8
- const logger = getLogger();
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}`);
1
+ function extractNameContext(transcript, name) {
2
+ const lowerTranscript = transcript.toLowerCase();
3
+ const lowerName = name.toLowerCase();
4
+ const index = lowerTranscript.indexOf(lowerName);
5
+ if (index === -1) {
6
+ return null;
7
+ }
8
+ const sentenceBoundary = /[.!?]/;
9
+ let startIndex = 0;
10
+ let boundariesFound = 0;
11
+ for (let i = index - 1; i >= 0; i--) {
12
+ if (sentenceBoundary.test(transcript[i])) {
13
+ boundariesFound++;
14
+ if (boundariesFound === 2) {
15
+ startIndex = i + 1;
16
+ break;
82
17
  }
83
18
  }
84
- logger.info(`Loaded ${mappings.length} sounds_like mappings from protokoll projects`);
85
- return mappings;
86
- };
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, []);
19
+ }
20
+ let endIndex = transcript.length;
21
+ boundariesFound = 0;
22
+ for (let i = index + name.length; i < transcript.length; i++) {
23
+ if (sentenceBoundary.test(transcript[i])) {
24
+ boundariesFound++;
25
+ if (boundariesFound === 2) {
26
+ endIndex = i + 1;
27
+ break;
93
28
  }
94
- collisionMap.get(key).push(mapping);
95
29
  }
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`);
30
+ }
31
+ let context = transcript.substring(startIndex, endIndex).trim();
32
+ if (context.length > 300) {
33
+ const midPoint = context.indexOf(name);
34
+ if (midPoint !== -1) {
35
+ let sentenceStart = midPoint;
36
+ let sentenceEnd = midPoint + name.length;
37
+ for (let i = midPoint - 1; i >= 0; i--) {
38
+ if (sentenceBoundary.test(context[i])) {
39
+ sentenceStart = i + 1;
40
+ break;
41
+ }
105
42
  }
106
- }
107
- logger.info(`Detected ${collisions.size} collisions in sounds_like mappings`);
108
- return collisions;
109
- };
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;
125
- };
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";
43
+ for (let i = midPoint + name.length; i < context.length; i++) {
44
+ if (sentenceBoundary.test(context[i])) {
45
+ sentenceEnd = i + 1;
46
+ break;
47
+ }
137
48
  }
138
- if (mapping.tier === 2 && mapping.entityType === "project") {
139
- mapping.scopedToProjects = [mapping.entityId];
140
- mapping.minConfidence = config?.tier2Confidence ?? 0.6;
49
+ context = context.substring(sentenceStart, sentenceEnd).trim();
50
+ } else {
51
+ context = context.substring(0, 300) + "...";
52
+ }
53
+ }
54
+ return context;
55
+ }
56
+ const create = (ctx) => ({
57
+ name: "lookup_person",
58
+ description: "Look up information about a person mentioned in the transcript. Use when you encounter a name that might need spelling verification or additional context.",
59
+ parameters: {
60
+ type: "object",
61
+ properties: {
62
+ name: {
63
+ type: "string",
64
+ description: "The name to look up (as heard in transcript)"
65
+ },
66
+ phonetic: {
67
+ type: "string",
68
+ description: "How the name sounds (for alias matching)"
141
69
  }
142
- logger.debug(
143
- `Classified "${mapping.soundsLike}" → "${mapping.correctText}" (${mapping.entityType}:${mapping.entityId}) as Tier ${mapping.tier} (risk: ${mapping.collisionRisk})`
144
- );
70
+ },
71
+ required: ["name"]
72
+ },
73
+ execute: async (args) => {
74
+ const context = ctx.contextInstance;
75
+ if (ctx.resolvedEntities?.has(args.name)) {
76
+ const resolvedName = ctx.resolvedEntities.get(args.name);
77
+ return {
78
+ success: true,
79
+ data: {
80
+ found: true,
81
+ suggestion: `Already resolved: use "${resolvedName}"`,
82
+ cached: true
83
+ }
84
+ };
145
85
  }
146
- };
147
- const organizeMappingsByTier = (mappings) => {
148
- database.tier1 = [];
149
- database.tier2 = /* @__PURE__ */ new Map();
150
- database.tier3 = [];
151
- for (const mapping of mappings) {
152
- if (mapping.tier === 1) {
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);
160
- } else {
161
- if (!database.tier2.has("_generic")) {
162
- database.tier2.set("_generic", []);
163
- }
164
- database.tier2.get("_generic").push(mapping);
86
+ const people = context.search(args.name);
87
+ const personMatches = people.filter((e) => e.type === "person");
88
+ if (personMatches.length > 0) {
89
+ return {
90
+ success: true,
91
+ data: {
92
+ found: true,
93
+ person: personMatches[0],
94
+ suggestion: `Use "${personMatches[0].name}" for correct spelling`
165
95
  }
166
- } else {
167
- database.tier3.push(mapping);
96
+ };
97
+ }
98
+ if (args.phonetic) {
99
+ const person = context.findBySoundsLike(args.phonetic);
100
+ if (person) {
101
+ return {
102
+ success: true,
103
+ data: {
104
+ found: true,
105
+ person,
106
+ suggestion: `"${args.phonetic}" likely refers to "${person.name}"`
107
+ }
108
+ };
168
109
  }
169
110
  }
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}`
172
- );
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
- });
192
- };
193
- push(name);
194
- for (const sl of soundsLike) {
195
- push(sl);
111
+ const allProjects = context.getAllProjects();
112
+ const projectOptions = allProjects.filter((p) => p.active !== false).map((p) => `${p.name}${p.description ? ` - ${p.description}` : ""}`);
113
+ const fileName = ctx.sourceFile.split("/").pop() || ctx.sourceFile;
114
+ const fileDate = ctx.audioDate.toLocaleString("en-US", {
115
+ weekday: "short",
116
+ year: "numeric",
117
+ month: "short",
118
+ day: "numeric",
119
+ hour: "2-digit",
120
+ minute: "2-digit"
121
+ });
122
+ const transcriptContext = extractNameContext(ctx.transcriptText, args.name);
123
+ const promptLines = [
124
+ `File: ${fileName}`,
125
+ `Date: ${fileDate}`,
126
+ "",
127
+ `Unknown person mentioned: "${args.name}"`
128
+ ];
129
+ if (transcriptContext) {
130
+ promptLines.push("");
131
+ promptLines.push("Context from transcript:");
132
+ promptLines.push(`"${transcriptContext}"`);
133
+ }
134
+ return {
135
+ success: true,
136
+ needsUserInput: true,
137
+ userPrompt: promptLines.join("\n"),
138
+ data: {
139
+ found: false,
140
+ clarificationType: "new_person",
141
+ term: args.name,
142
+ message: `Person "${args.name}" not found. Asking user for details.`,
143
+ knownProjects: allProjects.filter((p) => p.active !== false),
144
+ options: projectOptions
196
145
  }
197
146
  };
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());
249
- };
250
- return {
251
- load,
252
- getTier1Mappings,
253
- getTier2MappingsForProject,
254
- hasCollision,
255
- getCollision,
256
- getAllCollisions,
257
- classifyTier
258
- };
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
- ];
147
+ }
148
+ });
282
149
 
283
150
  export { create };
284
151
  //# sourceMappingURL=index55.js.map
@@ -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":"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"}
1
+ {"version":3,"file":"index55.js","sources":["../src/agentic/tools/lookup-person.ts"],"sourcesContent":["/**\n * Lookup Person Tool\n * \n * Looks up information about a person mentioned in the transcript.\n */\n\nimport { TranscriptionTool, ToolContext, ToolResult } from '../types';\n\n/**\n * Extract context from transcript around where a name is mentioned.\n * Returns approximately one sentence before and after the name mention.\n */\nfunction extractNameContext(transcript: string, name: string): string | null {\n // Case-insensitive search for the name\n const lowerTranscript = transcript.toLowerCase();\n const lowerName = name.toLowerCase();\n const index = lowerTranscript.indexOf(lowerName);\n \n if (index === -1) {\n return null;\n }\n \n // Define strong sentence boundaries (., !, ?)\n const sentenceBoundary = /[.!?]/;\n \n // Look backwards for the start (find the sentence boundary 1 sentence before)\n let startIndex = 0;\n let boundariesFound = 0;\n for (let i = index - 1; i >= 0; i--) {\n if (sentenceBoundary.test(transcript[i])) {\n boundariesFound++;\n // After finding first boundary (end of current sentence), \n // keep looking for the second (end of previous sentence)\n if (boundariesFound === 2) {\n // Start after this boundary\n startIndex = i + 1;\n break;\n }\n }\n }\n \n // Look forwards for the end (find sentence boundary 1 sentence after)\n let endIndex = transcript.length;\n boundariesFound = 0;\n for (let i = index + name.length; i < transcript.length; i++) {\n if (sentenceBoundary.test(transcript[i])) {\n boundariesFound++;\n // After finding first boundary (end of current sentence),\n // keep looking for the second (end of next sentence)\n if (boundariesFound === 2) {\n // Include this boundary\n endIndex = i + 1;\n break;\n }\n }\n }\n \n // Extract and clean up the context\n let context = transcript.substring(startIndex, endIndex).trim();\n \n // Limit length to avoid overwhelming the prompt (max ~300 chars)\n if (context.length > 300) {\n // Try to cut at a sentence boundary\n const midPoint = context.indexOf(name);\n if (midPoint !== -1) {\n // Keep the sentence with the name, trim around it\n let sentenceStart = midPoint;\n let sentenceEnd = midPoint + name.length;\n \n // Find sentence start\n for (let i = midPoint - 1; i >= 0; i--) {\n if (sentenceBoundary.test(context[i])) {\n sentenceStart = i + 1;\n break;\n }\n }\n \n // Find sentence end\n for (let i = midPoint + name.length; i < context.length; i++) {\n if (sentenceBoundary.test(context[i])) {\n sentenceEnd = i + 1;\n break;\n }\n }\n \n context = context.substring(sentenceStart, sentenceEnd).trim();\n } else {\n // Just truncate if name not found in extracted context\n context = context.substring(0, 300) + '...';\n }\n }\n \n return context;\n}\n\nexport const create = (ctx: ToolContext): TranscriptionTool => ({\n name: 'lookup_person',\n description: 'Look up information about a person mentioned in the transcript. Use when you encounter a name that might need spelling verification or additional context.',\n parameters: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The name to look up (as heard in transcript)',\n },\n phonetic: {\n type: 'string',\n description: 'How the name sounds (for alias matching)',\n },\n },\n required: ['name'],\n },\n execute: async (args: { name: string; phonetic?: string }): Promise<ToolResult> => {\n const context = ctx.contextInstance;\n \n // First, check if this person was already resolved in this session\n if (ctx.resolvedEntities?.has(args.name)) {\n const resolvedName = ctx.resolvedEntities.get(args.name);\n return {\n success: true,\n data: {\n found: true,\n suggestion: `Already resolved: use \"${resolvedName}\"`,\n cached: true,\n },\n };\n }\n \n // Try direct name search\n const people = context.search(args.name);\n const personMatches = people.filter(e => e.type === 'person');\n \n if (personMatches.length > 0) {\n return {\n success: true,\n data: {\n found: true,\n person: personMatches[0],\n suggestion: `Use \"${personMatches[0].name}\" for correct spelling`,\n },\n };\n }\n \n // Try phonetic match (sounds_like)\n if (args.phonetic) {\n const person = context.findBySoundsLike(args.phonetic);\n if (person) {\n return {\n success: true,\n data: {\n found: true,\n person,\n suggestion: `\"${args.phonetic}\" likely refers to \"${person.name}\"`,\n },\n };\n }\n }\n \n // Not found - always signal that we need user input\n // The executor will decide whether to actually prompt based on handler availability\n const allProjects = context.getAllProjects();\n const projectOptions = allProjects\n .filter(p => p.active !== false)\n .map(p => `${p.name}${p.description ? ` - ${p.description}` : ''}`);\n \n // Extract filename from sourceFile path for cleaner display\n const fileName = ctx.sourceFile.split('/').pop() || ctx.sourceFile;\n const fileDate = ctx.audioDate.toLocaleString('en-US', {\n weekday: 'short',\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n \n // Find context from transcript where the name is mentioned\n const transcriptContext = extractNameContext(ctx.transcriptText, args.name);\n \n const promptLines = [\n `File: ${fileName}`,\n `Date: ${fileDate}`,\n '',\n `Unknown person mentioned: \"${args.name}\"`,\n ];\n \n if (transcriptContext) {\n promptLines.push('');\n promptLines.push('Context from transcript:');\n promptLines.push(`\"${transcriptContext}\"`);\n }\n \n return {\n success: true,\n needsUserInput: true,\n userPrompt: promptLines.join('\\n'),\n data: {\n found: false,\n clarificationType: 'new_person',\n term: args.name,\n message: `Person \"${args.name}\" not found. Asking user for details.`,\n knownProjects: allProjects.filter(p => p.active !== false),\n options: projectOptions,\n },\n };\n },\n});\n\n"],"names":[],"mappings":"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"}