@redaksjon/protokoll 1.0.31-dev.20260317224752.adeddfd → 1.0.31-dev.20260320152728.3166fe3

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.31-dev.20260317224752.adeddfd (working/adeddfd 2026-03-17 15:46:49 -0700) linux arm64 v24.14.0";
211
+ const VERSION = "1.0.31-dev.20260320152728.3166fe3 (working/3166fe3 2026-03-20 08:26:22 -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"];
@@ -2961,7 +2961,47 @@ async function readTranscriptsListResource(options) {
2961
2961
  };
2962
2962
  }
2963
2963
 
2964
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2965
+ function resolveCanonicalEntityId$1(id) {
2966
+ const t = id.trim();
2967
+ const colon = t.indexOf(":");
2968
+ if (colon === -1) {
2969
+ return t;
2970
+ }
2971
+ const rest = t.slice(colon + 1).trim();
2972
+ if (UUID_RE.test(rest)) {
2973
+ return rest;
2974
+ }
2975
+ return t;
2976
+ }
2977
+ function entityIdLookupOrder(id) {
2978
+ const t = id.trim();
2979
+ const canon = resolveCanonicalEntityId$1(t);
2980
+ return canon === t ? [t] : [t, canon];
2981
+ }
2982
+
2964
2983
  const logger$5 = Logging.getLogger("@redaksjon/protokoll-mcp").get("entity-resources");
2984
+ const PROJECT_PLAN_ARRAY_KEYS$1 = ["related_plans", "plans", "riotplan_plans"];
2985
+ function stripProjectPlanArraysForResource(entity) {
2986
+ const base = entity && typeof entity === "object" && !Array.isArray(entity) ? { ...entity } : {};
2987
+ let totalRows = 0;
2988
+ for (const k of PROJECT_PLAN_ARRAY_KEYS$1) {
2989
+ const v = base[k];
2990
+ if (Array.isArray(v)) {
2991
+ totalRows += v.length;
2992
+ }
2993
+ }
2994
+ for (const k of PROJECT_PLAN_ARRAY_KEYS$1) {
2995
+ const v = base[k];
2996
+ if (Array.isArray(v) && v.length > 0) {
2997
+ delete base[k];
2998
+ }
2999
+ }
3000
+ if (totalRows > 0) {
3001
+ base.related_plans_total = totalRows;
3002
+ }
3003
+ return base;
3004
+ }
2965
3005
  const ENTITY_DIRECTORY = {
2966
3006
  person: "people",
2967
3007
  project: "projects",
@@ -2973,14 +3013,18 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
2973
3013
  if (entityType in ENTITY_DIRECTORY && isInitialized()) {
2974
3014
  const storageConfig = getStorageConfig();
2975
3015
  if (storageConfig.backend === "gcs") {
2976
- const gcsEntity = await findContextEntityInGcs(entityType, entityId);
2977
- if (gcsEntity) {
2978
- const yamlContent2 = yaml.dump(gcsEntity);
2979
- return {
2980
- uri: buildEntityUri(entityType, entityId),
2981
- mimeType: "application/yaml",
2982
- text: yamlContent2
2983
- };
3016
+ for (const tryId of entityIdLookupOrder(entityId)) {
3017
+ const gcsEntity = await findContextEntityInGcs(entityType, tryId);
3018
+ if (gcsEntity) {
3019
+ const idForUri = typeof gcsEntity.id === "string" ? String(gcsEntity.id).trim() : tryId;
3020
+ const toDump = entityType === "project" ? stripProjectPlanArraysForResource(gcsEntity) : gcsEntity;
3021
+ const yamlContent2 = yaml.dump(toDump);
3022
+ return {
3023
+ uri: buildEntityUri(entityType, idForUri),
3024
+ mimeType: "application/yaml",
3025
+ text: yamlContent2
3026
+ };
3027
+ }
2984
3028
  }
2985
3029
  }
2986
3030
  }
@@ -3009,41 +3053,49 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
3009
3053
  });
3010
3054
  throw new Error(`No Protokoll context found. Expected .protokoll/ or context dirs in ${searchDir}`);
3011
3055
  }
3012
- const lookupEntity = (candidate) => {
3056
+ const lookupEntityById = (candidate, forId) => {
3013
3057
  switch (entityType) {
3014
3058
  case "person":
3015
- return candidate.getPerson(entityId);
3059
+ return candidate.getPerson(forId);
3016
3060
  case "project":
3017
- return candidate.getProject(entityId);
3061
+ return candidate.getProject(forId);
3018
3062
  case "term":
3019
- return candidate.getTerm(entityId);
3063
+ return candidate.getTerm(forId);
3020
3064
  case "company":
3021
- return candidate.getCompany(entityId);
3065
+ return candidate.getCompany(forId);
3022
3066
  case "ignored":
3023
- return candidate.getIgnored(entityId);
3067
+ return candidate.getIgnored(forId);
3024
3068
  default:
3025
3069
  throw new Error(`Unknown entity type: ${entityType}`);
3026
3070
  }
3027
3071
  };
3028
- let entity = lookupEntity(context);
3029
- if (!entity && isInitialized()) {
3030
- const serverContext = getContext();
3072
+ const serverContext = isInitialized() ? getContext() : void 0;
3073
+ if (serverContext?.hasContext() && serverContext !== context) {
3074
+ try {
3075
+ await serverContext.reload();
3076
+ } catch (error) {
3077
+ logger$5.debug("entity.read.server_context_reload_failed", {
3078
+ entityType,
3079
+ entityId,
3080
+ error: error instanceof Error ? error.message : String(error)
3081
+ });
3082
+ }
3083
+ }
3084
+ let entity;
3085
+ for (const tryId of entityIdLookupOrder(entityId)) {
3086
+ entity = lookupEntityById(context, tryId);
3087
+ if (entity) {
3088
+ break;
3089
+ }
3031
3090
  if (serverContext?.hasContext() && serverContext !== context) {
3032
- try {
3033
- await serverContext.reload();
3034
- } catch (error) {
3035
- logger$5.debug("entity.read.server_context_reload_failed", {
3036
- entityType,
3037
- entityId,
3038
- error: error instanceof Error ? error.message : String(error)
3039
- });
3040
- }
3041
- entity = lookupEntity(serverContext);
3091
+ entity = lookupEntityById(serverContext, tryId);
3042
3092
  if (entity) {
3043
3093
  logger$5.info("entity.read.server_context_fallback_hit", {
3044
3094
  entityType,
3045
- entityId
3095
+ entityId,
3096
+ tryId
3046
3097
  });
3098
+ break;
3047
3099
  }
3048
3100
  }
3049
3101
  }
@@ -3056,13 +3108,16 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
3056
3108
  });
3057
3109
  throw new Error(`${entityType} "${entityId}" not found`);
3058
3110
  }
3111
+ const canonicalId = entity.id;
3059
3112
  logger$5.info("entity.read.found", {
3060
3113
  entityType,
3061
- entityId
3114
+ entityId,
3115
+ canonicalId: canonicalId !== entityId ? canonicalId : void 0
3062
3116
  });
3063
- const yamlContent = yaml.dump(entity);
3117
+ const payload = entityType === "project" ? stripProjectPlanArraysForResource(entity) : entity;
3118
+ const yamlContent = yaml.dump(payload);
3064
3119
  return {
3065
- uri: buildEntityUri(entityType, entityId),
3120
+ uri: buildEntityUri(entityType, canonicalId),
3066
3121
  mimeType: "application/yaml",
3067
3122
  text: yamlContent
3068
3123
  };
@@ -4471,7 +4526,10 @@ async function getContextInstance(contextDirectory) {
4471
4526
  return createToolContext(contextDirectory);
4472
4527
  }
4473
4528
  function normalizeProjectId(value) {
4474
- return typeof value === "string" ? value.trim().toLowerCase() : "";
4529
+ if (typeof value !== "string") {
4530
+ return "";
4531
+ }
4532
+ return resolveCanonicalEntityId$1(value).trim().toLowerCase();
4475
4533
  }
4476
4534
  function createAllowedProjectSet(allowedProjectIds) {
4477
4535
  return new Set(
@@ -4485,6 +4543,25 @@ function isProjectInScope(projectId, allowedProjectIds) {
4485
4543
  function hasScopedProjectReference(projectIds, allowedProjectIds) {
4486
4544
  return (projectIds || []).some((projectId) => isProjectInScope(projectId, allowedProjectIds));
4487
4545
  }
4546
+ function dedupeByCanonicalEntityId(items) {
4547
+ const groups = /* @__PURE__ */ new Map();
4548
+ for (const item of items) {
4549
+ const key = resolveCanonicalEntityId$1(item.id).toLowerCase();
4550
+ const arr = groups.get(key) ?? [];
4551
+ arr.push(item);
4552
+ groups.set(key, arr);
4553
+ }
4554
+ const out = [];
4555
+ for (const group of groups.values()) {
4556
+ if (group.length === 1) {
4557
+ out.push(group[0]);
4558
+ continue;
4559
+ }
4560
+ const preferred = group.find((r) => r.id === resolveCanonicalEntityId$1(r.id)) ?? group[0];
4561
+ out.push(preferred);
4562
+ }
4563
+ return out;
4564
+ }
4488
4565
  async function loadProjects(context) {
4489
4566
  const projects = context.getAllProjects();
4490
4567
  if (projects.length > 0) {
@@ -4828,7 +4905,7 @@ async function handleContextStatus(args) {
4828
4905
  async function handleListProjects(args) {
4829
4906
  const context = await getContextInstance(args.contextDirectory);
4830
4907
  const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4831
- let projects = scope?.projects ?? await loadProjects(context);
4908
+ let projects = dedupeByCanonicalEntityId(scope?.projects ?? await loadProjects(context));
4832
4909
  if (!args.includeInactive) {
4833
4910
  projects = projects.filter((p) => p.active !== false);
4834
4911
  }
@@ -4848,7 +4925,7 @@ async function handleListProjects(args) {
4848
4925
  offset,
4849
4926
  count: paginatedProjects.length,
4850
4927
  projects: paginatedProjects.map((p) => ({
4851
- id: p.id,
4928
+ id: resolveCanonicalEntityId$1(p.id),
4852
4929
  name: p.name,
4853
4930
  active: p.active !== false,
4854
4931
  destination: p.routing?.destination,
@@ -4861,7 +4938,7 @@ async function handleListProjects(args) {
4861
4938
  async function handleListPeople(args) {
4862
4939
  const context = await getContextInstance(args.contextDirectory);
4863
4940
  const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4864
- let people = await loadPeople(context);
4941
+ let people = dedupeByCanonicalEntityId(await loadPeople(context));
4865
4942
  if (scope) {
4866
4943
  people = people.filter((person) => scope.associatedPeople.has(person.id));
4867
4944
  }
@@ -4881,7 +4958,7 @@ async function handleListPeople(args) {
4881
4958
  offset,
4882
4959
  count: paginatedPeople.length,
4883
4960
  people: paginatedPeople.map((p) => ({
4884
- id: p.id,
4961
+ id: resolveCanonicalEntityId$1(p.id),
4885
4962
  name: p.name,
4886
4963
  company: p.company,
4887
4964
  role: p.role,
@@ -4892,7 +4969,7 @@ async function handleListPeople(args) {
4892
4969
  async function handleListTerms(args) {
4893
4970
  const context = await getContextInstance(args.contextDirectory);
4894
4971
  const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4895
- let terms = await loadTerms(context);
4972
+ let terms = dedupeByCanonicalEntityId(await loadTerms(context));
4896
4973
  if (scope) {
4897
4974
  terms = terms.filter((term) => hasScopedProjectReference(term.projects, scope.allowedProjectIds));
4898
4975
  }
@@ -4912,7 +4989,7 @@ async function handleListTerms(args) {
4912
4989
  offset,
4913
4990
  count: paginatedTerms.length,
4914
4991
  terms: paginatedTerms.map((t) => ({
4915
- id: t.id,
4992
+ id: resolveCanonicalEntityId$1(t.id),
4916
4993
  name: t.name,
4917
4994
  expansion: t.expansion,
4918
4995
  domain: t.domain,
@@ -4923,7 +5000,7 @@ async function handleListTerms(args) {
4923
5000
  async function handleListCompanies(args) {
4924
5001
  const context = await getContextInstance(args.contextDirectory);
4925
5002
  const scope = await buildProjectScopeState(context, args.allowedProjectIds);
4926
- let companies = await loadCompanies(context);
5003
+ let companies = dedupeByCanonicalEntityId(await loadCompanies(context));
4927
5004
  if (scope) {
4928
5005
  companies = companies.filter((company) => scope.associatedCompanies.has(company.id));
4929
5006
  }
@@ -4943,7 +5020,7 @@ async function handleListCompanies(args) {
4943
5020
  offset,
4944
5021
  count: paginatedCompanies.length,
4945
5022
  companies: paginatedCompanies.map((c) => ({
4946
- id: c.id,
5023
+ id: resolveCanonicalEntityId$1(c.id),
4947
5024
  name: c.name,
4948
5025
  fullName: c.fullName,
4949
5026
  industry: c.industry,
@@ -4971,28 +5048,29 @@ async function handleSearchContext(args) {
4971
5048
  async function handleGetEntity(args) {
4972
5049
  const context = await getContextInstance(args.contextDirectory);
4973
5050
  const scope = await buildProjectScopeState(context, args.allowedProjectIds);
5051
+ const entityId = resolveCanonicalEntityId$1(args.entityId.trim());
4974
5052
  let entity;
4975
5053
  switch (args.entityType) {
4976
5054
  case "project":
4977
- entity = findProjectResilient(context, args.entityId);
5055
+ entity = findProjectResilient(context, entityId);
4978
5056
  break;
4979
5057
  case "person":
4980
- entity = findPersonResilient(context, args.entityId);
5058
+ entity = findPersonResilient(context, entityId);
4981
5059
  break;
4982
5060
  case "term":
4983
- entity = findTermResilient(context, args.entityId);
5061
+ entity = findTermResilient(context, entityId);
4984
5062
  break;
4985
5063
  case "company":
4986
- entity = findCompanyResilient(context, args.entityId);
5064
+ entity = findCompanyResilient(context, entityId);
4987
5065
  break;
4988
5066
  case "ignored":
4989
- entity = findIgnoredResilient(context, args.entityId);
5067
+ entity = findIgnoredResilient(context, entityId);
4990
5068
  break;
4991
5069
  default:
4992
5070
  throw new Error(`Unknown entity type: ${args.entityType}`);
4993
5071
  }
4994
5072
  if (scope && !isEntityVisibleInProjectScope(entity, scope)) {
4995
- throw new Error(`Project-scoped key cannot access ${args.entityType} "${args.entityId}".`);
5073
+ throw new Error(`Project-scoped key cannot access ${args.entityType} "${entityId}".`);
4996
5074
  }
4997
5075
  const filePath = context.getEntityFilePath(entity);
4998
5076
  return {
@@ -5030,6 +5108,142 @@ async function handlePredictEntities(args) {
5030
5108
  });
5031
5109
  return { success: true, predictions };
5032
5110
  }
5111
+ const PROJECT_PLAN_ARRAY_KEYS = ["related_plans", "plans", "riotplan_plans"];
5112
+ function humanizePlanSlug(slug) {
5113
+ const trimmed = slug.trim();
5114
+ const withoutPrefix = trimmed.replace(/^[0-9a-f]{8}-/i, "");
5115
+ const words = withoutPrefix.split(/[-_/]+/).filter(Boolean);
5116
+ if (words.length === 0) {
5117
+ return trimmed;
5118
+ }
5119
+ return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(" ");
5120
+ }
5121
+ function pickString(obj, keys) {
5122
+ for (const k of keys) {
5123
+ const v = obj[k];
5124
+ if (typeof v === "string" && v.trim().length > 0) {
5125
+ return v.trim();
5126
+ }
5127
+ }
5128
+ return "";
5129
+ }
5130
+ function pickCreatedMs(obj) {
5131
+ const raw = pickString(obj, ["createdAt", "created_at", "created", "date", "startedAt", "started_at"]);
5132
+ if (!raw) {
5133
+ return NaN;
5134
+ }
5135
+ const t = Date.parse(raw);
5136
+ return Number.isNaN(t) ? NaN : t;
5137
+ }
5138
+ function normalizePlanRow(raw, index) {
5139
+ if (typeof raw === "string") {
5140
+ const id = raw.trim();
5141
+ return {
5142
+ id: id || `row-${index}`,
5143
+ title: humanizePlanSlug(id),
5144
+ stage: "",
5145
+ createdAt: null,
5146
+ createdMs: NaN
5147
+ };
5148
+ }
5149
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
5150
+ const o = raw;
5151
+ const id = pickString(o, ["id", "planId", "plan_id", "folder", "slug", "path", "name"]) || `row-${index}`;
5152
+ const explicitTitle = pickString(o, ["title", "label", "displayName", "planTitle", "summary"]);
5153
+ const title = explicitTitle.length > 0 ? explicitTitle : humanizePlanSlug(
5154
+ pickString(o, ["folder", "slug", "path", "id", "planId", "plan_id"]) || id
5155
+ );
5156
+ const stage = pickString(o, ["stage", "status", "state", "phase", "category"]);
5157
+ const createdRaw = pickString(o, ["createdAt", "created_at", "created", "date", "startedAt"]);
5158
+ const createdMs = pickCreatedMs(o);
5159
+ return {
5160
+ id,
5161
+ title,
5162
+ stage,
5163
+ createdAt: createdRaw || null,
5164
+ createdMs
5165
+ };
5166
+ }
5167
+ return {
5168
+ id: `row-${index}`,
5169
+ title: "",
5170
+ stage: "",
5171
+ createdAt: null,
5172
+ createdMs: NaN
5173
+ };
5174
+ }
5175
+ function collectPlanRowsFromProjectRecord(record) {
5176
+ const rows = [];
5177
+ for (const k of PROJECT_PLAN_ARRAY_KEYS) {
5178
+ const v = record[k];
5179
+ if (Array.isArray(v)) {
5180
+ rows.push(...v);
5181
+ }
5182
+ }
5183
+ return rows;
5184
+ }
5185
+ const listProjectPlansTool = {
5186
+ name: "protokoll_list_project_plans",
5187
+ description: "List plans associated with a project entity (from related_plans / plans / riotplan_plans on the project YAML). Supports limit/offset pagination. Used by clients instead of embedding full plan lists in the project resource.",
5188
+ inputSchema: {
5189
+ type: "object",
5190
+ properties: {
5191
+ projectId: {
5192
+ type: "string",
5193
+ description: "Project entity UUID"
5194
+ },
5195
+ limit: {
5196
+ type: "number",
5197
+ description: "Page size (default 25, max 100)"
5198
+ },
5199
+ offset: {
5200
+ type: "number",
5201
+ description: "Number of plans to skip (default 0)"
5202
+ },
5203
+ contextDirectory: {
5204
+ type: "string",
5205
+ description: "Path to the .protokoll context directory"
5206
+ }
5207
+ },
5208
+ required: ["projectId"]
5209
+ }
5210
+ };
5211
+ async function handleListProjectPlans(args) {
5212
+ const context = await getContextInstance(args.contextDirectory);
5213
+ const scope = await buildProjectScopeState(context, args.allowedProjectIds);
5214
+ const projectId = resolveCanonicalEntityId$1(args.projectId.trim());
5215
+ const entity = findProjectResilient(context, projectId);
5216
+ if (scope && !isEntityVisibleInProjectScope(entity, scope)) {
5217
+ throw new Error(`Project-scoped key cannot access project "${projectId}".`);
5218
+ }
5219
+ const record = entity;
5220
+ const allRows = collectPlanRowsFromProjectRecord(record);
5221
+ const normalized = allRows.map((r, i) => normalizePlanRow(r, i));
5222
+ normalized.sort((a, b) => {
5223
+ const da = Number.isNaN(a.createdMs) ? -Infinity : a.createdMs;
5224
+ const db = Number.isNaN(b.createdMs) ? -Infinity : b.createdMs;
5225
+ if (db !== da) {
5226
+ return db - da;
5227
+ }
5228
+ return a.title.localeCompare(b.title, void 0, { sensitivity: "base" });
5229
+ });
5230
+ const limit = Math.min(100, Math.max(1, args.limit ?? 25));
5231
+ const offset = Math.max(0, args.offset ?? 0);
5232
+ const page = normalized.slice(offset, offset + limit);
5233
+ const plans = page.map((p) => ({
5234
+ id: p.id,
5235
+ title: p.title,
5236
+ stage: p.stage,
5237
+ createdAt: p.createdAt
5238
+ }));
5239
+ return {
5240
+ total: normalized.length,
5241
+ limit,
5242
+ offset,
5243
+ count: plans.length,
5244
+ plans
5245
+ };
5246
+ }
5033
5247
 
5034
5248
  function isValidUUID(str) {
5035
5249
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -5234,7 +5448,7 @@ const addProjectTool = {
5234
5448
  };
5235
5449
  const editProjectTool = {
5236
5450
  name: "protokoll_edit_project",
5237
- description: "Edit an existing project with manual modifications. Unlike protokoll_update_project (which regenerates from a source), this allows direct edits: adding specific sounds_like variants, changing routing, modifying classification, managing relationships, etc. For array fields (sounds_like, topics, explicit_phrases, associated_people, associated_companies, children, siblings, related_terms), use add_* to append or remove_* to delete specific values, or use the base field name to replace the entire array.",
5451
+ description: "Edit an existing project with manual modifications. Unlike protokoll_update_project (which regenerates from a source), this allows direct edits: adding specific sounds_like variants, changing routing, modifying classification, managing relationships, etc. For array fields (sounds_like, urls, topics, explicit_phrases, associated_people, associated_companies, children, siblings, related_terms), use add_* to append or remove_* to delete specific values, or use the base field name to replace the entire array.",
5238
5452
  inputSchema: {
5239
5453
  type: "object",
5240
5454
  properties: {
@@ -5283,6 +5497,21 @@ const editProjectTool = {
5283
5497
  items: { type: "string" },
5284
5498
  description: "Remove these sounds_like variants"
5285
5499
  },
5500
+ urls: {
5501
+ type: "array",
5502
+ items: { type: "string" },
5503
+ description: "Replace all project URLs with this list (e.g. org homepage, documentation)"
5504
+ },
5505
+ add_urls: {
5506
+ type: "array",
5507
+ items: { type: "string" },
5508
+ description: "Append these URLs to the project urls list"
5509
+ },
5510
+ remove_urls: {
5511
+ type: "array",
5512
+ items: { type: "string" },
5513
+ description: "Remove these URLs from the project urls list"
5514
+ },
5286
5515
  topics: {
5287
5516
  type: "array",
5288
5517
  items: { type: "string" },
@@ -5823,12 +6052,19 @@ async function handleEditProject(args) {
5823
6052
  const context = await createToolContext(args.contextDirectory);
5824
6053
  await assertContextAvailableForEntityEdit(context);
5825
6054
  const existingProject = findProjectResilient(context, args.id);
6055
+ const existingProjectUrls = existingProject.urls;
5826
6056
  const updatedSoundsLike = mergeArray(
5827
6057
  existingProject.sounds_like,
5828
6058
  args.sounds_like,
5829
6059
  args.add_sounds_like,
5830
6060
  args.remove_sounds_like
5831
6061
  );
6062
+ const updatedUrls = mergeArray(
6063
+ existingProjectUrls,
6064
+ args.urls,
6065
+ args.add_urls,
6066
+ args.remove_urls
6067
+ );
5832
6068
  const updatedTopics = mergeArray(
5833
6069
  existingProject.classification?.topics,
5834
6070
  args.topics,
@@ -5916,6 +6152,11 @@ async function handleEditProject(args) {
5916
6152
  } else if (existingProject.sounds_like && (args.sounds_like !== void 0 || args.remove_sounds_like)) {
5917
6153
  delete updatedProject.sounds_like;
5918
6154
  }
6155
+ if (updatedUrls !== void 0) {
6156
+ updatedProject.urls = updatedUrls;
6157
+ } else if (existingProjectUrls && (args.urls !== void 0 || args.remove_urls)) {
6158
+ delete updatedProject.urls;
6159
+ }
5919
6160
  if (updatedTopics !== void 0) {
5920
6161
  updatedProject.classification.topics = updatedTopics;
5921
6162
  } else if (existingProject.classification?.topics && (args.topics !== void 0 || args.remove_topics)) {
@@ -5964,6 +6205,9 @@ async function handleEditProject(args) {
5964
6205
  if (args.sounds_like !== void 0) changes.push(`sounds_like replaced with ${args.sounds_like.length} items`);
5965
6206
  if (args.add_sounds_like?.length) changes.push(`added ${args.add_sounds_like.length} sounds_like variants`);
5966
6207
  if (args.remove_sounds_like?.length) changes.push(`removed ${args.remove_sounds_like.length} sounds_like variants`);
6208
+ if (args.urls !== void 0) changes.push(`urls replaced with ${args.urls.length} items`);
6209
+ if (args.add_urls?.length) changes.push(`added ${args.add_urls.length} urls`);
6210
+ if (args.remove_urls?.length) changes.push(`removed ${args.remove_urls.length} urls`);
5967
6211
  if (args.topics !== void 0) changes.push(`topics replaced with ${args.topics.length} items`);
5968
6212
  if (args.add_topics?.length) changes.push(`added ${args.add_topics.length} topics`);
5969
6213
  if (args.remove_topics?.length) changes.push(`removed ${args.remove_topics.length} topics`);
@@ -9935,6 +10179,7 @@ const tools = [
9935
10179
  // Context Management
9936
10180
  contextStatusTool,
9937
10181
  listProjectsTool,
10182
+ listProjectPlansTool,
9938
10183
  listPeopleTool,
9939
10184
  listTermsTool,
9940
10185
  listCompaniesTool,
@@ -10019,6 +10264,10 @@ async function handleToolCall(name, args) {
10019
10264
  return handleContextStatus(args);
10020
10265
  case "protokoll_list_projects":
10021
10266
  return handleListProjects(args);
10267
+ case "protokoll_list_project_plans":
10268
+ return handleListProjectPlans(
10269
+ args
10270
+ );
10022
10271
  case "protokoll_list_people":
10023
10272
  return handleListPeople(args);
10024
10273
  case "protokoll_list_terms":