@firfi/huly-mcp 0.38.0 → 0.39.0

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 (3) hide show
  1. package/README.md +3 -2
  2. package/dist/index.cjs +276 -167
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
- # @firfi/huly-mcp
1
+ # Huly MCP
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@firfi/huly-mcp)](https://www.npmjs.com/package/@firfi/huly-mcp)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@firfi/huly-mcp)](https://www.npmjs.com/package/@firfi/huly-mcp)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![MCP](https://img.shields.io/badge/MCP-compatible-blue)](https://modelcontextprotocol.io)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
8
+ [![Glama score](https://glama.ai/mcp/servers/@dearlordylord/huly-mcp/badges/score.svg)](https://glama.ai/mcp/servers/@dearlordylord/huly-mcp)
8
9
  [![MCP Server](https://badge.mcpx.dev?type=server&features=tools)](https://github.com/dearlordylord/huly-mcp)[![cooked at Monadical](https://img.shields.io/endpoint?url=https://monadical.com/static/api/cooked-at-monadical.json)](https://monadical.com)
9
10
 
10
- MCP server for [Huly](https://huly.io/) integration.
11
+ **Huly MCP** is a feature-complete MCP server for [Huly](https://huly.io/) integration. Published on npm as [`@firfi/huly-mcp`](https://www.npmjs.com/package/@firfi/huly-mcp).
11
12
 
12
13
  ## Installation
13
14
 
package/dist/index.cjs CHANGED
@@ -73778,7 +73778,7 @@ var require_index_cjs4 = __commonJS({
73778
73778
  let canOpen = true;
73779
73779
  let canClose = true;
73780
73780
  pos = t.index + 1;
73781
- const isSingle2 = t[0] === "'";
73781
+ const isSingle3 = t[0] === "'";
73782
73782
  let lastChar = 32;
73783
73783
  if (t.index - 1 >= 0) {
73784
73784
  lastChar = text2.charCodeAt(t.index - 1);
@@ -73829,7 +73829,7 @@ var require_index_cjs4 = __commonJS({
73829
73829
  canClose = isNextPunctChar;
73830
73830
  }
73831
73831
  if (!canOpen && !canClose) {
73832
- if (isSingle2) {
73832
+ if (isSingle3) {
73833
73833
  token.content = replaceAt(token.content, t.index, APOSTROPHE);
73834
73834
  }
73835
73835
  continue;
@@ -73840,11 +73840,11 @@ var require_index_cjs4 = __commonJS({
73840
73840
  if (stack[j].level < thisLevel) {
73841
73841
  break;
73842
73842
  }
73843
- if (item.single === isSingle2 && stack[j].level === thisLevel) {
73843
+ if (item.single === isSingle3 && stack[j].level === thisLevel) {
73844
73844
  item = stack[j];
73845
73845
  let openQuote;
73846
73846
  let closeQuote;
73847
- if (isSingle2) {
73847
+ if (isSingle3) {
73848
73848
  openQuote = state.md.options.quotes[2];
73849
73849
  closeQuote = state.md.options.quotes[3];
73850
73850
  } else {
@@ -73868,10 +73868,10 @@ var require_index_cjs4 = __commonJS({
73868
73868
  stack.push({
73869
73869
  token: i,
73870
73870
  pos: t.index,
73871
- single: isSingle2,
73871
+ single: isSingle3,
73872
73872
  level: thisLevel
73873
73873
  });
73874
- } else if (canClose && isSingle2) {
73874
+ } else if (canClose && isSingle3) {
73875
73875
  token.content = replaceAt(token.content, t.index, APOSTROPHE);
73876
73876
  }
73877
73877
  }
@@ -158094,6 +158094,30 @@ var validatePersonUuid = (uuid5) => {
158094
158094
  var import_promises = require("node:dns/promises");
158095
158095
  var http = __toESM(require("node:http"), 1);
158096
158096
  var https = __toESM(require("node:https"), 1);
158097
+
158098
+ // src/utils/assertions.ts
158099
+ var AssertionError = class extends Error {
158100
+ _tag = "AssertionError";
158101
+ constructor(message) {
158102
+ super(message);
158103
+ this.name = "AssertionError";
158104
+ }
158105
+ };
158106
+ var assertExists = (value3, message) => {
158107
+ if (value3 === null || value3 === void 0) {
158108
+ throw new AssertionError(message ?? "Expected value to exist");
158109
+ }
158110
+ return value3;
158111
+ };
158112
+ var isExistent = (value3) => value3 !== null && value3 !== void 0;
158113
+ var assertAt = (arr, index, message) => {
158114
+ return assertExists(arr[index], message ?? `Expected array item at index ${index}`);
158115
+ };
158116
+ var isNonEmpty7 = (arr) => arr.length > 0;
158117
+ var isSingle2 = (arr) => arr.length === 1;
158118
+ var isPair = (arr) => arr.length === 2;
158119
+
158120
+ // src/huly/url-fetch.ts
158097
158121
  var FETCH_TIMEOUT_MS = 3e4;
158098
158122
  var HTTP_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
158099
158123
  var IPV4_OCTET_COUNT = 4;
@@ -158150,10 +158174,14 @@ var parseIpv4Address = (hostname3) => {
158150
158174
  return null;
158151
158175
  }
158152
158176
  const octets = parts2.map(Number);
158177
+ const a = assertAt(octets, 0);
158178
+ const b = assertAt(octets, 1);
158179
+ const c = assertAt(octets, 2);
158180
+ const d = assertAt(octets, 3);
158153
158181
  if (octets.some((octet) => !Number.isInteger(octet) || octet < 0 || octet > IPV4_MAX_OCTET)) {
158154
158182
  return null;
158155
158183
  }
158156
- return [octets[0], octets[1], octets[2], octets[3]];
158184
+ return [a, b, c, d];
158157
158185
  };
158158
158186
  var isBlockedIpv4Address = ([a, b, c]) => {
158159
158187
  if (a === 0) return true;
@@ -158178,8 +158206,10 @@ var ipv4FromMappedIpv6Hextets = (hostname3) => {
158178
158206
  if (match16 === null) {
158179
158207
  return null;
158180
158208
  }
158181
- const high = Number.parseInt(match16[1], 16);
158182
- const low = Number.parseInt(match16[2], 16);
158209
+ const highText = assertAt(match16, 1);
158210
+ const lowText = assertAt(match16, 2);
158211
+ const high = Number.parseInt(highText, 16);
158212
+ const low = Number.parseInt(lowText, 16);
158183
158213
  return [
158184
158214
  Math.floor(high / BYTE_BASE),
158185
158215
  high % BYTE_BASE,
@@ -158204,6 +158234,17 @@ var parseIpv6Side = (value3) => {
158204
158234
  });
158205
158235
  return hextets.length === parts2.length ? hextets : null;
158206
158236
  };
158237
+ var isIpv6Hextets = (hextets) => hextets.length === IPV6_HEXTET_COUNT;
158238
+ var toIpv6Hextets = (hextets) => [
158239
+ assertAt(hextets, 0),
158240
+ assertAt(hextets, 1),
158241
+ assertAt(hextets, 2),
158242
+ assertAt(hextets, 3),
158243
+ assertAt(hextets, 4),
158244
+ assertAt(hextets, 5),
158245
+ assertAt(hextets, 6),
158246
+ assertAt(hextets, 7)
158247
+ ];
158207
158248
  var parseIpv6Hextets = (hostname3) => {
158208
158249
  if (hostname3.includes(".")) {
158209
158250
  return null;
@@ -158212,26 +158253,29 @@ var parseIpv6Hextets = (hostname3) => {
158212
158253
  if (compressedParts.length > 2) {
158213
158254
  return null;
158214
158255
  }
158215
- const left3 = parseIpv6Side(compressedParts[0]);
158216
- const right3 = compressedParts.length === 2 ? parseIpv6Side(compressedParts[1]) : [];
158256
+ const leftText = assertAt(compressedParts, 0);
158257
+ const left3 = parseIpv6Side(leftText);
158258
+ const right3 = compressedParts.length === 2 ? parseIpv6Side(assertAt(compressedParts, 1)) : [];
158217
158259
  if (left3 === null || right3 === null) {
158218
158260
  return null;
158219
158261
  }
158220
158262
  if (compressedParts.length === 1) {
158221
- return left3.length === IPV6_HEXTET_COUNT ? left3 : null;
158263
+ return isIpv6Hextets(left3) ? left3 : null;
158222
158264
  }
158223
158265
  const zeroCount = IPV6_HEXTET_COUNT - left3.length - right3.length;
158224
158266
  if (zeroCount < 1) {
158225
158267
  return null;
158226
158268
  }
158227
- return [...left3, ...Array.from({ length: zeroCount }, () => 0), ...right3];
158269
+ const hextets = [...left3, ...Array.from({ length: zeroCount }, () => 0), ...right3];
158270
+ return toIpv6Hextets(hextets);
158228
158271
  };
158229
158272
  var isIpv6InPrefix = (hextets, prefix) => {
158230
158273
  const fullHextetCount = Math.floor(prefix.prefixLength / IPV6_HEXTET_BITS);
158231
158274
  const remainingBits = prefix.prefixLength % IPV6_HEXTET_BITS;
158232
158275
  let index = 0;
158233
158276
  for (const prefixHextet2 of prefix.hextets.slice(0, fullHextetCount)) {
158234
- if (hextets[index] !== prefixHextet2) {
158277
+ const addressHextet2 = assertAt(hextets, index);
158278
+ if (addressHextet2 !== prefixHextet2) {
158235
158279
  return false;
158236
158280
  }
158237
158281
  index += 1;
@@ -158239,8 +158283,8 @@ var isIpv6InPrefix = (hextets, prefix) => {
158239
158283
  if (remainingBits === 0) {
158240
158284
  return true;
158241
158285
  }
158242
- const prefixHextet = prefix.hextets[fullHextetCount];
158243
- const addressHextet = hextets[fullHextetCount];
158286
+ const prefixHextet = assertAt(prefix.hextets, fullHextetCount);
158287
+ const addressHextet = assertAt(hextets, fullHextetCount);
158244
158288
  const mask = IPV6_HEXTET_MAX << IPV6_HEXTET_BITS - remainingBits & IPV6_HEXTET_MAX;
158245
158289
  return (addressHextet & mask) === (prefixHextet & mask);
158246
158290
  };
@@ -158248,7 +158292,7 @@ var requestFirstSuccessfulAddress = async (url4, dependencies, addresses, index
158248
158292
  if (index >= addresses.length) {
158249
158293
  throw new Error(`Request failed for all resolved addresses: ${failures3.join("; ")}`);
158250
158294
  }
158251
- const address = addresses[index];
158295
+ const address = assertAt(addresses, index);
158252
158296
  try {
158253
158297
  return await dependencies.requestUrl(url4, address);
158254
158298
  } catch (error2) {
@@ -168592,8 +168636,9 @@ var mapParseCauseToMcp = (cause3, toolName) => {
168592
168636
  return mapParseErrorToMcp(cause3.error, toolName);
168593
168637
  }
168594
168638
  const failures3 = Chunk_exports.toArray(Cause_exports.failures(cause3));
168595
- if (failures3.length > 0) {
168596
- return mapParseErrorToMcp(failures3[0], toolName);
168639
+ const firstFailure = failures3[0];
168640
+ if (firstFailure !== void 0) {
168641
+ return mapParseErrorToMcp(firstFailure, toolName);
168597
168642
  }
168598
168643
  return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError);
168599
168644
  };
@@ -168605,8 +168650,9 @@ var mapDomainCauseToMcp = (cause3, warnings = []) => {
168605
168650
  return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, "UnexpectedError", warnings);
168606
168651
  }
168607
168652
  const failures3 = Chunk_exports.toArray(Cause_exports.failures(cause3));
168608
- if (failures3.length > 0) {
168609
- return mapDomainErrorToMcp(failures3[0], warnings);
168653
+ const firstFailure = failures3[0];
168654
+ if (firstFailure !== void 0) {
168655
+ return mapDomainErrorToMcp(firstFailure, warnings);
168610
168656
  }
168611
168657
  return createErrorResponse("An unexpected error occurred", McpErrorCode.InternalError, void 0, warnings);
168612
168658
  };
@@ -168688,7 +168734,7 @@ var httpStatusForErrorCode = (code) => {
168688
168734
  var acceptsModernResponseTypes = (req) => {
168689
168735
  const accept = firstHeader(req.headers.accept);
168690
168736
  if (accept === void 0) return false;
168691
- const parts2 = accept.split(",").map((part) => part.split(";")[0].trim().toLowerCase());
168737
+ const parts2 = accept.split(",").map((part) => (part.split(";")[0] ?? "").trim().toLowerCase());
168692
168738
  return parts2.includes("application/json") && parts2.includes("text/event-stream");
168693
168739
  };
168694
168740
  var validateJsonRpcRequest = (body) => {
@@ -178446,10 +178492,44 @@ var makeDiagnosticsScope = Effect_exports.gen(function* () {
178446
178492
  });
178447
178493
 
178448
178494
  // src/version.ts
178449
- var VERSION = true ? "0.38.0" : "0.0.0-dev";
178495
+ var VERSION = true ? "0.39.0" : "0.0.0-dev";
178496
+
178497
+ // src/mcp/json-schema-refs.ts
178498
+ var isJsonObject = (value3) => typeof value3 === "object" && value3 !== null && !Array.isArray(value3);
178499
+ var mergeDefinitionRecords = (definitions) => {
178500
+ const merged = definitions.reduce(
178501
+ (acc, definition) => definition === void 0 ? acc : { ...acc, ...definition },
178502
+ {}
178503
+ );
178504
+ return Object.keys(merged).length > 0 ? merged : void 0;
178505
+ };
178506
+ var collectJsonSchemaDefinitions = (value3) => {
178507
+ if (Array.isArray(value3)) {
178508
+ return mergeDefinitionRecords(value3.map(collectJsonSchemaDefinitions));
178509
+ }
178510
+ if (!isJsonObject(value3)) return void 0;
178511
+ const ownDefinitions = isJsonObject(value3.$defs) ? value3.$defs : void 0;
178512
+ const nestedDefinitions = Object.entries(value3).filter(([key]) => key !== "$defs").map(([, nested6]) => collectJsonSchemaDefinitions(nested6));
178513
+ return mergeDefinitionRecords([...nestedDefinitions, ownDefinitions]);
178514
+ };
178515
+ var omitJsonSchemaDocumentMetadata = (schema) => Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$defs" && key !== "$schema"));
178450
178516
 
178451
178517
  // src/mcp/tool-output-schema.ts
178452
178518
  var toolWarningCodeEnum = [...ToolWarningCodeSchema.literals];
178519
+ var wrapResultOutputSchema = (resultSchema) => {
178520
+ const resultDefs = collectJsonSchemaDefinitions(resultSchema);
178521
+ const resultJsonSchemaDialect = "$schema" in resultSchema ? resultSchema["$schema"] : void 0;
178522
+ const embeddedResultSchema = omitJsonSchemaDocumentMetadata(resultSchema);
178523
+ return {
178524
+ ...resultJsonSchemaDialect === void 0 ? {} : { $schema: resultJsonSchemaDialect },
178525
+ ...resultDefs === void 0 ? {} : { $defs: resultDefs },
178526
+ type: "object",
178527
+ properties: {
178528
+ result: embeddedResultSchema
178529
+ },
178530
+ required: ["result"]
178531
+ };
178532
+ };
178453
178533
  var defaultToolOutputSchema = {
178454
178534
  type: "object",
178455
178535
  properties: {
@@ -178478,27 +178558,15 @@ var defaultToolOutputSchema = {
178478
178558
  },
178479
178559
  required: ["result"]
178480
178560
  };
178481
- var versionToolOutputSchema = {
178561
+ var versionToolOutputSchema = wrapResultOutputSchema({
178482
178562
  type: "object",
178483
178563
  properties: {
178484
- result: {
178485
- type: "object",
178486
- properties: {
178487
- current: { type: "string", minLength: 1 },
178488
- latest: { type: "string", minLength: 1 }
178489
- },
178490
- required: ["current", "latest"]
178491
- }
178564
+ current: { type: "string", minLength: 1 },
178565
+ latest: { type: "string", minLength: 1 }
178492
178566
  },
178493
- required: ["result"]
178494
- };
178495
- var hulyContextToolOutputSchema = {
178496
- type: "object",
178497
- properties: {
178498
- result: getHulyContextResultJsonSchema
178499
- },
178500
- required: ["result"]
178501
- };
178567
+ required: ["current", "latest"]
178568
+ });
178569
+ var hulyContextToolOutputSchema = wrapResultOutputSchema(getHulyContextResultJsonSchema);
178502
178570
 
178503
178571
  // src/huly/operations/activity-messages.ts
178504
178572
  var import_core11 = __toESM(require_lib5(), 1);
@@ -178799,7 +178867,7 @@ var findPersonByEmail = (client, email3) => Effect_exports.gen(function* () {
178799
178867
  provider: contact.channelProvider.Email
178800
178868
  }
178801
178869
  );
178802
- if (channels.length === 0) {
178870
+ if (!isNonEmpty7(channels)) {
178803
178871
  return void 0;
178804
178872
  }
178805
178873
  const channel = channels[0];
@@ -179089,7 +179157,7 @@ var findSpace = (client, params) => Effect_exports.gen(function* () {
179089
179157
  }))
179090
179158
  });
179091
179159
  }
179092
- return matches[0];
179160
+ return assertAt(matches, 0);
179093
179161
  });
179094
179162
  var updateSpaceDoc = (client, space, operations) => client.updateDoc(
179095
179163
  toClassRef(space._class),
@@ -179299,7 +179367,8 @@ var findDirectMessage = (identifier2) => Effect_exports.gen(function* () {
179299
179367
  if (matches.length > 1) {
179300
179368
  return yield* new DirectMessageIdentifierAmbiguousError({ identifier: identifier2, matches: Count.make(matches.length) });
179301
179369
  }
179302
- return { client, dm: matches[0] };
179370
+ if (isSingle2(matches)) return { client, dm: matches[0] };
179371
+ return yield* new DirectMessageNotFoundError({ identifier: identifier2 });
179303
179372
  });
179304
179373
  var createDirectMessageSpace = (client, members) => Effect_exports.gen(function* () {
179305
179374
  const dmId = (0, import_core14.generateId)();
@@ -180346,9 +180415,13 @@ var parseIssueIdentifier = (identifier2, projectIdentifier) => {
180346
180415
  const idStr = String(identifier2).trim();
180347
180416
  const match16 = idStr.match(/^([A-Z]+)-(\d+)$/i);
180348
180417
  if (match16) {
180418
+ const [, projectPrefix, issueNumber] = match16;
180419
+ if (projectPrefix === void 0 || issueNumber === void 0) {
180420
+ return { fullIdentifier: `${projectIdentifier}-${idStr}`, number: null };
180421
+ }
180349
180422
  return {
180350
- fullIdentifier: `${match16[1].toUpperCase()}-${match16[2]}`,
180351
- number: parseInt(match16[2], 10)
180423
+ fullIdentifier: `${projectPrefix.toUpperCase()}-${issueNumber}`,
180424
+ number: parseInt(issueNumber, 10)
180352
180425
  };
180353
180426
  }
180354
180427
  const numMatch = idStr.match(/^\d+$/);
@@ -181566,12 +181639,12 @@ var resolveBoard = (client, identifier2, options = {}) => Effect_exports.gen(fun
181566
181639
  board.class.Board,
181567
181640
  hulyQuery({ ...baseQuery, _id: toRef(identifier2) })
181568
181641
  );
181569
- if (idMatches.length > 0) return idMatches[0];
181642
+ if (isNonEmpty7(idMatches)) return idMatches[0];
181570
181643
  const nameMatches = yield* client.findAll(
181571
181644
  board.class.Board,
181572
181645
  hulyQuery({ ...baseQuery, name: identifier2 })
181573
181646
  );
181574
- if (nameMatches.length === 1) return nameMatches[0];
181647
+ if (isSingle2(nameMatches)) return nameMatches[0];
181575
181648
  if (nameMatches.length > 1) {
181576
181649
  return yield* new BoardIdentifierAmbiguousError({ identifier: identifier2, matches: nameMatches.length });
181577
181650
  }
@@ -181587,7 +181660,7 @@ var resolveBoardProjectType = (client, projectTypeRef) => Effect_exports.gen(fun
181587
181660
  const matches = projectTypeRef === void 0 ? projectTypes.filter(isBoardProjectType) : projectTypes.filter(
181588
181661
  (projectType) => isBoardProjectType(projectType) && (projectType._id === projectTypeRef || projectType.name === projectTypeRef)
181589
181662
  );
181590
- if (matches.length === 1) return matches[0];
181663
+ if (isSingle2(matches)) return matches[0];
181591
181664
  const identifier2 = projectTypeRef ?? String(board.descriptors.BoardType);
181592
181665
  if (matches.length === 0) return yield* new BoardProjectTypeNotFoundError({ identifier: identifier2 });
181593
181666
  return yield* new BoardProjectTypeIdentifierAmbiguousError({ identifier: identifier2, matches: matches.length });
@@ -181617,7 +181690,7 @@ var resolveBoardTaskType = (client, resolvedBoard, projectType, taskTypeRef) =>
181617
181690
  const taskTypes = yield* getBoardTaskTypes(client, projectType);
181618
181691
  const matches = taskTypeRef === void 0 ? taskTypes.filter(isBoardCardTaskType) : taskTypes.filter((taskType) => taskType._id === taskTypeRef || taskType.name === taskTypeRef);
181619
181692
  const fallbackMatches = taskTypeRef === void 0 && matches.length === 0 && taskTypes.length === 1 ? taskTypes : matches;
181620
- if (fallbackMatches.length === 1) return fallbackMatches[0];
181693
+ if (isSingle2(fallbackMatches)) return fallbackMatches[0];
181621
181694
  const identifier2 = taskTypeRef ?? "default board card task type";
181622
181695
  if (fallbackMatches.length === 0) {
181623
181696
  return yield* new BoardTaskTypeNotFoundError({ identifier: identifier2, board: resolvedBoard.name });
@@ -181646,7 +181719,7 @@ var getBoardWorkflowStatuses = (client, projectType, taskType) => Effect_exports
181646
181719
  var resolveBoardStatus = (client, resolvedBoard, projectType, taskType, statusRef) => Effect_exports.gen(function* () {
181647
181720
  const statuses = yield* getBoardWorkflowStatuses(client, projectType, taskType);
181648
181721
  const matches = statusRef === void 0 ? statuses.slice(0, 1) : statuses.filter((status) => status.id === statusRef || status.name === statusRef);
181649
- if (matches.length === 1) return matches[0];
181722
+ if (isSingle2(matches)) return matches[0];
181650
181723
  const identifier2 = statusRef ?? "default board card status";
181651
181724
  if (matches.length === 0) return yield* new BoardStatusNotFoundError({ identifier: identifier2, board: resolvedBoard.name });
181652
181725
  return yield* new BoardStatusIdentifierAmbiguousError({
@@ -181726,14 +181799,14 @@ var resolveBoardCard = (client, resolvedBoard, identifier2) => Effect_exports.ge
181726
181799
  board.class.Card,
181727
181800
  hulyQuery({ space, _id: toRef(identifier2) })
181728
181801
  );
181729
- if (idMatches.length > 0) return idMatches[0];
181802
+ if (isNonEmpty7(idMatches)) return idMatches[0];
181730
181803
  const number8 = boardCardNumber(identifier2);
181731
181804
  if (number8 !== void 0) {
181732
181805
  const numberMatches = yield* client.findAll(
181733
181806
  board.class.Card,
181734
181807
  hulyQuery({ space, number: number8 })
181735
181808
  );
181736
- if (numberMatches.length === 1) return numberMatches[0];
181809
+ if (isSingle2(numberMatches)) return numberMatches[0];
181737
181810
  if (numberMatches.length > 1) {
181738
181811
  return yield* new BoardCardIdentifierAmbiguousError({
181739
181812
  identifier: identifier2,
@@ -181746,7 +181819,7 @@ var resolveBoardCard = (client, resolvedBoard, identifier2) => Effect_exports.ge
181746
181819
  board.class.Card,
181747
181820
  hulyQuery({ space, title: identifier2 })
181748
181821
  );
181749
- if (titleMatches.length === 1) return titleMatches[0];
181822
+ if (isSingle2(titleMatches)) return titleMatches[0];
181750
181823
  if (titleMatches.length > 1) {
181751
181824
  return yield* new BoardCardIdentifierAmbiguousError({
181752
181825
  identifier: identifier2,
@@ -181867,8 +181940,9 @@ var createBoard = (params) => Effect_exports.gen(function* () {
181867
181940
  board.class.Board,
181868
181941
  hulyQuery({ name: params.name, archived: false })
181869
181942
  );
181870
- if (existing.length === 1) {
181871
- return { id: BoardId.make(existing[0]._id), name: BoardName.make(existing[0].name), created: false };
181943
+ if (isSingle2(existing)) {
181944
+ const boardDoc = existing[0];
181945
+ return { id: BoardId.make(boardDoc._id), name: BoardName.make(boardDoc.name), created: false };
181872
181946
  }
181873
181947
  if (existing.length > 1) {
181874
181948
  return yield* new BoardIdentifierAmbiguousError({ identifier: params.name, matches: existing.length });
@@ -182508,24 +182582,6 @@ var import_core29 = __toESM(require_lib5(), 1);
182508
182582
  var import_core28 = __toESM(require_lib5(), 1);
182509
182583
  var import_rank3 = __toESM(require_lib35(), 1);
182510
182584
  var import_time2 = __toESM(require_lib38(), 1);
182511
-
182512
- // src/utils/assertions.ts
182513
- var AssertionError = class extends Error {
182514
- _tag = "AssertionError";
182515
- constructor(message) {
182516
- super(message);
182517
- this.name = "AssertionError";
182518
- }
182519
- };
182520
- var assertExists = (value3, message) => {
182521
- if (value3 === null || value3 === void 0) {
182522
- throw new AssertionError(message ?? "Expected value to exist");
182523
- }
182524
- return value3;
182525
- };
182526
- var isExistent = (value3) => value3 !== null && value3 !== void 0;
182527
-
182528
- // src/huly/operations/planner-shared.ts
182529
182585
  var todoLookup = {
182530
182586
  user: contact.class.Person,
182531
182587
  attachedTo: tracker.class.Issue
@@ -182684,7 +182740,7 @@ var findTodo = (client, locator, defaultCompletionState = "open") => Effect_expo
182684
182740
  if (todos.length > 1) {
182685
182741
  return yield* new TodoIdentifierAmbiguousError({ locator: describeLocator(locator), matches: todos.length });
182686
182742
  }
182687
- return todos[0];
182743
+ return assertAt(todos, 0);
182688
182744
  });
182689
182745
  var todoOwnerSummary = (todo, emailByPersonId) => {
182690
182746
  const ownerId = toRef(todo.user);
@@ -182700,11 +182756,14 @@ var todoAttachmentSummary = (todo) => {
182700
182756
  if (todo.attachedTo === time.ids.NotAttached) return { type: "none" };
182701
182757
  const issue2 = todo.$lookup?.attachedTo;
182702
182758
  if (todo.attachedToClass === tracker.class.Issue && issue2 !== void 0) {
182759
+ const identifier2 = IssueIdentifier.make(issue2.identifier);
182760
+ const segments = identifier2.split("-");
182761
+ const project3 = assertAt(segments, 0);
182703
182762
  return {
182704
182763
  type: "issue",
182705
182764
  id: IssueId.make(issue2._id),
182706
- project: ProjectIdentifier.make(IssueIdentifier.make(issue2.identifier).split("-")[0]),
182707
- identifier: IssueIdentifier.make(issue2.identifier),
182765
+ project: ProjectIdentifier.make(project3),
182766
+ identifier: identifier2,
182708
182767
  title: attachmentTitleOrFallback(issue2.title, issue2.identifier)
182709
182768
  };
182710
182769
  }
@@ -183381,7 +183440,7 @@ var HULY_MODEL_ID_SEPARATOR = ":";
183381
183440
  var FINAL_SEGMENT_OFFSET = 1;
183382
183441
  var hulyModelLabelTail = (value3) => {
183383
183442
  const segments = value3.split(HULY_MODEL_ID_SEPARATOR);
183384
- return segments[segments.length - FINAL_SEGMENT_OFFSET];
183443
+ return assertAt(segments, segments.length - FINAL_SEGMENT_OFFSET);
183385
183444
  };
183386
183445
  var decodeHulyModelLabelTail = (value3) => Either_exports.flatMap(
183387
183446
  Schema_exports.decodeUnknownEither(Schema_exports.String)(value3),
@@ -183827,6 +183886,9 @@ var collectDescendants = (rootIds, descendantsByAncestor) => {
183827
183886
  let nextIndex = 0;
183828
183887
  while (nextIndex < pending3.length) {
183829
183888
  const current = pending3[nextIndex];
183889
+ if (current === void 0) {
183890
+ break;
183891
+ }
183830
183892
  const unvisitedChildren = [...descendantsByAncestor.get(current) ?? []].filter((childId) => !visited.has(childId));
183831
183893
  unvisitedChildren.forEach((childId) => {
183832
183894
  visited.add(childId);
@@ -185357,6 +185419,28 @@ var commentTools = [
185357
185419
  }
185358
185420
  ];
185359
185421
 
185422
+ // src/huly/operations/contact-channel-locators.ts
185423
+ var channelIdentifier = (locator) => locator._tag === "channelId" ? locator.channelId : `${locator.provider}:${locator.value}`;
185424
+ var INVALID_CHANNEL_LOCATOR_REASON = "provide exactly one channel locator: channelId, or provider plus value";
185425
+ var validateProviderValue = (provider, value3) => value3.length === 0 || provider === "email" && !Schema_exports.is(Email)(value3) ? Effect_exports.fail(new InvalidContactChannelValueError({ provider, value: value3 })) : Effect_exports.void;
185426
+ var validateChannelLocator2 = (ownerIdentifier, locator) => Effect_exports.gen(function* () {
185427
+ const hasChannelId = locator.channelId !== void 0;
185428
+ const hasProvider = locator.provider !== void 0;
185429
+ const hasValue = locator.value !== void 0;
185430
+ if (hasChannelId && !hasProvider && !hasValue) {
185431
+ return { _tag: "channelId", channelId: ChannelId.make(locator.channelId) };
185432
+ }
185433
+ if (!hasChannelId && hasProvider && hasValue) {
185434
+ yield* validateProviderValue(locator.provider, locator.value);
185435
+ return { _tag: "providerValue", provider: locator.provider, value: locator.value };
185436
+ }
185437
+ return yield* new InvalidContactChannelLocatorError({
185438
+ ownerIdentifier,
185439
+ reason: INVALID_CHANNEL_LOCATOR_REASON
185440
+ });
185441
+ });
185442
+ var requireChannelUpdateFields = (params) => params.newProvider === void 0 && params.newValue === void 0 ? Effect_exports.fail(new NoUpdateFieldsError({ operation: "update_contact_channel", fields: ["newProvider", "newValue"] })) : Effect_exports.void;
185443
+
185360
185444
  // src/huly/operations/contact-channel-providers.ts
185361
185445
  var CONTACT_CHANNEL_PROVIDER_SDK_KEYS = ContactChannelProviderSdkKeys;
185362
185446
  var exactChannelProviderSdkKeys = (value3) => value3;
@@ -185401,13 +185485,11 @@ var channelSummaryWithValue = (channel, provider, value3) => ({
185401
185485
 
185402
185486
  // src/huly/operations/organization-resolvers.ts
185403
185487
  var findOrganizationByIdentifier = (client, identifier2) => Effect_exports.gen(function* () {
185404
- const byId = Option_exports.fromNullable(
185405
- yield* client.findOne(
185406
- contact.class.Organization,
185407
- hulyQuery({ _id: toRef(identifier2) })
185408
- )
185488
+ const byId = yield* client.findOne(
185489
+ contact.class.Organization,
185490
+ hulyQuery({ _id: toRef(identifier2) })
185409
185491
  );
185410
- if (Option_exports.isSome(byId)) return byId;
185492
+ if (byId !== void 0) return Option_exports.some(byId);
185411
185493
  const byName = yield* client.findAll(
185412
185494
  contact.class.Organization,
185413
185495
  hulyQuery({ name: identifier2 })
@@ -185419,7 +185501,8 @@ var findOrganizationByIdentifier = (client, identifier2) => Effect_exports.gen(f
185419
185501
  matches: Count.make(byName.length)
185420
185502
  });
185421
185503
  }
185422
- return Option_exports.some(byName[0]);
185504
+ if (isSingle2(byName)) return Option_exports.some(byName[0]);
185505
+ return Option_exports.none();
185423
185506
  });
185424
185507
  var resolveOrganizationByIdentifier = (client, identifier2) => Effect_exports.flatMap(findOrganizationByIdentifier(client, identifier2), (organization) => Option_exports.match(organization, {
185425
185508
  onNone: () => Effect_exports.fail(new OrganizationNotFoundError({ identifier: identifier2 })),
@@ -185456,28 +185539,7 @@ var resolveOrganizationOwner = (client, identifier2) => Effect_exports.gen(funct
185456
185539
  return { id: org._id, ownerClass: contact.class.Organization, identifier: identifier2 };
185457
185540
  });
185458
185541
 
185459
- // src/huly/operations/contact-channels.ts
185460
- var listContactChannelProviders = () => Effect_exports.succeed(listContactChannelProviderLabels());
185461
- var channelIdentifier = (locator) => locator._tag === "channelId" ? locator.channelId : `${locator.provider}:${locator.value}`;
185462
- var INVALID_CHANNEL_LOCATOR_REASON = "provide exactly one channel locator: channelId, or provider plus value";
185463
- var validateProviderValue = (provider, value3) => value3.length === 0 || provider === "email" && !Schema_exports.is(Email)(value3) ? Effect_exports.fail(new InvalidContactChannelValueError({ provider, value: value3 })) : Effect_exports.void;
185464
- var validateChannelLocator2 = (ownerIdentifier, locator) => Effect_exports.gen(function* () {
185465
- const hasChannelId = locator.channelId !== void 0;
185466
- const hasProvider = locator.provider !== void 0;
185467
- const hasValue = locator.value !== void 0;
185468
- if (hasChannelId && !hasProvider && !hasValue) {
185469
- return { _tag: "channelId", channelId: ChannelId.make(locator.channelId) };
185470
- }
185471
- if (!hasChannelId && hasProvider && hasValue) {
185472
- yield* validateProviderValue(locator.provider, locator.value);
185473
- return { _tag: "providerValue", provider: locator.provider, value: locator.value };
185474
- }
185475
- return yield* new InvalidContactChannelLocatorError({
185476
- ownerIdentifier,
185477
- reason: INVALID_CHANNEL_LOCATOR_REASON
185478
- });
185479
- });
185480
- var requireChannelUpdateFields = (params) => params.newProvider === void 0 && params.newValue === void 0 ? Effect_exports.fail(new NoUpdateFieldsError({ operation: "update_contact_channel", fields: ["newProvider", "newValue"] })) : Effect_exports.void;
185542
+ // src/huly/operations/contact-channel-queries.ts
185481
185543
  var findChannelsForOwner = (client, owner) => client.findAll(
185482
185544
  contact.class.Channel,
185483
185545
  hulyQuery({
@@ -185505,6 +185567,9 @@ var findChannelByIdForOwner = (client, owner, channelId) => Effect_exports.map(
185505
185567
  ),
185506
185568
  Option_exports.fromNullable
185507
185569
  );
185570
+
185571
+ // src/huly/operations/contact-channels.ts
185572
+ var listContactChannelProviders = () => Effect_exports.succeed(listContactChannelProviderLabels());
185508
185573
  var resolveChannelByLocator = (client, owner, locator) => Effect_exports.gen(function* () {
185509
185574
  if (locator._tag === "channelId") {
185510
185575
  return yield* Effect_exports.flatMap(findChannelByIdForOwner(client, owner, locator.channelId), (channel) => Option_exports.match(channel, {
@@ -185531,7 +185596,7 @@ var resolveChannelByLocator = (client, owner, locator) => Effect_exports.gen(fun
185531
185596
  matches: Count.make(matches.length)
185532
185597
  });
185533
185598
  }
185534
- return matches[0];
185599
+ return assertAt(matches, 0);
185535
185600
  });
185536
185601
  var findRemovableChannel = (client, owner, locator) => Effect_exports.gen(function* () {
185537
185602
  if (locator._tag === "channelId") {
@@ -185565,7 +185630,7 @@ var listOwnerChannels = (client, owner) => Effect_exports.gen(function* () {
185565
185630
  var addOwnerChannel = (client, owner, params) => Effect_exports.gen(function* () {
185566
185631
  yield* validateProviderValue(params.provider, params.value);
185567
185632
  const existing = yield* findExactChannels(client, owner, params.provider, params.value);
185568
- if (existing.length > 0) {
185633
+ if (isNonEmpty7(existing)) {
185569
185634
  return { added: false, channel: yield* channelSummary(existing[0]) };
185570
185635
  }
185571
185636
  const channelId = yield* client.addCollection(
@@ -185932,8 +185997,9 @@ var formatName = (firstName, lastName) => `${lastName},${firstName}`;
185932
185997
  var parseName = (name) => {
185933
185998
  const parts2 = name.split(",");
185934
185999
  const FIRST_LAST_PARTS = 2;
185935
- if (parts2.length >= FIRST_LAST_PARTS) {
185936
- return { lastName: parts2[0], firstName: parts2.slice(1).join(",") };
186000
+ const [lastName, ...firstNameParts] = parts2;
186001
+ if (lastName !== void 0 && parts2.length >= FIRST_LAST_PARTS) {
186002
+ return { lastName, firstName: firstNameParts.join(",") };
185937
186003
  }
185938
186004
  return { firstName: name, lastName: "" };
185939
186005
  };
@@ -187056,7 +187122,7 @@ var findSnapshotByIdentifier = (client, doc, identifier2) => Effect_exports.gen(
187056
187122
  hulyQuery({ attachedTo: doc._id, title: identifier2 }),
187057
187123
  { limit: 2 }
187058
187124
  );
187059
- if (titleMatches.length === 1) return titleMatches[0];
187125
+ if (isSingle2(titleMatches)) return titleMatches[0];
187060
187126
  if (titleMatches.length > 1) {
187061
187127
  return yield* new HulyError({
187062
187128
  message: `Multiple snapshots on document '${doc.title}' have title '${identifier2}'. Use the snapshotId from list_document_snapshots.`
@@ -187068,7 +187134,7 @@ var findSnapshotByIdentifier = (client, doc, identifier2) => Effect_exports.gen(
187068
187134
  hulyQuery({ attachedTo: doc._id, createdOn }),
187069
187135
  { limit: 2 }
187070
187136
  ) : [];
187071
- if (dateMatches.length === 1) return dateMatches[0];
187137
+ if (isSingle2(dateMatches)) return dateMatches[0];
187072
187138
  if (dateMatches.length > 1) {
187073
187139
  return yield* new HulyError({
187074
187140
  message: `Multiple snapshots on document '${doc.title}' have createdOn '${identifier2}'. Use the snapshotId from list_document_snapshots.`
@@ -187389,7 +187455,7 @@ var resolveDrive = (client, identifier2) => Effect_exports.gen(function* () {
187389
187455
  );
187390
187456
  if (byId !== void 0) return byId;
187391
187457
  const byName = yield* client.findAll(drive.class.Drive, hulyQuery({ name: identifier2 }));
187392
- if (byName.length === 1) return byName[0];
187458
+ if (isSingle2(byName)) return byName[0];
187393
187459
  if (byName.length > 1) {
187394
187460
  return yield* Effect_exports.fail(
187395
187461
  new DriveIdentifierAmbiguousError({
@@ -187426,10 +187492,16 @@ var resolvePath = (client, driveSpace, path2) => Effect_exports.gen(function* ()
187426
187492
  })
187427
187493
  );
187428
187494
  }
187429
- current = candidates[0];
187430
- currentPath = childPath(currentPath, current.title);
187495
+ if (!isSingle2(candidates)) {
187496
+ return yield* Effect_exports.fail(
187497
+ new DrivePathNotFoundError({ drive: driveSpace.name, path: childPath(currentPath, segment) })
187498
+ );
187499
+ }
187500
+ const currentItem = candidates[0];
187501
+ current = currentItem;
187502
+ currentPath = childPath(currentPath, currentItem.title);
187431
187503
  if (segment !== lastSegment) {
187432
- if (!isFolder(current)) {
187504
+ if (!isFolder(currentItem)) {
187433
187505
  return yield* Effect_exports.fail(
187434
187506
  new DriveParentNotFolderError({
187435
187507
  drive: driveSpace.name,
@@ -187438,7 +187510,7 @@ var resolvePath = (client, driveSpace, path2) => Effect_exports.gen(function* ()
187438
187510
  })
187439
187511
  );
187440
187512
  }
187441
- parent = current._id;
187513
+ parent = currentItem._id;
187442
187514
  }
187443
187515
  }
187444
187516
  return { item: current, path: currentPath };
@@ -187524,7 +187596,7 @@ var ensureFolderPath = (client, driveSpace, driveIdentifier, path2) => Effect_ex
187524
187596
  })
187525
187597
  );
187526
187598
  }
187527
- if (matches.length === 1) {
187599
+ if (isSingle2(matches)) {
187528
187600
  const existing = matches[0];
187529
187601
  if (!isFolder(existing)) {
187530
187602
  return yield* Effect_exports.fail(
@@ -188224,17 +188296,22 @@ var uploadDriveFile = (params) => Effect_exports.gen(function* () {
188224
188296
  const storage = yield* HulyStorageClient;
188225
188297
  const driveSpace = yield* resolveDrive(client, params.drive);
188226
188298
  const normalized = normalizeDrivePath(params.path);
188227
- if (normalized.segments.length === 0) {
188299
+ if (!isNonEmpty7(normalized.segments)) {
188300
+ return yield* Effect_exports.fail(
188301
+ new DrivePathConflictError({ drive: params.drive, path: normalized.path, existingKind: "folder" })
188302
+ );
188303
+ }
188304
+ const title = normalized.segments.at(-1);
188305
+ if (title === void 0) {
188228
188306
  return yield* Effect_exports.fail(
188229
188307
  new DrivePathConflictError({ drive: params.drive, path: normalized.path, existingKind: "folder" })
188230
188308
  );
188231
188309
  }
188232
- const title = normalized.segments[normalized.segments.length - 1];
188233
188310
  const parentPath = parentPathOf(normalized);
188234
188311
  const createParents = params.createParents ?? DEFAULT_DRIVE_CREATE_PARENTS;
188235
188312
  const parent = createParents ? yield* ensureFolderPath(client, driveSpace, params.drive, parentPath) : yield* resolveExistingParentFolder(client, driveSpace, params.drive, normalized, parentPath);
188236
188313
  const existing = yield* findChildrenByTitle(client, driveSpace, parent.folder?._id ?? drive.ids.Root, title);
188237
- if (existing.length > 0) {
188314
+ if (isNonEmpty7(existing)) {
188238
188315
  const existingItem = existing[0];
188239
188316
  return yield* Effect_exports.fail(
188240
188317
  new DrivePathConflictError({
@@ -188782,14 +188859,15 @@ var resolveAssociation = (client, identifier2, filters) => Effect_exports.gen(fu
188782
188859
  )
188783
188860
  );
188784
188861
  const rolePair = identifier2.split(" -> ");
188785
- if (rolePair.length === 2) {
188862
+ if (isPair(rolePair)) {
188863
+ const [nameA, nameB] = rolePair;
188786
188864
  addCandidates(
188787
188865
  yield* client.findAll(
188788
188866
  core.class.Association,
188789
188867
  hulyQuery({
188790
188868
  ...associationClassFilterQuery(filters),
188791
- nameA: rolePair[0],
188792
- nameB: rolePair[1]
188869
+ nameA,
188870
+ nameB
188793
188871
  }),
188794
188872
  { limit: ASSOCIATION_LOOKUP_AMBIGUITY_LIMIT }
188795
188873
  )
@@ -188805,7 +188883,7 @@ var resolveAssociation = (client, identifier2, filters) => Effect_exports.gen(fu
188805
188883
  candidates: candidates.map(toCandidate)
188806
188884
  });
188807
188885
  }
188808
- return candidates[0];
188886
+ return assertAt(candidates, 0);
188809
188887
  });
188810
188888
  var rejectSystemClass = (className, operation) => isSystemClassName(String(className)) ? Effect_exports.fail(new AssociationSystemClassUnsupportedError({ className, operation })) : Effect_exports.void;
188811
188889
  var systemClassInAssociation = (association) => {
@@ -189084,7 +189162,8 @@ var resolveIssueLocator = (locator, field) => Effect_exports.gen(function* () {
189084
189162
  }
189085
189163
  const match16 = String(locator.issue).match(/^([A-Z]+)-\d+$/i);
189086
189164
  if (match16 !== null) {
189087
- const { client, project: project3 } = yield* findProject(match16[1].toUpperCase());
189165
+ const projectIdentifier = assertAt(match16, 1);
189166
+ const { client, project: project3 } = yield* findProject(projectIdentifier.toUpperCase());
189088
189167
  const issue2 = yield* findIssueInProject(client, project3, locator.issue);
189089
189168
  return resolvedSummary(issue2, "issue");
189090
189169
  }
@@ -189124,7 +189203,7 @@ var resolveDocumentWithoutTeamspace = (client, identifier2, field) => Effect_exp
189124
189203
  }))
189125
189204
  });
189126
189205
  }
189127
- return resolvedSummary(byTitle[0], "document");
189206
+ return resolvedSummary(assertAt(byTitle, 0), "document");
189128
189207
  });
189129
189208
  var findCardById = (client, identifier2) => client.findOne(
189130
189209
  cardPlugin.class.Card,
@@ -189161,7 +189240,7 @@ var findCardSpace2 = (client, identifier2, field) => Effect_exports.gen(function
189161
189240
  }))
189162
189241
  });
189163
189242
  }
189164
- return byName[0];
189243
+ return assertAt(byName, 0);
189165
189244
  });
189166
189245
  var resolveCardInSpace = (client, identifier2, cardSpace, field) => Effect_exports.gen(function* () {
189167
189246
  const byId = yield* client.findOne(
@@ -189194,7 +189273,7 @@ var resolveCardInSpace = (client, identifier2, cardSpace, field) => Effect_expor
189194
189273
  }))
189195
189274
  });
189196
189275
  }
189197
- return resolvedSummary(byTitle[0], "card");
189276
+ return resolvedSummary(assertAt(byTitle, 0), "card");
189198
189277
  });
189199
189278
  var resolveCardLocator = (client, locator, field) => Effect_exports.gen(function* () {
189200
189279
  if (locator.cardSpace !== void 0) {
@@ -189673,12 +189752,20 @@ var deleteRelation = (params) => Effect_exports.gen(function* () {
189673
189752
  if (matches.length > 1) {
189674
189753
  return yield* new RelationIdentifierAmbiguousError({
189675
189754
  identifier: `${params.association}/${endpoints.docA.id}/${endpoints.docB.id}`,
189676
- relationIds: matches.map((relation) => RelationId.make(relation._id))
189755
+ relationIds: matches.map((relation2) => RelationId.make(relation2._id))
189677
189756
  });
189678
189757
  }
189679
- yield* client.removeDoc(core.class.Relation, matches[0].space, matches[0]._id);
189758
+ if (!isSingle2(matches)) {
189759
+ return {
189760
+ associationId: AssociationId.make(association._id),
189761
+ deleted: false,
189762
+ reason: "not_found"
189763
+ };
189764
+ }
189765
+ const relation = matches[0];
189766
+ yield* client.removeDoc(core.class.Relation, relation.space, relation._id);
189680
189767
  return {
189681
- relationId: RelationId.make(matches[0]._id),
189768
+ relationId: RelationId.make(relation._id),
189682
189769
  associationId: AssociationId.make(association._id),
189683
189770
  deleted: true,
189684
189771
  reason: "deleted"
@@ -190543,7 +190630,7 @@ var resolveCategory = (client, identifier2, parentIdentifier) => Effect_exports.
190543
190630
  (parent) => ({ name: identifier2, attachedTo: parent.id })
190544
190631
  );
190545
190632
  const matches = yield* findAllCategories(client, query);
190546
- if (matches.length === 1) return matches[0];
190633
+ if (isSingle2(matches)) return matches[0];
190547
190634
  if (matches.length > 1) {
190548
190635
  return yield* new InventoryCategoryIdentifierAmbiguousError({ identifier: identifier2, matches: matches.length });
190549
190636
  }
@@ -190560,7 +190647,7 @@ var resolveProduct = (client, identifier2, categoryIdentifier) => Effect_exports
190560
190647
  (category) => ({ name: identifier2, attachedTo: category._id })
190561
190648
  );
190562
190649
  const matches = yield* findAllProducts(client, query);
190563
- if (matches.length === 1) return matches[0];
190650
+ if (isSingle2(matches)) return matches[0];
190564
190651
  if (matches.length > 1) {
190565
190652
  return yield* new InventoryProductIdentifierAmbiguousError({ identifier: identifier2, matches: matches.length });
190566
190653
  }
@@ -190578,7 +190665,7 @@ var resolveVariant = (client, identifier2, productIdentifier, categoryIdentifier
190578
190665
  );
190579
190666
  const candidates = yield* findAllVariants(client, query);
190580
190667
  const matches = candidates.filter((variant) => variant.name === identifier2 || variant.sku === identifier2);
190581
- if (matches.length === 1) return matches[0];
190668
+ if (isSingle2(matches)) return matches[0];
190582
190669
  if (matches.length > 1) {
190583
190670
  return yield* new InventoryVariantIdentifierAmbiguousError({ identifier: identifier2, matches: matches.length });
190584
190671
  }
@@ -191616,7 +191703,7 @@ var blockingIssueFindOptions = {
191616
191703
  var resolveTargetIssue = (client, sourceProject, targetIssueStr) => Effect_exports.gen(function* () {
191617
191704
  const { fullIdentifier } = parseIssueIdentifier(targetIssueStr, sourceProject.identifier);
191618
191705
  const match16 = fullIdentifier.match(/^([A-Z]+)-\d+$/i);
191619
- const prefix = match16 ? match16[1].toUpperCase() : null;
191706
+ const prefix = match16?.[1]?.toUpperCase() ?? null;
191620
191707
  if (prefix !== null && prefix !== sourceProject.identifier.toUpperCase()) {
191621
191708
  const { client: c, project: targetProject } = yield* findProject(prefix);
191622
191709
  const issue3 = yield* findIssueInProject(c, targetProject, targetIssueStr);
@@ -193004,7 +193091,7 @@ var removeTemplateChild = (params) => Effect_exports.gen(function* () {
193004
193091
  project: params.project
193005
193092
  });
193006
193093
  }
193007
- const removedChild = template.children[childIndex];
193094
+ const removedChild = assertAt(template.children, childIndex);
193008
193095
  const newChildren = template.children.filter((_, i) => i !== childIndex);
193009
193096
  yield* client.updateDoc(
193010
193097
  tracker.class.IssueTemplate,
@@ -193865,7 +193952,10 @@ var categorySummaryFor = (category) => ({
193865
193952
  });
193866
193953
  var placeholderFieldIds = (markdown) => {
193867
193954
  const regexp = new RegExp(import_templates.templateFieldRegexp.source, import_templates.templateFieldRegexp.flags);
193868
- const ids3 = [...markdown.matchAll(regexp)].map((match16) => match16[1]).filter((id, index, allIds) => id.length > 0 && allIds.indexOf(id) === index);
193955
+ const ids3 = [...markdown.matchAll(regexp)].flatMap((match16) => {
193956
+ const id = assertAt(match16, 1);
193957
+ return [id];
193958
+ }).filter((id, index, allIds) => id.length > 0 && allIds.indexOf(id) === index);
193869
193959
  return ids3.map((id) => TemplateFieldId.make(id));
193870
193960
  };
193871
193961
  var markdownForTemplate = (template, client) => MessageTemplateMarkdown.make(markupToMarkdownString(template.message, client.markupUrlConfig));
@@ -193913,7 +194003,7 @@ var resolveCategory2 = (client, identifier2) => Effect_exports.gen(function* ()
193913
194003
  matches: Count.make(matches.length)
193914
194004
  });
193915
194005
  }
193916
- return matches[0];
194006
+ return assertAt(matches, 0);
193917
194007
  });
193918
194008
  var categoryMapFor = (client, categoryIds) => Effect_exports.gen(function* () {
193919
194009
  const diagnostics = yield* Diagnostics;
@@ -193986,7 +194076,7 @@ var resolveTemplate = (client, params) => Effect_exports.gen(function* () {
193986
194076
  matches: Count.make(matches.length)
193987
194077
  });
193988
194078
  }
193989
- return matches[0];
194079
+ return assertAt(matches, 0);
193990
194080
  });
193991
194081
  var resolveFieldCategory = (client, identifier2) => Effect_exports.gen(function* () {
193992
194082
  const byId = yield* client.findOne(
@@ -194008,7 +194098,7 @@ var resolveFieldCategory = (client, identifier2) => Effect_exports.gen(function*
194008
194098
  matches: Count.make(matches.length)
194009
194099
  });
194010
194100
  }
194011
- return matches[0];
194101
+ return assertAt(matches, 0);
194012
194102
  });
194013
194103
  var fieldCategoryMapFor = (client, categoryIds) => Effect_exports.gen(function* () {
194014
194104
  const diagnostics = yield* Diagnostics;
@@ -195257,14 +195347,17 @@ var getTimeReport = (params) => Effect_exports.gen(function* () {
195257
195347
  { _id: { $in: employeeIds } }
195258
195348
  ) : [];
195259
195349
  const personMap = new Map(persons.map((p) => [p._id, p.name]));
195260
- const timeReports = reports.map((r) => ({
195261
- id: TimeSpendReportId.make(r._id),
195262
- identifier: IssueIdentifier.make(issue2.identifier),
195263
- employee: r.employee && personMap.has(r.employee) ? PersonName.make(personMap.get(r.employee)) : void 0,
195264
- date: r.date,
195265
- value: r.value,
195266
- description: r.description
195267
- }));
195350
+ const timeReports = reports.map((r) => {
195351
+ const employeeName = r.employee == null ? void 0 : personMap.get(r.employee);
195352
+ return {
195353
+ id: TimeSpendReportId.make(r._id),
195354
+ identifier: IssueIdentifier.make(issue2.identifier),
195355
+ employee: employeeName === void 0 ? void 0 : PersonName.make(employeeName),
195356
+ date: r.date,
195357
+ value: r.value,
195358
+ description: r.description
195359
+ };
195360
+ });
195268
195361
  return {
195269
195362
  identifier: IssueIdentifier.make(issue2.identifier),
195270
195363
  totalTime: issue2.reportedTime,
@@ -195386,7 +195479,7 @@ var listWorkSlots = (params) => Effect_exports.gen(function* () {
195386
195479
  contact.class.Channel,
195387
195480
  { value: params.employeeId }
195388
195481
  );
195389
- if (channels.length > 0) {
195482
+ if (isNonEmpty7(channels)) {
195390
195483
  const channel = channels[0];
195391
195484
  query.user = refAsPersonId(channel.attachedTo);
195392
195485
  }
@@ -195895,7 +195988,7 @@ var resolveProcess = (client, identifier2) => Effect_exports.gen(function* () {
195895
195988
  const matches = [...allProcesses].filter(
195896
195989
  (process4) => normalizeForComparison(process4.name) === normalizeForComparison(identifier2)
195897
195990
  );
195898
- if (matches.length === 1) return matches[0];
195991
+ if (isSingle2(matches)) return matches[0];
195899
195992
  const data = yield* loadProcessDefinitionData(client, matches.length === 0 ? [...allProcesses] : matches);
195900
195993
  const candidates = data.map(processCandidate);
195901
195994
  return yield* matches.length === 0 ? Effect_exports.fail(new ProcessNotFoundError({ identifier: identifier2, candidates })) : Effect_exports.fail(new ProcessIdentifierAmbiguousError({ identifier: identifier2, candidates }));
@@ -195911,7 +196004,7 @@ var resolveMasterTag = (client, identifier2) => Effect_exports.gen(function* ()
195911
196004
  const matches = allTags.filter(
195912
196005
  (tag2) => normalizeForComparison(masterTagLabel(tag2) ?? "") === normalizeForComparison(identifier2)
195913
196006
  );
195914
- if (matches.length === 1) return matches[0]._id;
196007
+ if (isSingle2(matches)) return matches[0]._id;
195915
196008
  if (matches.length === 0) {
195916
196009
  return yield* looksLikeMasterTagId(identifier2) ? Effect_exports.succeed(toRef(identifier2)) : Effect_exports.fail(new ProcessMasterTagNotFoundError({ identifier: identifier2 }));
195917
196010
  }
@@ -195929,7 +196022,7 @@ var resolveCardFilter = (client, identifier2) => Effect_exports.gen(function* ()
195929
196022
  const byId = yield* client.findOne(cardPlugin.class.Card, { _id: toRef(identifier2) });
195930
196023
  if (byId !== void 0) return byId._id;
195931
196024
  const byTitle = yield* client.findAll(cardPlugin.class.Card, { title: identifier2 });
195932
- if (byTitle.length === 1) return byTitle[0]._id;
196025
+ if (isSingle2(byTitle)) return byTitle[0]._id;
195933
196026
  if (byTitle.length > 1) {
195934
196027
  return yield* Effect_exports.fail(
195935
196028
  new ProcessCardIdentifierAmbiguousError({
@@ -196006,8 +196099,8 @@ var listProcesses = (params) => Effect_exports.gen(function* () {
196006
196099
  var getProcess = (params) => Effect_exports.gen(function* () {
196007
196100
  const client = yield* HulyClient;
196008
196101
  const process4 = yield* resolveProcess(client, params.process);
196009
- const [data] = yield* loadProcessDefinitionData(client, [process4]);
196010
- const [states, transitions] = yield* Effect_exports.all([
196102
+ const [masterTags, states, transitions] = yield* Effect_exports.all([
196103
+ findMasterTagsByIds(client, [process4.masterTag]),
196011
196104
  client.findAll(
196012
196105
  processPlugin.class.State,
196013
196106
  { process: process4._id },
@@ -196019,7 +196112,14 @@ var getProcess = (params) => Effect_exports.gen(function* () {
196019
196112
  { sort: { rank: import_core66.SortingOrder.Ascending } }
196020
196113
  )
196021
196114
  ]);
196022
- const result = processDetail({ ...data, states: [...states], transitions: [...transitions] });
196115
+ const result = processDetail({
196116
+ process: process4,
196117
+ masterTagName: masterTags.get(process4.masterTag)?.name,
196118
+ stateCount: states.length,
196119
+ transitionCount: transitions.length,
196120
+ states: [...states],
196121
+ transitions: [...transitions]
196122
+ });
196023
196123
  return yield* encodeOrConnectionError(ProcessDetailSchema, result, "getProcess");
196024
196124
  });
196025
196125
  var listExecutions = (params) => Effect_exports.gen(function* () {
@@ -196794,7 +196894,7 @@ var resolveVacancy = (client, identifier2) => Effect_exports.gen(function* () {
196794
196894
  matches: Count.make(byName.length)
196795
196895
  });
196796
196896
  }
196797
- return byName[0];
196897
+ return assertAt(byName, 0);
196798
196898
  });
196799
196899
  var findApplicant = (client, applicantIdentifier, vacancy, candidate) => Effect_exports.gen(function* () {
196800
196900
  const byId = yield* client.findOne(
@@ -196823,7 +196923,7 @@ var findApplicant = (client, applicantIdentifier, vacancy, candidate) => Effect_
196823
196923
  matches: Count.make(applicants.length)
196824
196924
  });
196825
196925
  }
196826
- return applicants[0];
196926
+ return assertAt(applicants, 0);
196827
196927
  });
196828
196928
  var applicantRefFromDoc = (client, applicant) => Effect_exports.gen(function* () {
196829
196929
  const vacancy = yield* client.findOne(recruitIds.class.Vacancy, { _id: applicant.space });
@@ -197520,7 +197620,7 @@ var findReview = (client, identifier2, candidate, application) => Effect_exports
197520
197620
  if (reviews.length > 1) {
197521
197621
  return yield* new RecruitingReviewIdentifierAmbiguousError({ identifier: identifier2, matches: Count.make(reviews.length) });
197522
197622
  }
197523
- return reviews[0];
197623
+ return assertAt(reviews, 0);
197524
197624
  });
197525
197625
  var resolveReviewLocator = (client, params) => Effect_exports.gen(function* () {
197526
197626
  const candidate = yield* optionalCandidate(client, params.candidate);
@@ -197700,7 +197800,7 @@ var findOpinion = (client, identifier2, review) => Effect_exports.gen(function*
197700
197800
  if (opinions.length > 1) {
197701
197801
  return yield* new RecruitingOpinionIdentifierAmbiguousError({ identifier: identifier2, matches: Count.make(opinions.length) });
197702
197802
  }
197703
- return opinions[0];
197803
+ return assertAt(opinions, 0);
197704
197804
  });
197705
197805
  var resolveReview = (client, review) => review === void 0 ? Effect_exports.succeed(void 0) : findReview(client, review);
197706
197806
  var parentReviewFromOpinion = (client, opinion) => findReview(client, ReviewIdentifier.make(String(opinion.attachedTo)));
@@ -198253,7 +198353,14 @@ var resolveRelatedIssue = (params) => Effect_exports.gen(function* () {
198253
198353
  reason: "issue locator without project must use a full project-prefixed identifier like HULY-123"
198254
198354
  });
198255
198355
  }
198256
- const { client, project: project3 } = yield* findProject(match16[1].toUpperCase());
198356
+ const [, projectIdentifier] = match16;
198357
+ if (projectIdentifier === void 0) {
198358
+ return yield* new RecruitingIssueLocatorInvalidError({
198359
+ issue: params.issue,
198360
+ reason: "issue locator without project must use a full project-prefixed identifier like HULY-123"
198361
+ });
198362
+ }
198363
+ const { client, project: project3 } = yield* findProject(projectIdentifier.toUpperCase());
198257
198364
  const issue2 = yield* findIssueInProject(client, project3, params.issue);
198258
198365
  return { client, issue: issue2, project: project3 };
198259
198366
  });
@@ -199026,7 +199133,8 @@ var findSpaceType = (client, identifier2) => Effect_exports.gen(function* () {
199026
199133
  }))
199027
199134
  });
199028
199135
  }
199029
- return byName[0];
199136
+ if (isSingle2(byName)) return byName[0];
199137
+ return yield* new SpaceTypeNotFoundError({ identifier: NonEmptyString2.make(identifier2) });
199030
199138
  });
199031
199139
  var permissionsByIds = (client, permissionIds) => Effect_exports.gen(function* () {
199032
199140
  const uniqueIds = sortStrings([...new Set(permissionIds)]);
@@ -199650,7 +199758,7 @@ var resolveSpaceRole = (client, spaceType, role) => Effect_exports.gen(function*
199650
199758
  }))
199651
199759
  });
199652
199760
  }
199653
- return matches[0];
199761
+ return assertAt(matches, 0);
199654
199762
  });
199655
199763
  var findSpaceTypeRoles = (client, spaceType) => client.findAll(
199656
199764
  roleClass,
@@ -200239,7 +200347,7 @@ var resolveProjectType = (client, projectTypeRef) => Effect_exports.gen(function
200239
200347
  const selected = projectTypeRef === void 0 ? projectTypes.filter(isDefaultClassicProjectType) : projectTypes.filter(
200240
200348
  (projectType) => projectType._id === projectTypeRef || normalizeForComparison(projectType.name) === normalizeForComparison(projectTypeRef)
200241
200349
  );
200242
- if (selected.length === 1) {
200350
+ if (isSingle2(selected)) {
200243
200351
  return selected[0];
200244
200352
  }
200245
200353
  const message = projectTypeRef === void 0 ? "Could not select a default Classic project type unambiguously; pass projectType by ID or name." : `Project type '${projectTypeRef}' did not resolve to exactly one project type.`;
@@ -200249,7 +200357,7 @@ var resolveTaskType = (taskTypes, taskTypeRef) => {
200249
200357
  const selected = taskTypes.filter(
200250
200358
  (taskType) => taskType._id === taskTypeRef || normalizeForComparison(taskType.name) === normalizeForComparison(taskTypeRef)
200251
200359
  );
200252
- return selected.length === 1 ? Effect_exports.succeed(selected[0]) : Effect_exports.fail(new HulyError({ message: `Task type '${taskTypeRef}' did not resolve to exactly one task type.` }));
200360
+ return isSingle2(selected) ? Effect_exports.succeed(selected[0]) : Effect_exports.fail(new HulyError({ message: `Task type '${taskTypeRef}' did not resolve to exactly one task type.` }));
200253
200361
  };
200254
200362
  var existingTaskTypeByName = (taskTypes, name) => taskTypes.find((taskType) => normalizeForComparison(taskType.name) === normalizeForComparison(name));
200255
200363
  var existingStatusByName = (statuses, name) => statuses.find((status) => normalizeForComparison(status.name) === normalizeForComparison(name));
@@ -200270,6 +200378,7 @@ var replaceOrAppendProjectStatus = (statuses, statusId, taskTypeId) => statuses.
200270
200378
  var sameStatusRefList = (left3, right3) => left3.length === right3.length && left3.every((value3, index) => value3 === right3[index]);
200271
200379
  var sameProjectStatusList = (left3, right3) => left3.length === right3.length && left3.every((value3, index) => {
200272
200380
  const rightValue = right3[index];
200381
+ if (rightValue === void 0) return false;
200273
200382
  return sameProjectStatus(value3, rightValue);
200274
200383
  });
200275
200384
  var listProjectTypes = (_params) => Effect_exports.gen(function* () {
@@ -202637,7 +202746,7 @@ var toClientCompatibleInputSchema = (schema) => {
202637
202746
  Object.entries(schema).filter(([key]) => key !== "type" && !ROOT_COMPOSITION_KEYS.has(key))
202638
202747
  );
202639
202748
  const properties = mergedSchemaField(schema, "properties");
202640
- const defs = mergedSchemaField(schema, "$defs");
202749
+ const defs = collectJsonSchemaDefinitions(schema);
202641
202750
  return {
202642
202751
  ...rootFields,
202643
202752
  type: "object",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firfi/huly-mcp",
3
- "version": "0.38.0",
4
- "description": "MCP server for Huly integration",
3
+ "version": "0.39.0",
4
+ "description": "Huly MCP: feature-complete MCP server for Huly integration",
5
5
  "mcpName": "io.github.dearlordylord/huly-mcp",
6
6
  "type": "module",
7
7
  "main": "./dist/index.cjs",
@@ -118,7 +118,7 @@
118
118
  "check-format": "find src test -name '*.ts' -print0 | xargs -0 eslint --rule '@effect/dprint: error'",
119
119
  "check-all": "pnpm build && pnpm typecheck && pnpm circular && pnpm verify-registry-metadata && pnpm verify-sdk-parity && pnpm verify-readme && pnpm lint && pnpm test:coverage",
120
120
  "circular": "madge --extensions ts --circular src",
121
- "local-release": "pnpm dlx @changesets/cli@2.30.0 version && pnpm sync-registry-metadata && git add package.json CHANGELOG.md server.json .changeset && (git diff --cached --quiet || HUSKY=0 git commit -m \"RELEASING: Releasing 1 package(s)\") && PKG_VERSION=$(node -p \"require('./package.json').version\") && pnpm dlx esbuild@0.27.2 src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:ws \"--define:PKG_VERSION=\\\"$PKG_VERSION\\\"\" && pnpm verify-version && npm_config_ignore_scripts=true pnpm dlx @changesets/cli@2.30.0 publish",
121
+ "local-release": "pnpm dlx @changesets/cli@2.30.0 version && pnpm sync-registry-metadata && git add package.json CHANGELOG.md server.json .changeset && (git diff --cached --quiet || HUSKY=0 git commit -m \"RELEASING: Releasing 1 package(s)\") && PKG_VERSION=$(node -p \"require('./package.json').version\") && pnpm dlx esbuild@0.27.2 src/index.ts --bundle --platform=node --format=cjs --outfile=dist/index.cjs --external:ws \"--define:PKG_VERSION=\\\"$PKG_VERSION\\\"\" && pnpm verify-version && npm_config_ignore_scripts=true pnpm dlx @changesets/cli@2.30.0 publish && git push origin \"v$PKG_VERSION\" && gh release create \"v$PKG_VERSION\" --generate-notes --latest",
122
122
  "verify-version": "node -e \"const v=require('./package.json').version; const d=require('fs').readFileSync('dist/index.cjs','utf8'); if(!d.includes('\\\"'+v+'\\\"'))throw new Error('dist version mismatch: expected '+v)\"",
123
123
  "verify-registry-metadata": "node scripts/sync-registry-metadata.mjs --check",
124
124
  "verify-sdk-parity": "node scripts/audit-sdk-parity.mjs",