@redaksjon/protokoll 1.0.29 → 1.0.30-dev.20260317191035.81f637e

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.
@@ -208,7 +208,7 @@ function isProtokolUri(uri) {
208
208
  return uri.startsWith(`${SCHEME}://`);
209
209
  }
210
210
 
211
- const VERSION = "1.0.29 (HEAD/f6962e2 T:v1.0.29 2026-03-17 07:58:57 -0700) linux arm64 v24.14.0";
211
+ const VERSION = "1.0.30-dev.20260317191035.81f637e (working/81f637e 2026-03-17 12:10:15 -0700) linux arm64 v24.14.0";
212
212
  const PROGRAM_NAME = "protokoll";
213
213
  const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = "YYYY-M-D-HHmmss";
214
214
  const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
@@ -4470,6 +4470,131 @@ async function getContextInstance(contextDirectory) {
4470
4470
  }
4471
4471
  return createToolContext(contextDirectory);
4472
4472
  }
4473
+ function normalizeProjectId(value) {
4474
+ return typeof value === "string" ? value.trim().toLowerCase() : "";
4475
+ }
4476
+ function createAllowedProjectSet(allowedProjectIds) {
4477
+ return new Set(
4478
+ (allowedProjectIds || []).map((projectId) => normalizeProjectId(projectId)).filter((projectId) => projectId.length > 0)
4479
+ );
4480
+ }
4481
+ function isProjectInScope(projectId, allowedProjectIds) {
4482
+ const normalized = normalizeProjectId(projectId);
4483
+ return normalized.length > 0 && allowedProjectIds.has(normalized);
4484
+ }
4485
+ function hasScopedProjectReference(projectIds, allowedProjectIds) {
4486
+ return (projectIds || []).some((projectId) => isProjectInScope(projectId, allowedProjectIds));
4487
+ }
4488
+ async function loadProjects(context) {
4489
+ const projects = context.getAllProjects();
4490
+ if (projects.length > 0) {
4491
+ return projects;
4492
+ }
4493
+ const gcsProjects = await listContextEntitiesFromGcs("project");
4494
+ return gcsProjects.map((project) => ({
4495
+ id: String(project.id || ""),
4496
+ name: String(project.name || ""),
4497
+ type: "project",
4498
+ active: project.active !== false,
4499
+ routing: typeof project.routing === "object" && project.routing !== null ? project.routing : void 0,
4500
+ classification: typeof project.classification === "object" && project.classification !== null ? project.classification : void 0
4501
+ })).filter((project) => project.id.length > 0 && project.name.length > 0).map((project) => ({
4502
+ ...project,
4503
+ routing: {
4504
+ destination: project.routing?.destination,
4505
+ structure: project.routing?.structure
4506
+ },
4507
+ classification: {
4508
+ context_type: project.classification?.context_type,
4509
+ explicit_phrases: project.classification?.explicit_phrases,
4510
+ associated_people: project.classification?.associated_people,
4511
+ associated_companies: project.classification?.associated_companies
4512
+ }
4513
+ }));
4514
+ }
4515
+ async function loadPeople(context) {
4516
+ const people = context.getAllPeople();
4517
+ if (people.length > 0) {
4518
+ return people;
4519
+ }
4520
+ const gcsPeople = await listContextEntitiesFromGcs("person");
4521
+ return gcsPeople.map((person) => ({
4522
+ id: String(person.id || ""),
4523
+ name: String(person.name || ""),
4524
+ type: "person",
4525
+ company: typeof person.company === "string" ? person.company : void 0,
4526
+ role: typeof person.role === "string" ? person.role : void 0,
4527
+ sounds_like: Array.isArray(person.sounds_like) ? person.sounds_like : void 0
4528
+ })).filter((person) => person.id.length > 0 && person.name.length > 0);
4529
+ }
4530
+ async function loadTerms(context) {
4531
+ const terms = context.getAllTerms();
4532
+ if (terms.length > 0) {
4533
+ return terms;
4534
+ }
4535
+ const gcsTerms = await listContextEntitiesFromGcs("term");
4536
+ return gcsTerms.map((term) => ({
4537
+ id: String(term.id || ""),
4538
+ name: String(term.name || ""),
4539
+ type: "term",
4540
+ expansion: typeof term.expansion === "string" ? term.expansion : void 0,
4541
+ domain: typeof term.domain === "string" ? term.domain : void 0,
4542
+ sounds_like: Array.isArray(term.sounds_like) ? term.sounds_like : void 0,
4543
+ projects: Array.isArray(term.projects) ? term.projects.filter((value) => typeof value === "string") : void 0
4544
+ })).filter((term) => term.id.length > 0 && term.name.length > 0);
4545
+ }
4546
+ async function loadCompanies(context) {
4547
+ const companies = context.getAllCompanies();
4548
+ if (companies.length > 0) {
4549
+ return companies;
4550
+ }
4551
+ const gcsCompanies = await listContextEntitiesFromGcs("company");
4552
+ return gcsCompanies.map((company) => ({
4553
+ id: String(company.id || ""),
4554
+ name: String(company.name || ""),
4555
+ type: "company",
4556
+ fullName: typeof company.fullName === "string" ? company.fullName : void 0,
4557
+ industry: typeof company.industry === "string" ? company.industry : void 0,
4558
+ sounds_like: Array.isArray(company.sounds_like) ? company.sounds_like : void 0
4559
+ })).filter((company) => company.id.length > 0 && company.name.length > 0);
4560
+ }
4561
+ async function buildProjectScopeState(context, allowedProjectIds) {
4562
+ const allowedProjectSet = createAllowedProjectSet(allowedProjectIds);
4563
+ if (allowedProjectSet.size === 0) {
4564
+ return null;
4565
+ }
4566
+ const projects = (await loadProjects(context)).filter((project) => isProjectInScope(project.id, allowedProjectSet));
4567
+ const associatedPeople = /* @__PURE__ */ new Set();
4568
+ const associatedCompanies = /* @__PURE__ */ new Set();
4569
+ for (const project of projects) {
4570
+ for (const personId of project.classification?.associated_people || []) {
4571
+ associatedPeople.add(personId);
4572
+ }
4573
+ for (const companyId of project.classification?.associated_companies || []) {
4574
+ associatedCompanies.add(companyId);
4575
+ }
4576
+ }
4577
+ return {
4578
+ allowedProjectIds: allowedProjectSet,
4579
+ projects,
4580
+ associatedPeople,
4581
+ associatedCompanies
4582
+ };
4583
+ }
4584
+ function isEntityVisibleInProjectScope(entity, scope) {
4585
+ switch (entity.type) {
4586
+ case "project":
4587
+ return isProjectInScope(entity.id, scope.allowedProjectIds);
4588
+ case "person":
4589
+ return scope.associatedPeople.has(entity.id);
4590
+ case "company":
4591
+ return scope.associatedCompanies.has(entity.id);
4592
+ case "term":
4593
+ return hasScopedProjectReference(entity.projects, scope.allowedProjectIds);
4594
+ default:
4595
+ return false;
4596
+ }
4597
+ }
4473
4598
  const contextStatusTool = {
4474
4599
  name: "protokoll_context_status",
4475
4600
  description: "Get the status of the Protokoll context system. Shows discovered .protokoll directories, entity counts, and configuration status. Use this to understand what context is available for transcription enhancement.",
@@ -4672,8 +4797,13 @@ const predictEntitiesTool = {
4672
4797
  };
4673
4798
  async function handleContextStatus(args) {
4674
4799
  const context = await getContextInstance(args.contextDirectory);
4800
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4675
4801
  const dirs = context.getDiscoveredDirs();
4676
4802
  const config = context.getConfig();
4803
+ const projects = scope?.projects ?? await loadProjects(context);
4804
+ const people = scope ? (await loadPeople(context)).filter((person) => scope.associatedPeople.has(person.id)) : await loadPeople(context);
4805
+ const terms = scope ? (await loadTerms(context)).filter((term) => hasScopedProjectReference(term.projects, scope.allowedProjectIds)) : await loadTerms(context);
4806
+ const companies = scope ? (await loadCompanies(context)).filter((company) => scope.associatedCompanies.has(company.id)) : await loadCompanies(context);
4677
4807
  return {
4678
4808
  hasContext: context.hasContext(),
4679
4809
  discoveredDirectories: dirs.map((d) => ({
@@ -4682,11 +4812,11 @@ async function handleContextStatus(args) {
4682
4812
  isPrimary: d.level === 0
4683
4813
  })),
4684
4814
  entityCounts: {
4685
- projects: context.getAllProjects().length,
4686
- people: context.getAllPeople().length,
4687
- terms: context.getAllTerms().length,
4688
- companies: context.getAllCompanies().length,
4689
- ignored: context.getAllIgnored().length
4815
+ projects: projects.length,
4816
+ people: people.length,
4817
+ terms: terms.length,
4818
+ companies: companies.length,
4819
+ ignored: scope ? 0 : context.getAllIgnored().length
4690
4820
  },
4691
4821
  config: {
4692
4822
  outputDirectory: config.outputDirectory,
@@ -4697,27 +4827,8 @@ async function handleContextStatus(args) {
4697
4827
  }
4698
4828
  async function handleListProjects(args) {
4699
4829
  const context = await getContextInstance(args.contextDirectory);
4700
- let projects = context.getAllProjects();
4701
- if (projects.length === 0) {
4702
- const gcsProjects = await listContextEntitiesFromGcs("project");
4703
- projects = gcsProjects.map((project) => ({
4704
- id: String(project.id || ""),
4705
- name: String(project.name || ""),
4706
- active: project.active !== false,
4707
- routing: typeof project.routing === "object" && project.routing !== null ? project.routing : void 0,
4708
- classification: typeof project.classification === "object" && project.classification !== null ? project.classification : void 0
4709
- })).filter((project) => project.id.length > 0 && project.name.length > 0).map((project) => ({
4710
- ...project,
4711
- routing: {
4712
- destination: project.routing?.destination,
4713
- structure: project.routing?.structure
4714
- },
4715
- classification: {
4716
- context_type: project.classification?.context_type,
4717
- explicit_phrases: project.classification?.explicit_phrases
4718
- }
4719
- }));
4720
- }
4830
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4831
+ let projects = scope?.projects ?? await loadProjects(context);
4721
4832
  if (!args.includeInactive) {
4722
4833
  projects = projects.filter((p) => p.active !== false);
4723
4834
  }
@@ -4749,16 +4860,10 @@ async function handleListProjects(args) {
4749
4860
  }
4750
4861
  async function handleListPeople(args) {
4751
4862
  const context = await getContextInstance(args.contextDirectory);
4752
- let people = context.getAllPeople();
4753
- if (people.length === 0) {
4754
- const gcsPeople = await listContextEntitiesFromGcs("person");
4755
- people = gcsPeople.map((person) => ({
4756
- id: String(person.id || ""),
4757
- name: String(person.name || ""),
4758
- company: typeof person.company === "string" ? person.company : void 0,
4759
- role: typeof person.role === "string" ? person.role : void 0,
4760
- sounds_like: Array.isArray(person.sounds_like) ? person.sounds_like : void 0
4761
- })).filter((person) => person.id.length > 0 && person.name.length > 0);
4863
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4864
+ let people = await loadPeople(context);
4865
+ if (scope) {
4866
+ people = people.filter((person) => scope.associatedPeople.has(person.id));
4762
4867
  }
4763
4868
  if (args.search) {
4764
4869
  const searchLower = args.search.toLowerCase();
@@ -4786,16 +4891,10 @@ async function handleListPeople(args) {
4786
4891
  }
4787
4892
  async function handleListTerms(args) {
4788
4893
  const context = await getContextInstance(args.contextDirectory);
4789
- let terms = context.getAllTerms();
4790
- if (terms.length === 0) {
4791
- const gcsTerms = await listContextEntitiesFromGcs("term");
4792
- terms = gcsTerms.map((term) => ({
4793
- id: String(term.id || ""),
4794
- name: String(term.name || ""),
4795
- expansion: typeof term.expansion === "string" ? term.expansion : void 0,
4796
- domain: typeof term.domain === "string" ? term.domain : void 0,
4797
- sounds_like: Array.isArray(term.sounds_like) ? term.sounds_like : void 0
4798
- })).filter((term) => term.id.length > 0 && term.name.length > 0);
4894
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4895
+ let terms = await loadTerms(context);
4896
+ if (scope) {
4897
+ terms = terms.filter((term) => hasScopedProjectReference(term.projects, scope.allowedProjectIds));
4799
4898
  }
4800
4899
  if (args.search) {
4801
4900
  const searchLower = args.search.toLowerCase();
@@ -4823,16 +4922,10 @@ async function handleListTerms(args) {
4823
4922
  }
4824
4923
  async function handleListCompanies(args) {
4825
4924
  const context = await getContextInstance(args.contextDirectory);
4826
- let companies = context.getAllCompanies();
4827
- if (companies.length === 0) {
4828
- const gcsCompanies = await listContextEntitiesFromGcs("company");
4829
- companies = gcsCompanies.map((company) => ({
4830
- id: String(company.id || ""),
4831
- name: String(company.name || ""),
4832
- fullName: typeof company.fullName === "string" ? company.fullName : void 0,
4833
- industry: typeof company.industry === "string" ? company.industry : void 0,
4834
- sounds_like: Array.isArray(company.sounds_like) ? company.sounds_like : void 0
4835
- })).filter((company) => company.id.length > 0 && company.name.length > 0);
4925
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4926
+ let companies = await loadCompanies(context);
4927
+ if (scope) {
4928
+ companies = companies.filter((company) => scope.associatedCompanies.has(company.id));
4836
4929
  }
4837
4930
  if (args.search) {
4838
4931
  const searchLower = args.search.toLowerCase();
@@ -4860,7 +4953,8 @@ async function handleListCompanies(args) {
4860
4953
  }
4861
4954
  async function handleSearchContext(args) {
4862
4955
  const context = await getContextInstance(args.contextDirectory);
4863
- const results = context.search(args.query);
4956
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4957
+ const results = scope ? context.search(args.query).filter((entity) => isEntityVisibleInProjectScope(entity, scope)) : context.search(args.query);
4864
4958
  const total = results.length;
4865
4959
  const limit = args.limit ?? 50;
4866
4960
  const offset = args.offset ?? 0;
@@ -4876,6 +4970,7 @@ async function handleSearchContext(args) {
4876
4970
  }
4877
4971
  async function handleGetEntity(args) {
4878
4972
  const context = await getContextInstance(args.contextDirectory);
4973
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4879
4974
  let entity;
4880
4975
  switch (args.entityType) {
4881
4976
  case "project":
@@ -4896,6 +4991,9 @@ async function handleGetEntity(args) {
4896
4991
  default:
4897
4992
  throw new Error(`Unknown entity type: ${args.entityType}`);
4898
4993
  }
4994
+ if (scope && !isEntityVisibleInProjectScope(entity, scope)) {
4995
+ throw new Error(`Project-scoped key cannot access ${args.entityType} "${args.entityId}".`);
4996
+ }
4899
4997
  const filePath = context.getEntityFilePath(entity);
4900
4998
  return {
4901
4999
  ...formatEntity(entity),