@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
@@ -0,0 +1,858 @@
1
+ import { Command } from 'commander';
2
+ import * as readline from 'readline';
3
+ import * as fs from 'fs/promises';
4
+ import * as path from 'node:path';
5
+ import { create } from '../context/index.js';
6
+ import { create as create$1 } from '../reasoning/index.js';
7
+ import { getLogger } from '../logging.js';
8
+ import { extractTimestampFromFilename, slugifyTitle } from './action.js';
9
+ import { DEFAULT_MODEL } from '../constants.js';
10
+
11
+ // CLI output helper
12
+ const print = (text)=>process.stdout.write(text + '\n');
13
+ const FEEDBACK_TOOLS = [
14
+ {
15
+ name: 'correct_text',
16
+ description: 'Replace text in the transcript. Use this to fix misspellings, wrong terms, or incorrect names.',
17
+ parameters: {
18
+ find: {
19
+ type: 'string',
20
+ description: 'The text to find in the transcript',
21
+ required: true
22
+ },
23
+ replace: {
24
+ type: 'string',
25
+ description: 'The text to replace it with',
26
+ required: true
27
+ },
28
+ replace_all: {
29
+ type: 'boolean',
30
+ description: 'Replace all occurrences (default: true)'
31
+ }
32
+ }
33
+ },
34
+ {
35
+ name: 'add_term',
36
+ description: 'Add a new term to the context so it will be recognized in future transcripts. Use this when you learn about abbreviations, acronyms, or technical terms.',
37
+ parameters: {
38
+ term: {
39
+ type: 'string',
40
+ description: 'The correct term/abbreviation',
41
+ required: true
42
+ },
43
+ definition: {
44
+ type: 'string',
45
+ description: 'What the term means',
46
+ required: true
47
+ },
48
+ sounds_like: {
49
+ type: 'array',
50
+ items: {
51
+ type: 'string'
52
+ },
53
+ description: 'Phonetic variations that might be transcribed incorrectly (e.g., ["W C M P", "double u see em pee"])'
54
+ },
55
+ context: {
56
+ type: 'string',
57
+ description: 'Additional context about when this term is used'
58
+ }
59
+ }
60
+ },
61
+ {
62
+ name: 'add_person',
63
+ description: 'Add a new person to the context for future name recognition. Use this when you learn about people whose names were transcribed incorrectly.',
64
+ parameters: {
65
+ name: {
66
+ type: 'string',
67
+ description: 'The correct full name',
68
+ required: true
69
+ },
70
+ sounds_like: {
71
+ type: 'array',
72
+ items: {
73
+ type: 'string'
74
+ },
75
+ description: 'Phonetic variations (e.g., ["San Jay", "Sanjai", "Sanjey"])',
76
+ required: true
77
+ },
78
+ role: {
79
+ type: 'string',
80
+ description: 'Their role or title'
81
+ },
82
+ company: {
83
+ type: 'string',
84
+ description: 'Company they work for'
85
+ },
86
+ context: {
87
+ type: 'string',
88
+ description: 'Additional context about this person'
89
+ }
90
+ }
91
+ },
92
+ {
93
+ name: 'change_project',
94
+ description: 'Change the project assignment of this transcript. This updates metadata and may move the file to a new location based on project routing.',
95
+ parameters: {
96
+ project_id: {
97
+ type: 'string',
98
+ description: 'The project ID to assign',
99
+ required: true
100
+ }
101
+ }
102
+ },
103
+ {
104
+ name: 'change_title',
105
+ description: 'Change the title of this transcript. This updates the document heading and renames the file.',
106
+ parameters: {
107
+ new_title: {
108
+ type: 'string',
109
+ description: 'The new title for the transcript',
110
+ required: true
111
+ }
112
+ }
113
+ },
114
+ {
115
+ name: 'provide_help',
116
+ description: 'Provide helpful information to the user about what kinds of feedback they can give.',
117
+ parameters: {
118
+ topic: {
119
+ type: 'string',
120
+ description: 'The topic to help with',
121
+ enum: [
122
+ 'terms',
123
+ 'people',
124
+ 'projects',
125
+ 'corrections',
126
+ 'general'
127
+ ]
128
+ }
129
+ }
130
+ },
131
+ {
132
+ name: 'complete',
133
+ description: 'Call this when you have finished processing all the feedback and applied all necessary changes.',
134
+ parameters: {
135
+ summary: {
136
+ type: 'string',
137
+ description: 'A summary of what was done',
138
+ required: true
139
+ }
140
+ }
141
+ }
142
+ ];
143
+ /**
144
+ * Create a readline interface for user input
145
+ */ const createReadlineInterface = ()=>{
146
+ return readline.createInterface({
147
+ input: process.stdin,
148
+ output: process.stdout
149
+ });
150
+ };
151
+ /**
152
+ * Ask a question and get user input
153
+ */ const askQuestion = (rl, question)=>{
154
+ return new Promise((resolve)=>{
155
+ rl.question(question, (answer)=>{
156
+ resolve(answer.trim());
157
+ });
158
+ });
159
+ };
160
+ /**
161
+ * Execute a feedback tool
162
+ */ const executeTool = async (toolName, args, feedbackCtx)=>{
163
+ const logger = getLogger();
164
+ switch(toolName){
165
+ case 'correct_text':
166
+ {
167
+ const find = String(args.find);
168
+ const replace = String(args.replace);
169
+ const replaceAll = args.replace_all !== false;
170
+ if (!feedbackCtx.transcriptContent.includes(find)) {
171
+ return {
172
+ success: false,
173
+ message: `Text "${find}" not found in transcript.`
174
+ };
175
+ }
176
+ const occurrences = feedbackCtx.transcriptContent.split(find).length - 1;
177
+ if (replaceAll) {
178
+ feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.split(find).join(replace);
179
+ } else {
180
+ feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(find, replace);
181
+ }
182
+ const changeCount = replaceAll ? occurrences : 1;
183
+ feedbackCtx.changes.push({
184
+ type: 'text_correction',
185
+ description: `Replaced "${find}" with "${replace}" (${changeCount} occurrence${changeCount > 1 ? 's' : ''})`,
186
+ details: {
187
+ find,
188
+ replace,
189
+ count: changeCount
190
+ }
191
+ });
192
+ if (feedbackCtx.verbose) {
193
+ print(` ✓ Replaced "${find}" → "${replace}" (${changeCount}x)`);
194
+ }
195
+ return {
196
+ success: true,
197
+ message: `Replaced ${changeCount} occurrence(s) of "${find}" with "${replace}".`
198
+ };
199
+ }
200
+ case 'add_term':
201
+ {
202
+ const term = String(args.term);
203
+ const definition = String(args.definition);
204
+ const soundsLike = args.sounds_like;
205
+ const termContext = args.context;
206
+ // Generate ID from term
207
+ const id = term.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
208
+ // Check if term already exists
209
+ const existing = feedbackCtx.context.getTerm(id);
210
+ if (existing) {
211
+ return {
212
+ success: false,
213
+ message: `Term "${term}" already exists in context.`
214
+ };
215
+ }
216
+ const newTerm = {
217
+ id,
218
+ name: term,
219
+ type: 'term',
220
+ expansion: definition,
221
+ sounds_like: soundsLike,
222
+ domain: termContext
223
+ };
224
+ if (!feedbackCtx.dryRun) {
225
+ await feedbackCtx.context.saveEntity(newTerm);
226
+ }
227
+ feedbackCtx.changes.push({
228
+ type: 'term_added',
229
+ description: `Added term "${term}" to context`,
230
+ details: {
231
+ term,
232
+ definition,
233
+ sounds_like: soundsLike
234
+ }
235
+ });
236
+ if (feedbackCtx.verbose) {
237
+ print(` ✓ Added term: ${term} = "${definition}"`);
238
+ if (soundsLike === null || soundsLike === void 0 ? void 0 : soundsLike.length) {
239
+ print(` sounds_like: ${soundsLike.join(', ')}`);
240
+ }
241
+ }
242
+ return {
243
+ success: true,
244
+ message: `Added term "${term}" to context. It will be recognized in future transcripts.`,
245
+ data: {
246
+ id,
247
+ term,
248
+ definition
249
+ }
250
+ };
251
+ }
252
+ case 'add_person':
253
+ {
254
+ const name = String(args.name);
255
+ const soundsLike = args.sounds_like;
256
+ const role = args.role;
257
+ const company = args.company;
258
+ const personContext = args.context;
259
+ // Generate ID from name
260
+ const id = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
261
+ // Check if person already exists
262
+ const existing = feedbackCtx.context.getPerson(id);
263
+ if (existing) {
264
+ return {
265
+ success: false,
266
+ message: `Person "${name}" already exists in context.`
267
+ };
268
+ }
269
+ const newPerson = {
270
+ id,
271
+ name,
272
+ type: 'person',
273
+ sounds_like: soundsLike,
274
+ role,
275
+ company,
276
+ context: personContext
277
+ };
278
+ if (!feedbackCtx.dryRun) {
279
+ await feedbackCtx.context.saveEntity(newPerson);
280
+ }
281
+ feedbackCtx.changes.push({
282
+ type: 'person_added',
283
+ description: `Added person "${name}" to context`,
284
+ details: {
285
+ name,
286
+ sounds_like: soundsLike,
287
+ role,
288
+ company
289
+ }
290
+ });
291
+ if (feedbackCtx.verbose) {
292
+ print(` ✓ Added person: ${name}`);
293
+ print(` sounds_like: ${soundsLike.join(', ')}`);
294
+ if (role) print(` role: ${role}`);
295
+ if (company) print(` company: ${company}`);
296
+ }
297
+ return {
298
+ success: true,
299
+ message: `Added person "${name}" to context. Their name will be recognized in future transcripts.`,
300
+ data: {
301
+ id,
302
+ name,
303
+ sounds_like: soundsLike
304
+ }
305
+ };
306
+ }
307
+ case 'change_project':
308
+ {
309
+ var _project_routing;
310
+ const projectId = String(args.project_id);
311
+ // Find the project
312
+ const project = feedbackCtx.context.getProject(projectId);
313
+ if (!project) {
314
+ // List available projects
315
+ const available = feedbackCtx.context.getAllProjects().map((p)=>p.id);
316
+ return {
317
+ success: false,
318
+ message: `Project "${projectId}" not found. Available projects: ${available.join(', ')}`
319
+ };
320
+ }
321
+ // Update metadata in transcript
322
+ const metadataRegex = /\*\*Project\*\*: .+/;
323
+ const projectIdRegex = /\*\*Project ID\*\*: `.+`/;
324
+ if (metadataRegex.test(feedbackCtx.transcriptContent)) {
325
+ feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(metadataRegex, `**Project**: ${project.name}`);
326
+ }
327
+ if (projectIdRegex.test(feedbackCtx.transcriptContent)) {
328
+ feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(projectIdRegex, `**Project ID**: \`${project.id}\``);
329
+ }
330
+ feedbackCtx.changes.push({
331
+ type: 'project_changed',
332
+ description: `Changed project to "${project.name}" (${project.id})`,
333
+ details: {
334
+ project_id: projectId,
335
+ project_name: project.name,
336
+ routing: project.routing
337
+ }
338
+ });
339
+ if (feedbackCtx.verbose) {
340
+ var _project_routing1;
341
+ print(` ✓ Changed project to: ${project.name} (${project.id})`);
342
+ if ((_project_routing1 = project.routing) === null || _project_routing1 === void 0 ? void 0 : _project_routing1.destination) {
343
+ print(` New destination: ${project.routing.destination}`);
344
+ }
345
+ }
346
+ return {
347
+ success: true,
348
+ message: `Changed project to "${project.name}". The transcript metadata has been updated.`,
349
+ data: {
350
+ project_id: projectId,
351
+ destination: (_project_routing = project.routing) === null || _project_routing === void 0 ? void 0 : _project_routing.destination
352
+ }
353
+ };
354
+ }
355
+ case 'change_title':
356
+ {
357
+ const newTitle = String(args.new_title);
358
+ // Update title in transcript (first # heading)
359
+ const titleRegex = /^# .+$/m;
360
+ if (titleRegex.test(feedbackCtx.transcriptContent)) {
361
+ feedbackCtx.transcriptContent = feedbackCtx.transcriptContent.replace(titleRegex, `# ${newTitle}`);
362
+ }
363
+ feedbackCtx.changes.push({
364
+ type: 'title_changed',
365
+ description: `Changed title to "${newTitle}"`,
366
+ details: {
367
+ new_title: newTitle,
368
+ slug: slugifyTitle(newTitle)
369
+ }
370
+ });
371
+ if (feedbackCtx.verbose) {
372
+ print(` ✓ Changed title to: ${newTitle}`);
373
+ }
374
+ return {
375
+ success: true,
376
+ message: `Changed title to "${newTitle}". The file will be renamed accordingly.`,
377
+ data: {
378
+ new_title: newTitle
379
+ }
380
+ };
381
+ }
382
+ case 'provide_help':
383
+ {
384
+ const topic = String(args.topic || 'general');
385
+ let helpText = '';
386
+ switch(topic){
387
+ case 'terms':
388
+ helpText = `
389
+ **Term Corrections**
390
+
391
+ You can teach me about abbreviations, acronyms, and technical terms:
392
+
393
+ - "Everywhere it says WCMP, that should be WCNP - Walmart's Native Cloud Platform"
394
+ - "YB should be spelled Wibey"
395
+ - "API should be written as A.P.I. in this context"
396
+
397
+ I'll:
398
+ 1. Fix the term in this transcript
399
+ 2. Add it to my vocabulary for future transcripts
400
+ `;
401
+ break;
402
+ case 'people':
403
+ helpText = `
404
+ **Name Corrections**
405
+
406
+ You can teach me about people whose names were transcribed incorrectly:
407
+
408
+ - "San Jay Grouper is actually Sanjay Gupta"
409
+ - "Priya was transcribed as 'pre a' - please fix"
410
+ - "Marie should be spelled Mari (without the e)"
411
+
412
+ I'll:
413
+ 1. Fix the name everywhere in this transcript
414
+ 2. Remember how the name sounds for future transcripts
415
+ `;
416
+ break;
417
+ case 'projects':
418
+ helpText = `
419
+ **Project Assignment**
420
+
421
+ You can tell me if a transcript belongs to a different project:
422
+
423
+ - "This should be in the Quantum Readiness project"
424
+ - "Move this to the client-alpha project"
425
+ - "This was misclassified - it's a personal note, not work"
426
+
427
+ I'll:
428
+ 1. Update the project metadata
429
+ 2. Move the file to the project's configured location
430
+ `;
431
+ break;
432
+ case 'corrections':
433
+ helpText = `
434
+ **General Corrections**
435
+
436
+ You can ask me to fix any text in the transcript:
437
+
438
+ - "Change 'gonna' to 'going to' everywhere"
439
+ - "The date mentioned should be January 15th, not January 5th"
440
+ - "Remove the paragraph about lunch - that was a tangent"
441
+
442
+ I'll make the corrections while preserving the rest of the transcript.
443
+ `;
444
+ break;
445
+ default:
446
+ helpText = `
447
+ **What I Can Help With**
448
+
449
+ I can process your feedback to:
450
+
451
+ 1. **Fix Terms & Abbreviations**: "WCMP should be WCNP"
452
+ 2. **Correct Names**: "San Jay Grouper is Sanjay Gupta"
453
+ 3. **Change Projects**: "This belongs in the Quantum project"
454
+ 4. **Update Title**: "Change the title to 'Q1 Planning Session'"
455
+ 5. **General Corrections**: "Replace X with Y everywhere"
456
+
457
+ Just describe what's wrong in natural language, and I'll figure out what to do!
458
+
459
+ Ask about specific topics:
460
+ - "How do I correct terms?"
461
+ - "How do I fix names?"
462
+ - "How do I change the project?"
463
+ `;
464
+ }
465
+ print(helpText);
466
+ return {
467
+ success: true,
468
+ message: helpText
469
+ };
470
+ }
471
+ case 'complete':
472
+ {
473
+ const summary = String(args.summary);
474
+ return {
475
+ success: true,
476
+ message: summary,
477
+ data: {
478
+ complete: true
479
+ }
480
+ };
481
+ }
482
+ default:
483
+ logger.warn('Unknown tool: %s', toolName);
484
+ return {
485
+ success: false,
486
+ message: `Unknown tool: ${toolName}`
487
+ };
488
+ }
489
+ };
490
+ /**
491
+ * Build the system prompt for the feedback agent
492
+ */ const buildFeedbackSystemPrompt = (transcriptPreview, availableProjects)=>{
493
+ const toolDescriptions = FEEDBACK_TOOLS.map((t)=>`- ${t.name}: ${t.description}`).join('\n');
494
+ return `You are an intelligent feedback processor for a transcription system. Your job is to understand user feedback about transcripts and take appropriate actions.
495
+
496
+ ## Current Transcript Preview
497
+ ${transcriptPreview.substring(0, 1000)}${transcriptPreview.length > 1000 ? '...' : ''}
498
+
499
+ ## Available Projects
500
+ ${availableProjects.length > 0 ? availableProjects.join(', ') : '(no projects configured)'}
501
+
502
+ ## Available Tools
503
+ ${toolDescriptions}
504
+
505
+ ## How to Process Feedback
506
+
507
+ 1. **Understand the feedback**: What is the user asking for?
508
+ 2. **Identify actions**: What tools do you need to use?
509
+ 3. **Execute in order**:
510
+ - First, make text corrections (correct_text)
511
+ - Then, add context entities (add_term, add_person)
512
+ - Finally, change metadata if needed (change_project, change_title)
513
+ 4. **Summarize**: Call 'complete' with a summary when done
514
+
515
+ ## Important Rules
516
+
517
+ - If the user asks for help or seems unsure, use provide_help first
518
+ - For name corrections: BOTH fix the text AND add the person to context
519
+ - For term corrections: BOTH fix the text AND add the term to context
520
+ - When fixing names/terms, use correct_text with replace_all=true
521
+ - Be thorough - if "San Jay Grouper" should be "Sanjay Gupta", also consider variations like "San jay", "Sanjay Grouper", etc.
522
+ - Always call 'complete' when finished, with a summary of what you did
523
+
524
+ ## Example Interactions
525
+
526
+ User: "YB should be Wibey"
527
+ → Use correct_text to replace "YB" with "Wibey"
528
+ → Use add_term to add "Wibey" with sounds_like ["YB", "Y B"]
529
+ → Use complete to summarize
530
+
531
+ User: "San Jay Grouper is actually Sanjay Gupta"
532
+ → Use correct_text to replace "San Jay Grouper" with "Sanjay Gupta"
533
+ → Use correct_text to replace any other variations like "San jay Grouper"
534
+ → Use add_person to add "Sanjay Gupta" with sounds_like ["San Jay Grouper", "Sanjay Grouper"]
535
+ → Use complete to summarize
536
+
537
+ User: "This should be in the Quantum Readiness project"
538
+ → Use change_project with project_id matching "Quantum Readiness" or similar
539
+ → Use complete to summarize
540
+
541
+ Respond with tool calls to process the feedback.`;
542
+ };
543
+ /**
544
+ * Process feedback using the agentic model
545
+ */ const processFeedback = async (feedback, feedbackCtx, reasoning)=>{
546
+ const logger = getLogger();
547
+ // Get available projects
548
+ const projects = feedbackCtx.context.getAllProjects().map((p)=>`${p.id} (${p.name})`);
549
+ // Build the prompt
550
+ const systemPrompt = buildFeedbackSystemPrompt(feedbackCtx.transcriptContent, projects);
551
+ // Convert tools to OpenAI format
552
+ const tools = FEEDBACK_TOOLS.map((t)=>({
553
+ type: 'function',
554
+ function: {
555
+ name: t.name,
556
+ description: t.description,
557
+ parameters: {
558
+ type: 'object',
559
+ properties: Object.fromEntries(Object.entries(t.parameters).map(([key, param])=>[
560
+ key,
561
+ {
562
+ type: param.type,
563
+ description: param.description,
564
+ ...param.enum ? {
565
+ enum: param.enum
566
+ } : {},
567
+ ...param.items ? {
568
+ items: param.items
569
+ } : {}
570
+ }
571
+ ])),
572
+ required: Object.entries(t.parameters).filter(([_, p])=>p.required).map(([key])=>key)
573
+ }
574
+ }
575
+ }));
576
+ // Process with reasoning model
577
+ let iterations = 0;
578
+ const maxIterations = 10;
579
+ const conversationHistory = [
580
+ {
581
+ role: 'system',
582
+ content: systemPrompt
583
+ },
584
+ {
585
+ role: 'user',
586
+ content: feedback
587
+ }
588
+ ];
589
+ while(iterations < maxIterations){
590
+ iterations++;
591
+ logger.debug('Feedback processing iteration %d', iterations);
592
+ try {
593
+ const response = await reasoning.completeWithTools({
594
+ messages: conversationHistory,
595
+ tools
596
+ });
597
+ // Check for tool calls
598
+ if (response.tool_calls && response.tool_calls.length > 0) {
599
+ // Add assistant message with tool calls
600
+ conversationHistory.push({
601
+ role: 'assistant',
602
+ content: response.content || '',
603
+ tool_calls: response.tool_calls.map((tc)=>({
604
+ id: tc.id,
605
+ function: {
606
+ name: tc.function.name,
607
+ arguments: tc.function.arguments
608
+ }
609
+ }))
610
+ });
611
+ // Execute each tool call
612
+ for (const toolCall of response.tool_calls){
613
+ const toolName = toolCall.function.name;
614
+ let args;
615
+ try {
616
+ args = JSON.parse(toolCall.function.arguments);
617
+ } catch {
618
+ args = {};
619
+ }
620
+ if (feedbackCtx.verbose) {
621
+ print(`\n[Executing: ${toolName}]`);
622
+ }
623
+ const result = await executeTool(toolName, args, feedbackCtx);
624
+ // Add tool result to conversation
625
+ conversationHistory.push({
626
+ role: 'tool',
627
+ content: JSON.stringify(result),
628
+ tool_call_id: toolCall.id
629
+ });
630
+ // Check if complete
631
+ if (toolName === 'complete') {
632
+ if (feedbackCtx.verbose) {
633
+ print(`\n${result.message}`);
634
+ }
635
+ return;
636
+ }
637
+ }
638
+ } else {
639
+ // No tool calls - model is done or confused
640
+ if (response.content) {
641
+ print(`\n${response.content}`);
642
+ }
643
+ return;
644
+ }
645
+ } catch (error) {
646
+ logger.error('Error during feedback processing', {
647
+ error
648
+ });
649
+ throw error;
650
+ }
651
+ }
652
+ logger.warn('Feedback processing reached max iterations');
653
+ };
654
+ /**
655
+ * Apply changes and save the transcript
656
+ */ const applyChanges = async (feedbackCtx)=>{
657
+ const logger = getLogger();
658
+ let newPath = feedbackCtx.transcriptPath;
659
+ let moved = false;
660
+ // Check if we need to rename the file (title changed)
661
+ const titleChange = feedbackCtx.changes.find((c)=>c.type === 'title_changed');
662
+ if (titleChange) {
663
+ const slug = titleChange.details.slug;
664
+ const timestamp = extractTimestampFromFilename(feedbackCtx.transcriptPath);
665
+ const dir = path.dirname(feedbackCtx.transcriptPath);
666
+ if (timestamp) {
667
+ const timeStr = `${timestamp.hour.toString().padStart(2, '0')}${timestamp.minute.toString().padStart(2, '0')}`;
668
+ newPath = path.join(dir, `${timestamp.day}-${timeStr}-${slug}.md`);
669
+ } else {
670
+ newPath = path.join(dir, `${slug}.md`);
671
+ }
672
+ }
673
+ // Check if we need to move the file (project changed)
674
+ const projectChange = feedbackCtx.changes.find((c)=>c.type === 'project_changed');
675
+ if (projectChange && projectChange.details.routing) {
676
+ const routing = projectChange.details.routing;
677
+ if (routing.destination) {
678
+ // Expand ~ to home directory
679
+ let dest = routing.destination;
680
+ if (dest.startsWith('~')) {
681
+ dest = path.join(process.env.HOME || '', dest.slice(1));
682
+ }
683
+ // Get date from transcript metadata or use current date
684
+ const now = new Date();
685
+ const year = now.getFullYear().toString();
686
+ const month = (now.getMonth() + 1).toString().padStart(2, '0');
687
+ // Build path based on structure
688
+ let structuredPath = dest;
689
+ const structure = routing.structure || 'month';
690
+ if (structure === 'year') {
691
+ structuredPath = path.join(dest, year);
692
+ } else if (structure === 'month') {
693
+ structuredPath = path.join(dest, year, month);
694
+ } else if (structure === 'day') {
695
+ const day = now.getDate().toString().padStart(2, '0');
696
+ structuredPath = path.join(dest, year, month, day);
697
+ }
698
+ // Update path
699
+ const filename = path.basename(newPath);
700
+ newPath = path.join(structuredPath, filename);
701
+ moved = true;
702
+ }
703
+ }
704
+ // Ensure directory exists
705
+ await fs.mkdir(path.dirname(newPath), {
706
+ recursive: true
707
+ });
708
+ // Write the updated content
709
+ if (!feedbackCtx.dryRun) {
710
+ await fs.writeFile(newPath, feedbackCtx.transcriptContent, 'utf-8');
711
+ // Delete original if moved/renamed
712
+ if (newPath !== feedbackCtx.transcriptPath) {
713
+ await fs.unlink(feedbackCtx.transcriptPath);
714
+ }
715
+ }
716
+ logger.info('Applied %d changes to transcript', feedbackCtx.changes.length);
717
+ return {
718
+ newPath,
719
+ moved
720
+ };
721
+ };
722
+ /**
723
+ * Run the feedback command
724
+ */ const runFeedback = async (transcriptPath, options)=>{
725
+ // Verify file exists
726
+ try {
727
+ await fs.access(transcriptPath);
728
+ } catch {
729
+ print(`Error: File not found: ${transcriptPath}`);
730
+ process.exit(1);
731
+ }
732
+ // Read transcript
733
+ const transcriptContent = await fs.readFile(transcriptPath, 'utf-8');
734
+ // Initialize context
735
+ const context = await create();
736
+ // Initialize reasoning
737
+ const reasoning = create$1({
738
+ model: options.model || DEFAULT_MODEL
739
+ });
740
+ // Create feedback context
741
+ const feedbackCtx = {
742
+ transcriptPath,
743
+ transcriptContent,
744
+ originalContent: transcriptContent,
745
+ context,
746
+ changes: [],
747
+ verbose: options.verbose || false,
748
+ dryRun: options.dryRun || false
749
+ };
750
+ // Get feedback from user if not provided
751
+ let feedback = options.feedback;
752
+ if (!feedback) {
753
+ const rl = createReadlineInterface();
754
+ print('\n' + '─'.repeat(60));
755
+ print(`[Feedback for: ${path.basename(transcriptPath)}]`);
756
+ print('─'.repeat(60));
757
+ print('\nDescribe what needs to be corrected in natural language.');
758
+ print('Examples:');
759
+ print(' - "YB should be Wibey"');
760
+ print(' - "San Jay Grouper is actually Sanjay Gupta"');
761
+ print(' - "This should be in the Quantum Readiness project"');
762
+ print(' - "What feedback can I give?" (for help)\n');
763
+ feedback = await askQuestion(rl, 'What is your feedback? ');
764
+ rl.close();
765
+ if (!feedback) {
766
+ print('No feedback provided.');
767
+ return;
768
+ }
769
+ }
770
+ if (options.verbose) {
771
+ print('\n[Processing feedback...]');
772
+ }
773
+ // Process feedback with agentic model
774
+ await processFeedback(feedback, feedbackCtx, reasoning);
775
+ // Apply changes
776
+ if (feedbackCtx.changes.length > 0) {
777
+ if (options.dryRun) {
778
+ print('\n[Dry Run] Would apply the following changes:');
779
+ for (const change of feedbackCtx.changes){
780
+ print(` - ${change.description}`);
781
+ }
782
+ } else {
783
+ const { newPath, moved } = await applyChanges(feedbackCtx);
784
+ print('\n' + '─'.repeat(60));
785
+ print('[Changes Applied]');
786
+ print('─'.repeat(60));
787
+ for (const change of feedbackCtx.changes){
788
+ print(` ✓ ${change.description}`);
789
+ }
790
+ if (newPath !== feedbackCtx.transcriptPath) {
791
+ if (moved) {
792
+ print(`\nFile moved to: ${newPath}`);
793
+ } else {
794
+ print(`\nFile renamed to: ${path.basename(newPath)}`);
795
+ }
796
+ } else {
797
+ print(`\nFile updated: ${transcriptPath}`);
798
+ }
799
+ }
800
+ } else {
801
+ print('\nNo changes were made.');
802
+ }
803
+ };
804
+ /**
805
+ * Register the feedback command
806
+ */ const registerFeedbackCommands = (program)=>{
807
+ const feedbackCmd = new Command('feedback').description('Provide natural language feedback to correct transcripts and improve context').argument('[file]', 'Transcript file to provide feedback on').option('-f, --feedback <text>', 'Feedback text (if not provided, will prompt interactively)').option('-m, --model <model>', 'Reasoning model to use', DEFAULT_MODEL).option('--dry-run', 'Show what would happen without making changes').option('-v, --verbose', 'Show detailed output').option('--help-me', 'Show examples of feedback you can provide').action(async (file, options)=>{
808
+ if (options.helpMe) {
809
+ print(`
810
+ ╔════════════════════════════════════════════════════════════╗
811
+ ║ PROTOKOLL FEEDBACK - EXAMPLES ║
812
+ ╠════════════════════════════════════════════════════════════╣
813
+ ║ ║
814
+ ║ CORRECTING TERMS & ABBREVIATIONS ║
815
+ ║ ───────────────────────────────── ║
816
+ ║ "Everywhere it says WCMP, that should be WCNP" ║
817
+ ║ "YB should be spelled Wibey" ║
818
+ ║ "API should be written as A-P-I" ║
819
+ ║ ║
820
+ ║ FIXING NAMES ║
821
+ ║ ──────────── ║
822
+ ║ "San Jay Grouper is actually Sanjay Gupta" ║
823
+ ║ "Priya was transcribed as 'pre a' - please fix" ║
824
+ ║ "Marie should be spelled Mari" ║
825
+ ║ ║
826
+ ║ CHANGING PROJECT ASSIGNMENT ║
827
+ ║ ─────────────────────────── ║
828
+ ║ "This should be in the Quantum Readiness project" ║
829
+ ║ "Move this to client-alpha" ║
830
+ ║ "This was misclassified - should be personal" ║
831
+ ║ ║
832
+ ║ GENERAL CORRECTIONS ║
833
+ ║ ─────────────────── ║
834
+ ║ "Change the title to 'Q1 Planning Session'" ║
835
+ ║ "Replace 'gonna' with 'going to' everywhere" ║
836
+ ║ ║
837
+ ╚════════════════════════════════════════════════════════════╝
838
+
839
+ Usage:
840
+ protokoll feedback /path/to/transcript.md
841
+ protokoll feedback /path/to/transcript.md -f "YB should be Wibey"
842
+ protokoll feedback /path/to/transcript.md --dry-run -v
843
+ `);
844
+ return;
845
+ }
846
+ if (!file) {
847
+ print('Error: A transcript file is required.');
848
+ print('Usage: protokoll feedback /path/to/transcript.md');
849
+ print('Run "protokoll feedback --help-me" for examples.');
850
+ process.exit(1);
851
+ }
852
+ await runFeedback(file, options);
853
+ });
854
+ program.addCommand(feedbackCmd);
855
+ };
856
+
857
+ export { FEEDBACK_TOOLS, applyChanges, buildFeedbackSystemPrompt, executeTool, processFeedback, registerFeedbackCommands, runFeedback };
858
+ //# sourceMappingURL=feedback.js.map