@redaksjon/protokoll 0.2.0 → 0.4.3

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.
@@ -18,7 +18,7 @@ import os__default from 'node:os';
18
18
  import winston from 'winston';
19
19
  import { IterationStrategyFactory } from '@riotprompt/riotprompt';
20
20
 
21
- const VERSION = "0.2.0 (HEAD/0ecc049 T:v0.2.0 2026-01-19 00:11:42 -0800) linux x64 v24.12.0";
21
+ const VERSION = "0.4.3 (HEAD/94e6969 T:v0.4.3 2026-01-21 16:26:11 -0800) linux x64 v24.12.0";
22
22
  const PROGRAM_NAME = "protokoll";
23
23
  const DEFAULT_DIFF = true;
24
24
  const DEFAULT_LOG = false;
@@ -30,8 +30,8 @@ const DEFAULT_DEBUG = false;
30
30
  const DEFAULT_CONTENT_TYPES = ["diff"];
31
31
  const DEFAULT_INPUT_DIRECTORY = "./";
32
32
  const DEFAULT_OUTPUT_DIRECTORY = "./";
33
- const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"];
34
- const ALLOWED_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm"];
33
+ const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
34
+ const ALLOWED_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
35
35
  const DEFAULT_OUTPUT_STRUCTURE = "month";
36
36
  const DEFAULT_OUTPUT_FILENAME_OPTIONS = ["date", "time", "subject"];
37
37
  const ALLOWED_OUTPUT_STRUCTURES = ["none", "year", "month", "day"];
@@ -242,6 +242,27 @@ const create$w = (params) => {
242
242
  };
243
243
  };
244
244
 
245
+ const isParentProject = (projectA, projectB) => {
246
+ return projectB.relationships?.parent === projectA.id;
247
+ };
248
+ const isChildProject = (projectA, projectB) => {
249
+ return projectA.relationships?.parent === projectB.id;
250
+ };
251
+ const areSiblingProjects = (projectA, projectB) => {
252
+ const aSiblings = projectA.relationships?.siblings || [];
253
+ const bSiblings = projectB.relationships?.siblings || [];
254
+ return aSiblings.includes(projectB.id) || bSiblings.includes(projectA.id);
255
+ };
256
+ const getProjectRelationshipDistance = (projectA, projectB) => {
257
+ if (projectA.id === projectB.id) return 0;
258
+ if (isParentProject(projectA, projectB) || isChildProject(projectA, projectB)) return 1;
259
+ if (areSiblingProjects(projectA, projectB)) return 2;
260
+ if (projectA.relationships?.parent && projectB.relationships?.parent && projectA.relationships.parent === projectB.relationships.parent) {
261
+ return 2;
262
+ }
263
+ return -1;
264
+ };
265
+
245
266
  const DIRECTORY_TO_TYPE = {
246
267
  "people": "person",
247
268
  "projects": "project",
@@ -555,6 +576,55 @@ const create$u = async (options = {}) => {
555
576
  },
556
577
  search: (query) => storage.search(query),
557
578
  findBySoundsLike: (phonetic) => storage.findBySoundsLike(phonetic),
579
+ /**
580
+ * Context-aware search that prefers entities related to context project.
581
+ * Still returns standard search results if no context provided.
582
+ */
583
+ searchWithContext: (query, contextProjectId) => {
584
+ const results = storage.search(query);
585
+ if (!contextProjectId) {
586
+ return results;
587
+ }
588
+ const contextProject = storage.get("project", contextProjectId);
589
+ if (!contextProject) {
590
+ return results;
591
+ }
592
+ const scoredResults = results.map((entity) => {
593
+ let score = 0;
594
+ if (entity.type === "project") {
595
+ const distance = getProjectRelationshipDistance(contextProject, entity);
596
+ if (distance >= 0) {
597
+ score += (3 - distance) * 50;
598
+ }
599
+ }
600
+ if (entity.type === "term") {
601
+ const term = entity;
602
+ if (term.projects?.includes(contextProjectId)) {
603
+ score += 100;
604
+ }
605
+ }
606
+ return { entity, score };
607
+ });
608
+ return scoredResults.sort((a, b) => b.score - a.score).map((r) => r.entity);
609
+ },
610
+ /**
611
+ * Get all projects related to a given project within maxDistance
612
+ * Distance: 0 = same, 1 = parent/child, 2 = siblings/cousins
613
+ */
614
+ getRelatedProjects: (projectId, maxDistance = 2) => {
615
+ const project = storage.get("project", projectId);
616
+ if (!project) return [];
617
+ const allProjects = storage.getAll("project");
618
+ const related = [];
619
+ for (const otherProject of allProjects) {
620
+ if (otherProject.id === projectId) continue;
621
+ const distance = getProjectRelationshipDistance(project, otherProject);
622
+ if (distance >= 0 && distance <= maxDistance) {
623
+ related.push({ project: otherProject, distance });
624
+ }
625
+ }
626
+ return related.sort((a, b) => a.distance - b.distance).map((r) => r.project);
627
+ },
558
628
  saveEntity: async (entity) => {
559
629
  const closestDir = discoveryResult.discoveredDirs.sort((a, b) => a.level - b.level)[0];
560
630
  if (!closestDir) {
@@ -2336,7 +2406,8 @@ const createRoutingMetadata = (decision) => {
2336
2406
  };
2337
2407
  };
2338
2408
  const extractTagsFromSignals = (signals) => {
2339
- return signals.filter((s) => s.type !== "context_type").map((s) => s.value).filter((v) => typeof v === "string");
2409
+ const tags = signals.filter((s) => s.type !== "context_type").map((s) => s.value).filter((v) => typeof v === "string");
2410
+ return Array.from(new Set(tags));
2340
2411
  };
2341
2412
 
2342
2413
  const create$l = (config) => {
@@ -3470,7 +3541,13 @@ const create$b = (ctx) => ({
3470
3541
  }
3471
3542
  };
3472
3543
  }
3473
- const searchResults = context.search(args.name);
3544
+ let contextProjectId;
3545
+ if (ctx.routingInstance) {
3546
+ const allProjects2 = context.getAllProjects();
3547
+ const activeProject = allProjects2.find((p) => p.active !== false);
3548
+ contextProjectId = activeProject?.id;
3549
+ }
3550
+ const searchResults = context.searchWithContext(args.name, contextProjectId);
3474
3551
  const projectMatches = searchResults.filter((e) => e.type === "project");
3475
3552
  const termMatches = searchResults.filter((e) => e.type === "term");
3476
3553
  if (projectMatches.length > 0) {
@@ -3479,7 +3556,8 @@ const create$b = (ctx) => ({
3479
3556
  success: true,
3480
3557
  data: {
3481
3558
  found: true,
3482
- project
3559
+ project,
3560
+ matchedVia: "context_aware_search"
3483
3561
  }
3484
3562
  };
3485
3563
  }
@@ -3747,6 +3825,26 @@ const create$7 = (ctx) => {
3747
3825
  };
3748
3826
  };
3749
3827
 
3828
+ const cleanResponseContent = (content) => {
3829
+ let cleaned = content.replace(/^(?:Using tools?|Let me|I'll|I will|Now I'll|First,?\s*I(?:'ll| will)).*?[\r\n]+/gim, "");
3830
+ cleaned = cleaned.replace(/\{"tool":\s*"[^"]+",\s*"input":\s*\{[^}]*\}\}/g, "");
3831
+ cleaned = cleaned.replace(/\b\w+_\w+\(\{[^}]*\}\)/g, "");
3832
+ const lines = cleaned.split("\n");
3833
+ let startIndex = 0;
3834
+ for (let i = 0; i < lines.length; i++) {
3835
+ const line = lines[i].trim();
3836
+ if (line === "") continue;
3837
+ const isCommentary = /^(checking|verifying|looking|searching|analyzing|processing|determining|using|calling|executing|I'm|I am|Let me)/i.test(line) || line.includes("tool") || line.includes('{"') || line.includes("reasoning");
3838
+ if (!isCommentary) {
3839
+ startIndex = i;
3840
+ break;
3841
+ }
3842
+ }
3843
+ if (startIndex > 0) {
3844
+ cleaned = lines.slice(startIndex).join("\n");
3845
+ }
3846
+ return cleaned.trim();
3847
+ };
3750
3848
  const create$6 = (reasoning, ctx) => {
3751
3849
  const logger = getLogger();
3752
3850
  const registry = create$7(ctx);
@@ -3784,6 +3882,15 @@ CRITICAL RULES:
3784
3882
  - When you have finished processing, output the COMPLETE corrected transcript as Markdown.
3785
3883
  - Do NOT say you don't have the transcript - it's in the conversation history.
3786
3884
 
3885
+ OUTPUT REQUIREMENTS - EXTREMELY IMPORTANT:
3886
+ - Your final response MUST contain ONLY the corrected transcript text.
3887
+ - DO NOT include any commentary like "Using tools to..." or "Let me verify...".
3888
+ - DO NOT include any explanations about what you're doing or have done.
3889
+ - DO NOT include any tool call information or JSON in your response.
3890
+ - DO NOT include any reasoning or processing notes.
3891
+ - Your output will be inserted directly into the user-facing document.
3892
+ - If you need to use tools, use them silently - don't narrate what you're doing.
3893
+
3787
3894
  Available tools:
3788
3895
  - lookup_person: Find information about people (use for any name that might be misspelled)
3789
3896
  - lookup_project: Find project routing information
@@ -3803,6 +3910,7 @@ Please:
3803
3910
  3. Use route_note to determine the destination
3804
3911
  4. Then output the COMPLETE corrected transcript as clean Markdown
3805
3912
 
3913
+ CRITICAL: Your response must contain ONLY the transcript text - no commentary, no explanations, no tool information.
3806
3914
  Remember: preserve ALL content, only fix transcription errors.`;
3807
3915
  conversationHistory.push({ role: "user", content: initialPrompt });
3808
3916
  try {
@@ -4325,7 +4433,9 @@ Corrections made so far: ${state.resolvedEntities.size > 0 ? Array.from(state.re
4325
4433
 
4326
4434
  Continue analyzing. If you need more information, use the tools.
4327
4435
  When you're done with tool calls, output the COMPLETE corrected transcript as Markdown.
4328
- Do NOT summarize - include ALL original content with corrections applied.`;
4436
+ Do NOT summarize - include ALL original content with corrections applied.
4437
+
4438
+ CRITICAL REMINDER: Your response must contain ONLY the transcript text. Do NOT include any commentary, explanations, or processing notes - those will leak into the user-facing document.`;
4329
4439
  conversationHistory.push({ role: "user", content: continuationPrompt });
4330
4440
  response = await reasoning.complete({
4331
4441
  systemPrompt,
@@ -4346,9 +4456,17 @@ Do NOT summarize - include ALL original content with corrections applied.`;
4346
4456
  });
4347
4457
  }
4348
4458
  if (response.content && response.content.length > 50) {
4349
- state.correctedText = response.content;
4459
+ const cleanedContent = cleanResponseContent(response.content);
4460
+ if (cleanedContent !== response.content) {
4461
+ logger.warn(
4462
+ "Removed leaked internal processing from response (%d -> %d chars)",
4463
+ response.content.length,
4464
+ cleanedContent.length
4465
+ );
4466
+ }
4467
+ state.correctedText = cleanedContent;
4350
4468
  state.confidence = 0.9;
4351
- logger.debug("Final transcript generated: %d characters", response.content.length);
4469
+ logger.debug("Final transcript generated: %d characters", cleanedContent.length);
4352
4470
  } else {
4353
4471
  logger.debug("Model did not produce transcript, requesting explicitly...");
4354
4472
  const finalRequest = `Please output the COMPLETE corrected transcript now.
@@ -4359,7 +4477,9 @@ ${transcriptText}
4359
4477
  CORRECTIONS TO APPLY:
4360
4478
  ${state.resolvedEntities.size > 0 ? Array.from(state.resolvedEntities.entries()).map(([k, v]) => `- "${k}" should be "${v}"`).join("\n") : "None identified"}
4361
4479
 
4362
- Output the full transcript as clean Markdown. Do NOT summarize.`;
4480
+ Output the full transcript as clean Markdown. Do NOT summarize.
4481
+
4482
+ CRITICAL: Your response must contain ONLY the corrected transcript text - absolutely no commentary, tool information, or explanations.`;
4363
4483
  const finalResponse = await reasoning.complete({
4364
4484
  systemPrompt,
4365
4485
  prompt: finalRequest
@@ -4367,7 +4487,15 @@ Output the full transcript as clean Markdown. Do NOT summarize.`;
4367
4487
  if (finalResponse.usage) {
4368
4488
  totalTokens += finalResponse.usage.totalTokens;
4369
4489
  }
4370
- state.correctedText = finalResponse.content || transcriptText;
4490
+ const cleanedFinalContent = cleanResponseContent(finalResponse.content || transcriptText);
4491
+ if (cleanedFinalContent !== finalResponse.content) {
4492
+ logger.warn(
4493
+ "Removed leaked internal processing from final response (%d -> %d chars)",
4494
+ finalResponse.content?.length || 0,
4495
+ cleanedFinalContent.length
4496
+ );
4497
+ }
4498
+ state.correctedText = cleanedFinalContent;
4371
4499
  state.confidence = 0.8;
4372
4500
  }
4373
4501
  } catch (error) {
@@ -4510,14 +4638,14 @@ const create$3 = async (config) => {
4510
4638
  const routingProjects = contextProjects.filter((project) => project.active !== false).map((project) => ({
4511
4639
  projectId: project.id,
4512
4640
  destination: {
4513
- path: project.routing.destination || defaultPath,
4514
- structure: project.routing.structure,
4515
- filename_options: project.routing.filename_options,
4641
+ path: project.routing?.destination || defaultPath,
4642
+ structure: project.routing?.structure || defaultStructure,
4643
+ filename_options: project.routing?.filename_options || defaultFilenameOptions,
4516
4644
  createDirectories: true
4517
4645
  },
4518
4646
  classification: project.classification,
4519
4647
  active: project.active,
4520
- auto_tags: project.routing.auto_tags
4648
+ auto_tags: project.routing?.auto_tags
4521
4649
  }));
4522
4650
  logger.debug("Loaded %d projects from context for routing", routingProjects.length);
4523
4651
  const routingConfig = {
@@ -6567,7 +6695,7 @@ const listTranscripts = async (options) => {
6567
6695
  let content;
6568
6696
  try {
6569
6697
  content = await fs$1.readFile(filePath, "utf-8");
6570
- } catch (error) {
6698
+ } catch {
6571
6699
  continue;
6572
6700
  }
6573
6701
  if (search) {
@@ -6670,5 +6798,5 @@ const registerTranscriptCommands = (program) => {
6670
6798
  });
6671
6799
  };
6672
6800
 
6673
- export { ALLOWED_OUTPUT_FILENAME_OPTIONS as A, listTranscripts as B, create$d as C, DEFAULT_REASONING_LEVEL as D, processFeedback as E, applyChanges as F, parseTranscript as G, combineTranscripts as H, editTranscript as I, create$p as J, DEFAULT_TEMP_DIRECTORY as K, createCompletion as L, contentFetcher as M, projectAssist as N, PROGRAM_NAME as P, VERSION as V, PROTOKOLL_DEFAULTS as a, DEFAULT_MAX_AUDIO_SIZE as b, create$w as c, create$1 as d, DEFAULT_INTERMEDIATE_DIRECTORY as e, ALLOWED_OUTPUT_STRUCTURES as f, getLogger as g, ALLOWED_AUDIO_EXTENSIONS as h, DEFAULT_OUTPUT_DIRECTORY as i, DEFAULT_INPUT_DIRECTORY as j, DEFAULT_OUTPUT_FILENAME_OPTIONS as k, DEFAULT_OUTPUT_STRUCTURE as l, DEFAULT_AUDIO_EXTENSIONS as m, DEFAULT_TIMEZONE as n, DEFAULT_CONFIG_DIR as o, create$2 as p, create$u as q, create as r, setLogLevel as s, DEFAULT_CONTEXT_DIR_NAME as t, DEFAULT_CONTEXT_CONFIG_FILE_NAME as u, DEFAULT_MODEL as v, DEFAULT_TRANSCRIPTION_MODEL as w, registerActionCommands as x, registerFeedbackCommands as y, registerTranscriptCommands as z };
6801
+ export { ALLOWED_OUTPUT_FILENAME_OPTIONS as A, listTranscripts as B, DEFAULT_TEMP_DIRECTORY as C, DEFAULT_REASONING_LEVEL as D, create$p as E, create$d as F, processFeedback as G, applyChanges as H, combineTranscripts as I, editTranscript as J, parseTranscript as K, createCompletion as L, contentFetcher as M, projectAssist as N, PROGRAM_NAME as P, VERSION as V, PROTOKOLL_DEFAULTS as a, DEFAULT_MAX_AUDIO_SIZE as b, create$w as c, create$1 as d, DEFAULT_INTERMEDIATE_DIRECTORY as e, ALLOWED_OUTPUT_STRUCTURES as f, getLogger as g, ALLOWED_AUDIO_EXTENSIONS as h, DEFAULT_OUTPUT_DIRECTORY as i, DEFAULT_INPUT_DIRECTORY as j, DEFAULT_OUTPUT_FILENAME_OPTIONS as k, DEFAULT_OUTPUT_STRUCTURE as l, DEFAULT_AUDIO_EXTENSIONS as m, DEFAULT_TIMEZONE as n, DEFAULT_CONFIG_DIR as o, create$2 as p, create$u as q, create as r, setLogLevel as s, DEFAULT_CONTEXT_DIR_NAME as t, DEFAULT_CONTEXT_CONFIG_FILE_NAME as u, DEFAULT_MODEL as v, DEFAULT_TRANSCRIPTION_MODEL as w, registerActionCommands as x, registerFeedbackCommands as y, registerTranscriptCommands as z };
6674
6802
  //# sourceMappingURL=transcript.js.map