@redaksjon/protokoll 0.0.12 → 0.0.13

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.
Files changed (75) hide show
  1. package/.cursor/rules/definition-of-done.md +1 -0
  2. package/.cursor/rules/no-emoticons.md +26 -12
  3. package/README.md +483 -69
  4. package/dist/agentic/executor.js +473 -41
  5. package/dist/agentic/executor.js.map +1 -1
  6. package/dist/agentic/index.js.map +1 -1
  7. package/dist/agentic/tools/lookup-person.js +123 -4
  8. package/dist/agentic/tools/lookup-person.js.map +1 -1
  9. package/dist/agentic/tools/lookup-project.js +139 -22
  10. package/dist/agentic/tools/lookup-project.js.map +1 -1
  11. package/dist/agentic/tools/route-note.js +5 -1
  12. package/dist/agentic/tools/route-note.js.map +1 -1
  13. package/dist/arguments.js +6 -3
  14. package/dist/arguments.js.map +1 -1
  15. package/dist/cli/action.js +704 -0
  16. package/dist/cli/action.js.map +1 -0
  17. package/dist/cli/config.js +482 -0
  18. package/dist/cli/config.js.map +1 -0
  19. package/dist/cli/context.js +466 -0
  20. package/dist/cli/context.js.map +1 -0
  21. package/dist/cli/feedback.js +858 -0
  22. package/dist/cli/feedback.js.map +1 -0
  23. package/dist/cli/index.js +103 -0
  24. package/dist/cli/index.js.map +1 -0
  25. package/dist/cli/install.js +572 -0
  26. package/dist/cli/install.js.map +1 -0
  27. package/dist/cli/transcript.js +199 -0
  28. package/dist/cli/transcript.js.map +1 -0
  29. package/dist/constants.js +11 -4
  30. package/dist/constants.js.map +1 -1
  31. package/dist/context/index.js +25 -1
  32. package/dist/context/index.js.map +1 -1
  33. package/dist/context/storage.js +56 -3
  34. package/dist/context/storage.js.map +1 -1
  35. package/dist/interactive/handler.js +310 -9
  36. package/dist/interactive/handler.js.map +1 -1
  37. package/dist/main.js +11 -1
  38. package/dist/main.js.map +1 -1
  39. package/dist/output/index.js.map +1 -1
  40. package/dist/output/manager.js +46 -1
  41. package/dist/output/manager.js.map +1 -1
  42. package/dist/phases/complete.js +37 -2
  43. package/dist/phases/complete.js.map +1 -1
  44. package/dist/pipeline/orchestrator.js +104 -31
  45. package/dist/pipeline/orchestrator.js.map +1 -1
  46. package/dist/protokoll.js +68 -2
  47. package/dist/protokoll.js.map +1 -1
  48. package/dist/reasoning/client.js +83 -0
  49. package/dist/reasoning/client.js.map +1 -1
  50. package/dist/reasoning/index.js +1 -0
  51. package/dist/reasoning/index.js.map +1 -1
  52. package/dist/util/metadata.js.map +1 -1
  53. package/dist/util/sound.js +116 -0
  54. package/dist/util/sound.js.map +1 -0
  55. package/docs/duplicate-question-prevention.md +117 -0
  56. package/docs/examples.md +152 -0
  57. package/docs/interactive-context-example.md +92 -0
  58. package/docs/package-lock.json +6 -0
  59. package/docs/package.json +3 -1
  60. package/guide/action.md +375 -0
  61. package/guide/config.md +207 -0
  62. package/guide/configuration.md +82 -67
  63. package/guide/context-commands.md +574 -0
  64. package/guide/context-system.md +20 -7
  65. package/guide/development.md +106 -4
  66. package/guide/feedback.md +335 -0
  67. package/guide/index.md +100 -4
  68. package/guide/interactive.md +15 -14
  69. package/guide/quickstart.md +21 -7
  70. package/guide/reasoning.md +18 -4
  71. package/guide/routing.md +192 -97
  72. package/package.json +1 -1
  73. package/scripts/coverage-priority.mjs +323 -0
  74. package/tsconfig.tsbuildinfo +1 -1
  75. package/vitest.config.ts +5 -1
@@ -12,6 +12,8 @@ const create = (reasoning, ctx)=>{
12
12
  resolvedEntities: new Map(),
13
13
  confidence: 0
14
14
  };
15
+ // Make resolvedEntities available to tools so they can avoid re-asking
16
+ ctx.resolvedEntities = state.resolvedEntities;
15
17
  const toolsUsed = [];
16
18
  const contextChanges = [];
17
19
  let iterations = 0;
@@ -98,7 +100,7 @@ Remember: preserve ALL content, only fix transcription errors.`;
98
100
  logger.debug('Executing tool: %s', toolCall.name);
99
101
  toolsUsed.push(toolCall.name);
100
102
  try {
101
- var _result_data, _result_data1;
103
+ var _result_data, _result_data_routingDecision, _result_data1, _result_data2, _result_data_project_routing, _result_data_project, _result_data3;
102
104
  const result = await registry.executeTool(toolCall.name, toolCall.arguments);
103
105
  // Format result for the model
104
106
  const resultStr = JSON.stringify(result.data || {
@@ -112,40 +114,49 @@ Remember: preserve ALL content, only fix transcription errors.`;
112
114
  });
113
115
  logger.debug('Tool %s result: %s', toolCall.name, result.success ? 'success' : 'failed');
114
116
  // Handle results that need user input
115
- if (result.needsUserInput && ctx.interactiveMode && ctx.interactiveInstance) {
116
- var _result_data2, _result_data3, _result_data4, _result_data5;
117
+ // Check if interactive handler is available (not gated by interactiveMode flag)
118
+ if (result.needsUserInput && ctx.interactiveInstance) {
119
+ var _result_data4, _result_data5, _result_data6, _result_data7;
117
120
  logger.info('Interactive: %s requires clarification', toolCall.name);
118
121
  const termName = String(toolCall.arguments.name || toolCall.arguments.term || '');
119
122
  const clarification = await ctx.interactiveInstance.handleClarification({
120
- type: ((_result_data2 = result.data) === null || _result_data2 === void 0 ? void 0 : _result_data2.clarificationType) || 'general',
121
- term: ((_result_data3 = result.data) === null || _result_data3 === void 0 ? void 0 : _result_data3.term) || termName,
123
+ type: ((_result_data4 = result.data) === null || _result_data4 === void 0 ? void 0 : _result_data4.clarificationType) || 'general',
124
+ term: ((_result_data5 = result.data) === null || _result_data5 === void 0 ? void 0 : _result_data5.term) || termName,
122
125
  context: result.userPrompt || '',
123
- suggestion: (_result_data4 = result.data) === null || _result_data4 === void 0 ? void 0 : _result_data4.suggestion,
124
- options: (_result_data5 = result.data) === null || _result_data5 === void 0 ? void 0 : _result_data5.options
126
+ suggestion: (_result_data6 = result.data) === null || _result_data6 === void 0 ? void 0 : _result_data6.suggestion,
127
+ options: (_result_data7 = result.data) === null || _result_data7 === void 0 ? void 0 : _result_data7.options
125
128
  });
126
129
  if (clarification.response) {
127
- var _result_data6;
130
+ var _result_data8, _result_data9;
128
131
  state.resolvedEntities.set(termName, clarification.response);
129
132
  logger.info('Clarified: %s -> %s', termName, clarification.response);
130
- // Handle new project creation
131
- if (((_result_data6 = result.data) === null || _result_data6 === void 0 ? void 0 : _result_data6.clarificationType) === 'new_project' && clarification.response.trim()) {
132
- const projectPath = clarification.response.trim();
133
- // Only create if user provided a path (not just pressed Enter)
134
- if (projectPath && projectPath !== termName) {
135
- const projectId = termName.toLowerCase().replace(/\s+/g, '-');
133
+ // Handle new project/term wizard response
134
+ if (((_result_data8 = result.data) === null || _result_data8 === void 0 ? void 0 : _result_data8.clarificationType) === 'new_project' && clarification.additionalInfo) {
135
+ var _result_data10;
136
+ const wizardResult = clarification.additionalInfo;
137
+ const knownProjects = (_result_data10 = result.data) === null || _result_data10 === void 0 ? void 0 : _result_data10.knownProjects;
138
+ if (wizardResult.action === 'create') {
139
+ // CREATE NEW PROJECT
140
+ const projectName = wizardResult.projectName || termName;
141
+ const projectId = projectName.toLowerCase().replace(/\s+/g, '-');
142
+ const projectDestination = wizardResult.destination;
136
143
  const newProject = {
137
144
  id: projectId,
138
- name: termName,
145
+ name: projectName,
139
146
  type: 'project',
140
- description: `Auto-created from transcript mentioning "${termName}"`,
147
+ description: wizardResult.description || `Project for "${projectName}"`,
141
148
  classification: {
142
149
  context_type: 'work',
143
150
  explicit_phrases: [
144
- termName.toLowerCase()
145
- ]
151
+ termName.toLowerCase(),
152
+ projectName.toLowerCase()
153
+ ].filter((v, i, a)=>a.indexOf(v) === i)
146
154
  },
147
155
  routing: {
148
- destination: projectPath,
156
+ // Only include destination if explicitly provided - otherwise uses global default
157
+ ...projectDestination && {
158
+ destination: projectDestination
159
+ },
149
160
  structure: 'month',
150
161
  filename_options: [
151
162
  'date',
@@ -157,39 +168,431 @@ Remember: preserve ALL content, only fix transcription errors.`;
157
168
  };
158
169
  try {
159
170
  await ctx.contextInstance.saveEntity(newProject);
160
- logger.info('Created new project: %s -> %s', termName, projectPath);
161
- // Record the context change
171
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
172
+ logger.info('Created new project: %s%s', projectName, projectDestination ? ` -> ${projectDestination}` : ' (using default destination)');
162
173
  contextChanges.push({
163
174
  entityType: 'project',
164
175
  entityId: projectId,
165
- entityName: termName,
176
+ entityName: projectName,
166
177
  action: 'created',
167
178
  details: {
168
- destination: projectPath,
169
- routing: newProject.routing
179
+ ...projectDestination && {
180
+ destination: projectDestination
181
+ },
182
+ description: wizardResult.description,
183
+ triggeredByTerm: termName
170
184
  }
171
185
  });
172
- // Update routing to use new project
173
- state.routeDecision = {
174
- projectId,
175
- destination: {
176
- path: projectPath,
177
- structure: 'month'
186
+ // Update routing if destination was specified
187
+ if (projectDestination) {
188
+ state.routeDecision = {
189
+ projectId,
190
+ destination: {
191
+ path: projectDestination,
192
+ structure: 'month'
193
+ },
194
+ confidence: 1.0,
195
+ signals: [
196
+ {
197
+ type: 'explicit_phrase',
198
+ value: termName,
199
+ weight: 1.0
200
+ }
201
+ ],
202
+ reasoning: `User created new project "${projectName}" routing to ${projectDestination}`
203
+ };
204
+ }
205
+ } catch (error) {
206
+ logger.warn('Failed to save new project: %s', error);
207
+ }
208
+ } else if (wizardResult.action === 'link' && typeof wizardResult.linkedProjectIndex === 'number') {
209
+ // LINK TO EXISTING PROJECT
210
+ if (knownProjects && wizardResult.linkedProjectIndex < knownProjects.length) {
211
+ var _linkedProject_classification, _linkedProject_classification1, _linkedProject_routing, _linkedProject_routing1, _linkedProject_routing2;
212
+ const linkedProject = knownProjects[wizardResult.linkedProjectIndex];
213
+ // Add the term as an alias
214
+ const existingPhrases = ((_linkedProject_classification = linkedProject.classification) === null || _linkedProject_classification === void 0 ? void 0 : _linkedProject_classification.explicit_phrases) || [];
215
+ const updatedPhrases = [
216
+ ...existingPhrases,
217
+ termName.toLowerCase()
218
+ ].filter((v, i, a)=>a.indexOf(v) === i); // dedupe
219
+ const updatedProject = {
220
+ ...linkedProject,
221
+ type: 'project',
222
+ // Add term description to project notes if provided
223
+ notes: wizardResult.termDescription ? `${linkedProject.description || ''}\n\n${termName}: ${wizardResult.termDescription}`.trim() : linkedProject.description,
224
+ classification: {
225
+ ...linkedProject.classification,
226
+ context_type: ((_linkedProject_classification1 = linkedProject.classification) === null || _linkedProject_classification1 === void 0 ? void 0 : _linkedProject_classification1.context_type) || 'work',
227
+ explicit_phrases: updatedPhrases
178
228
  },
179
- confidence: 1.0,
180
- signals: [
181
- {
182
- type: 'explicit_phrase',
183
- value: termName,
184
- weight: 1.0
229
+ routing: {
230
+ // Preserve existing destination (or omit if not set)
231
+ ...((_linkedProject_routing = linkedProject.routing) === null || _linkedProject_routing === void 0 ? void 0 : _linkedProject_routing.destination) && {
232
+ destination: linkedProject.routing.destination
233
+ },
234
+ structure: ((_linkedProject_routing1 = linkedProject.routing) === null || _linkedProject_routing1 === void 0 ? void 0 : _linkedProject_routing1.structure) || 'month',
235
+ filename_options: ((_linkedProject_routing2 = linkedProject.routing) === null || _linkedProject_routing2 === void 0 ? void 0 : _linkedProject_routing2.filename_options) || [
236
+ 'date',
237
+ 'time'
238
+ ]
239
+ }
240
+ };
241
+ try {
242
+ var _linkedProject_routing3;
243
+ await ctx.contextInstance.saveEntity(updatedProject);
244
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
245
+ logger.info('Linked "%s" to project "%s"', termName, linkedProject.name);
246
+ contextChanges.push({
247
+ entityType: 'project',
248
+ entityId: linkedProject.id,
249
+ entityName: linkedProject.name,
250
+ action: 'updated',
251
+ details: {
252
+ addedAlias: termName,
253
+ termDescription: wizardResult.termDescription,
254
+ explicit_phrases: updatedPhrases
185
255
  }
186
- ],
187
- reasoning: `User created new project "${termName}" routing to ${projectPath}`
256
+ });
257
+ // Update routing to use the linked project
258
+ if ((_linkedProject_routing3 = linkedProject.routing) === null || _linkedProject_routing3 === void 0 ? void 0 : _linkedProject_routing3.destination) {
259
+ state.routeDecision = {
260
+ projectId: linkedProject.id,
261
+ destination: {
262
+ path: linkedProject.routing.destination,
263
+ structure: 'month'
264
+ },
265
+ confidence: 1.0,
266
+ signals: [
267
+ {
268
+ type: 'explicit_phrase',
269
+ value: termName,
270
+ weight: 1.0
271
+ }
272
+ ],
273
+ reasoning: `User linked "${termName}" to existing project "${linkedProject.name}"`
274
+ };
275
+ }
276
+ } catch (error) {
277
+ logger.warn('Failed to update project with alias: %s', error);
278
+ }
279
+ }
280
+ } else if (wizardResult.action === 'term') {
281
+ var _wizardResult_createdProject;
282
+ // CREATE NEW TERM ENTITY
283
+ const termNameFinal = wizardResult.termName || termName;
284
+ const termId = termNameFinal.toLowerCase().replace(/\s+/g, '-');
285
+ // Get project IDs from indices
286
+ const projectIds = [];
287
+ if (wizardResult.termProjects && knownProjects) {
288
+ for (const idx of wizardResult.termProjects){
289
+ if (idx >= 0 && idx < knownProjects.length) {
290
+ projectIds.push(knownProjects[idx].id);
291
+ }
292
+ }
293
+ }
294
+ // Handle nested project creation from term wizard
295
+ if (((_wizardResult_createdProject = wizardResult.createdProject) === null || _wizardResult_createdProject === void 0 ? void 0 : _wizardResult_createdProject.action) === 'create' && wizardResult.createdProject.projectName) {
296
+ const projectName = wizardResult.createdProject.projectName;
297
+ const projectId = projectName.toLowerCase().replace(/\s+/g, '-');
298
+ const projectDestination = wizardResult.createdProject.destination;
299
+ const newProject = {
300
+ id: projectId,
301
+ name: projectName,
302
+ type: 'project',
303
+ description: wizardResult.createdProject.description || `Project for "${projectName}"`,
304
+ classification: {
305
+ context_type: 'work',
306
+ explicit_phrases: [
307
+ projectName.toLowerCase(),
308
+ termNameFinal.toLowerCase()
309
+ ].filter((v, i, a)=>a.indexOf(v) === i)
310
+ },
311
+ routing: {
312
+ // Only include destination if explicitly provided - otherwise uses global default
313
+ ...projectDestination && {
314
+ destination: projectDestination
315
+ },
316
+ structure: 'month',
317
+ filename_options: [
318
+ 'date',
319
+ 'time',
320
+ 'subject'
321
+ ]
322
+ },
323
+ active: true
188
324
  };
325
+ try {
326
+ await ctx.contextInstance.saveEntity(newProject);
327
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
328
+ logger.info('Created new project from term wizard: %s%s', projectName, projectDestination ? ` -> ${projectDestination}` : ' (using default destination)');
329
+ // Add the new project to the projectIds list for term association
330
+ projectIds.push(projectId);
331
+ contextChanges.push({
332
+ entityType: 'project',
333
+ entityId: projectId,
334
+ entityName: projectName,
335
+ action: 'created',
336
+ details: {
337
+ ...projectDestination && {
338
+ destination: projectDestination
339
+ },
340
+ description: wizardResult.createdProject.description,
341
+ createdForTerm: termNameFinal
342
+ }
343
+ });
344
+ // Update routing to use the new project (if destination was specified)
345
+ if (projectDestination) {
346
+ state.routeDecision = {
347
+ projectId,
348
+ destination: {
349
+ path: projectDestination,
350
+ structure: 'month'
351
+ },
352
+ confidence: 1.0,
353
+ signals: [
354
+ {
355
+ type: 'explicit_phrase',
356
+ value: termNameFinal,
357
+ weight: 1.0
358
+ }
359
+ ],
360
+ reasoning: `User created project "${projectName}" for term "${termNameFinal}"`
361
+ };
362
+ }
363
+ } catch (error) {
364
+ logger.warn('Failed to save new project from term wizard: %s', error);
365
+ }
366
+ }
367
+ const newTerm = {
368
+ id: termId,
369
+ name: termNameFinal,
370
+ type: 'term',
371
+ expansion: wizardResult.termExpansion,
372
+ notes: wizardResult.termDescription,
373
+ projects: projectIds.length > 0 ? projectIds : undefined,
374
+ sounds_like: [
375
+ termName.toLowerCase()
376
+ ]
377
+ };
378
+ try {
379
+ await ctx.contextInstance.saveEntity(newTerm);
380
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
381
+ logger.info('Created new term: %s (projects: %s)', termNameFinal, projectIds.length > 0 ? projectIds.join(', ') : 'none');
382
+ contextChanges.push({
383
+ entityType: 'term',
384
+ entityId: termId,
385
+ entityName: termNameFinal,
386
+ action: 'created',
387
+ details: {
388
+ expansion: wizardResult.termExpansion,
389
+ projects: projectIds,
390
+ description: wizardResult.termDescription
391
+ }
392
+ });
393
+ // If term has associated projects and we haven't set routing yet, use the first one
394
+ if (projectIds.length > 0 && !state.routeDecision) {
395
+ // For newly created project, we already set routing above
396
+ // For existing projects, look them up
397
+ if (knownProjects) {
398
+ var _primaryProject_routing;
399
+ const primaryProject = knownProjects.find((p)=>p.id === projectIds[0]);
400
+ if (primaryProject === null || primaryProject === void 0 ? void 0 : (_primaryProject_routing = primaryProject.routing) === null || _primaryProject_routing === void 0 ? void 0 : _primaryProject_routing.destination) {
401
+ state.routeDecision = {
402
+ projectId: primaryProject.id,
403
+ destination: {
404
+ path: primaryProject.routing.destination,
405
+ structure: 'month'
406
+ },
407
+ confidence: 1.0,
408
+ signals: [
409
+ {
410
+ type: 'explicit_phrase',
411
+ value: termNameFinal,
412
+ weight: 1.0
413
+ }
414
+ ],
415
+ reasoning: `User created term "${termNameFinal}" associated with project "${primaryProject.name}"`
416
+ };
417
+ }
418
+ }
419
+ }
189
420
  } catch (error) {
190
- logger.warn('Failed to save new project: %s', error);
421
+ logger.warn('Failed to save new term: %s', error);
422
+ }
423
+ } else if (wizardResult.action === 'ignore' && wizardResult.ignoredTerm) {
424
+ // IGNORE - add term to ignore list so user won't be asked again
425
+ const ignoredTermName = wizardResult.ignoredTerm;
426
+ const ignoredId = ignoredTermName.toLowerCase().replace(/[^a-z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
427
+ const newIgnored = {
428
+ id: ignoredId,
429
+ name: ignoredTermName,
430
+ type: 'ignored',
431
+ ignoredAt: new Date().toISOString()
432
+ };
433
+ try {
434
+ await ctx.contextInstance.saveEntity(newIgnored);
435
+ await ctx.contextInstance.reload();
436
+ logger.info('Added to ignore list: %s', ignoredTermName);
437
+ contextChanges.push({
438
+ entityType: 'ignored',
439
+ entityId: ignoredId,
440
+ entityName: ignoredTermName,
441
+ action: 'created',
442
+ details: {
443
+ reason: 'User chose to ignore this term'
444
+ }
445
+ });
446
+ } catch (error) {
447
+ logger.warn('Failed to save ignored term: %s', error);
191
448
  }
192
449
  }
450
+ // 'skip' action - do nothing
451
+ }
452
+ // Handle new person wizard response
453
+ if (((_result_data9 = result.data) === null || _result_data9 === void 0 ? void 0 : _result_data9.clarificationType) === 'new_person' && clarification.additionalInfo) {
454
+ var _result_data11;
455
+ const personWizardResult = clarification.additionalInfo;
456
+ const knownProjects = (_result_data11 = result.data) === null || _result_data11 === void 0 ? void 0 : _result_data11.knownProjects;
457
+ if (personWizardResult.action === 'create') {
458
+ var _personWizardResult_createdProject;
459
+ let linkedProjectId;
460
+ // First, handle any nested project creation
461
+ if (((_personWizardResult_createdProject = personWizardResult.createdProject) === null || _personWizardResult_createdProject === void 0 ? void 0 : _personWizardResult_createdProject.action) === 'create' && personWizardResult.createdProject.projectName) {
462
+ const projectName = personWizardResult.createdProject.projectName;
463
+ const projectId = projectName.toLowerCase().replace(/\s+/g, '-');
464
+ const projectDestination = personWizardResult.createdProject.destination;
465
+ const newProject = {
466
+ id: projectId,
467
+ name: projectName,
468
+ type: 'project',
469
+ description: personWizardResult.createdProject.description || `Project for "${projectName}"`,
470
+ classification: {
471
+ context_type: 'work',
472
+ explicit_phrases: [
473
+ projectName.toLowerCase()
474
+ ]
475
+ },
476
+ routing: {
477
+ // Only include destination if explicitly provided - otherwise uses global default
478
+ ...projectDestination && {
479
+ destination: projectDestination
480
+ },
481
+ structure: 'month',
482
+ filename_options: [
483
+ 'date',
484
+ 'time',
485
+ 'subject'
486
+ ]
487
+ },
488
+ active: true
489
+ };
490
+ try {
491
+ await ctx.contextInstance.saveEntity(newProject);
492
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
493
+ logger.info('Created new project from person wizard: %s%s', projectName, projectDestination ? ` -> ${projectDestination}` : ' (using default destination)');
494
+ linkedProjectId = projectId;
495
+ contextChanges.push({
496
+ entityType: 'project',
497
+ entityId: projectId,
498
+ entityName: projectName,
499
+ action: 'created',
500
+ details: {
501
+ ...projectDestination && {
502
+ destination: projectDestination
503
+ },
504
+ description: personWizardResult.createdProject.description,
505
+ createdForPerson: personWizardResult.personName
506
+ }
507
+ });
508
+ // Update routing to use the new project (if destination was specified)
509
+ if (projectDestination) {
510
+ state.routeDecision = {
511
+ projectId,
512
+ destination: {
513
+ path: projectDestination,
514
+ structure: 'month'
515
+ },
516
+ confidence: 1.0,
517
+ signals: [
518
+ {
519
+ type: 'explicit_phrase',
520
+ value: projectName,
521
+ weight: 1.0
522
+ }
523
+ ],
524
+ reasoning: `User created project "${projectName}" for person "${personWizardResult.personName}"`
525
+ };
526
+ }
527
+ } catch (error) {
528
+ logger.warn('Failed to save new project from person wizard: %s', error);
529
+ }
530
+ } else if (typeof personWizardResult.linkedProjectIndex === 'number' && knownProjects) {
531
+ // User linked to existing project
532
+ if (personWizardResult.linkedProjectIndex < knownProjects.length) {
533
+ var _linkedProject_routing4;
534
+ const linkedProject = knownProjects[personWizardResult.linkedProjectIndex];
535
+ linkedProjectId = linkedProject.id;
536
+ // Update routing to use the linked project
537
+ if ((_linkedProject_routing4 = linkedProject.routing) === null || _linkedProject_routing4 === void 0 ? void 0 : _linkedProject_routing4.destination) {
538
+ state.routeDecision = {
539
+ projectId: linkedProject.id,
540
+ destination: {
541
+ path: linkedProject.routing.destination,
542
+ structure: 'month'
543
+ },
544
+ confidence: 1.0,
545
+ signals: [
546
+ {
547
+ type: 'explicit_phrase',
548
+ value: personWizardResult.personName || termName,
549
+ weight: 1.0
550
+ }
551
+ ],
552
+ reasoning: `User linked person "${personWizardResult.personName}" to project "${linkedProject.name}"`
553
+ };
554
+ }
555
+ }
556
+ }
557
+ // Now save the person
558
+ const personName = personWizardResult.personName || termName;
559
+ const personId = personName.toLowerCase().replace(/\s+/g, '-');
560
+ const newPerson = {
561
+ id: personId,
562
+ name: personName,
563
+ type: 'person',
564
+ organization: personWizardResult.organization,
565
+ notes: personWizardResult.notes,
566
+ projects: linkedProjectId ? [
567
+ linkedProjectId
568
+ ] : [],
569
+ sounds_like: [
570
+ termName.toLowerCase()
571
+ ]
572
+ };
573
+ try {
574
+ await ctx.contextInstance.saveEntity(newPerson);
575
+ await ctx.contextInstance.reload(); // Reload so subsequent searches find this entity
576
+ logger.info('Created new person: %s (org: %s, project: %s)', personName, personWizardResult.organization || 'none', linkedProjectId || 'none');
577
+ // Update resolved entities with correct name
578
+ state.resolvedEntities.set(termName, personName);
579
+ contextChanges.push({
580
+ entityType: 'person',
581
+ entityId: personId,
582
+ entityName: personName,
583
+ action: 'created',
584
+ details: {
585
+ organization: personWizardResult.organization,
586
+ linkedProject: linkedProjectId,
587
+ notes: personWizardResult.notes,
588
+ heardAs: termName
589
+ }
590
+ });
591
+ } catch (error) {
592
+ logger.warn('Failed to save new person: %s', error);
593
+ }
594
+ }
595
+ // 'skip' action - do nothing
193
596
  }
194
597
  }
195
598
  }
@@ -197,8 +600,37 @@ Remember: preserve ALL content, only fix transcription errors.`;
197
600
  if ((_result_data = result.data) === null || _result_data === void 0 ? void 0 : _result_data.person) {
198
601
  state.resolvedEntities.set(result.data.person.name, result.data.suggestion);
199
602
  }
200
- if ((_result_data1 = result.data) === null || _result_data1 === void 0 ? void 0 : _result_data1.destination) {
201
- state.routeDecision = result.data;
603
+ // Capture routing from route_note tool
604
+ if ((_result_data1 = result.data) === null || _result_data1 === void 0 ? void 0 : (_result_data_routingDecision = _result_data1.routingDecision) === null || _result_data_routingDecision === void 0 ? void 0 : _result_data_routingDecision.destination) {
605
+ const routingDecision = result.data.routingDecision;
606
+ state.routeDecision = {
607
+ projectId: routingDecision.projectId,
608
+ destination: routingDecision.destination,
609
+ confidence: routingDecision.confidence || 1.0,
610
+ signals: routingDecision.signals,
611
+ reasoning: routingDecision.reasoning || 'Determined by route_note tool'
612
+ };
613
+ }
614
+ // Capture routing from lookup_project when project has routing config
615
+ if (((_result_data2 = result.data) === null || _result_data2 === void 0 ? void 0 : _result_data2.found) && ((_result_data3 = result.data) === null || _result_data3 === void 0 ? void 0 : (_result_data_project = _result_data3.project) === null || _result_data_project === void 0 ? void 0 : (_result_data_project_routing = _result_data_project.routing) === null || _result_data_project_routing === void 0 ? void 0 : _result_data_project_routing.destination)) {
616
+ const project = result.data.project;
617
+ state.routeDecision = {
618
+ projectId: project.id,
619
+ destination: {
620
+ path: project.routing.destination,
621
+ structure: project.routing.structure || 'month'
622
+ },
623
+ confidence: 1.0,
624
+ signals: [
625
+ {
626
+ type: 'explicit_phrase',
627
+ value: project.name,
628
+ weight: 1.0
629
+ }
630
+ ],
631
+ reasoning: `Matched project "${project.name}" with routing to ${project.routing.destination}`
632
+ };
633
+ logger.debug('Captured routing from project lookup: %s -> %s', project.name, project.routing.destination);
202
634
  }
203
635
  } catch (error) {
204
636
  logger.error('Tool execution failed', {