@lucern/mcp 0.3.0-alpha.6 → 0.3.0-alpha.8

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.
package/dist/runtime.js CHANGED
@@ -6,6 +6,50 @@ import { v } from 'convex/values';
6
6
  import 'crypto';
7
7
  import * as os from 'os';
8
8
 
9
+ // ../../apps/mcp-server/src/logging.ts
10
+ function isMcpDebugEnabled() {
11
+ const debug = process.env.LUCERN_MCP_DEBUG?.trim().toLowerCase();
12
+ return debug === "1" || debug === "true" || debug === "yes";
13
+ }
14
+ function formatUnknownError(error) {
15
+ if (error instanceof Error) {
16
+ const cause = error.cause;
17
+ const message = `${error.name}: ${error.message}`;
18
+ if (cause === void 0) {
19
+ return message;
20
+ }
21
+ return `${message}; cause=${formatUnknownError(cause)}`;
22
+ }
23
+ if (typeof error === "string") {
24
+ return error;
25
+ }
26
+ try {
27
+ return JSON.stringify(error);
28
+ } catch {
29
+ return Object.prototype.toString.call(error);
30
+ }
31
+ }
32
+ function stringifyForDebug(value) {
33
+ if (typeof value === "string") {
34
+ return value;
35
+ }
36
+ try {
37
+ return JSON.stringify(value);
38
+ } catch {
39
+ return Object.prototype.toString.call(value);
40
+ }
41
+ }
42
+ function debugMcp(message, context) {
43
+ if (!isMcpDebugEnabled()) {
44
+ return;
45
+ }
46
+ const suffix = context ? ` ${stringifyForDebug(context)}` : "";
47
+ console.error(`[lucern-debug] ${message}${suffix}`);
48
+ }
49
+ function debugMcpError(message, error) {
50
+ debugMcp(message, { error: formatUnknownError(error) });
51
+ }
52
+
9
53
  // ../../apps/mcp-server/src/handlers/sdk.ts
10
54
  var handlerSdkClientCache = /* @__PURE__ */ new WeakMap();
11
55
  async function resolveOntologyFromLookup(baseClient, input) {
@@ -129,7 +173,11 @@ function readStringArray(value) {
129
173
  if (Array.isArray(parsed)) {
130
174
  return readStringArray(parsed);
131
175
  }
132
- } catch {
176
+ } catch (error) {
177
+ debugMcpError("Failed to parse string array payload", {
178
+ value: normalized,
179
+ error
180
+ });
133
181
  }
134
182
  }
135
183
  }
@@ -5025,6 +5073,11 @@ var TENANT_CLIENT_INSTALLABLE_PACKAGES = [
5025
5073
  role: "sdk_dependency",
5026
5074
  directTenantImport: false
5027
5075
  },
5076
+ {
5077
+ packageName: "@lucern/graph-sync",
5078
+ role: "host_addon_runtime",
5079
+ directTenantImport: true
5080
+ },
5028
5081
  {
5029
5082
  packageName: "@lucern/identity",
5030
5083
  role: "component_runtime",
@@ -5144,8 +5197,11 @@ function compactRecord(input) {
5144
5197
  Object.entries(input).filter(([, value]) => value !== void 0)
5145
5198
  );
5146
5199
  }
5200
+ function isRecord(value) {
5201
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5202
+ }
5147
5203
  function recordValue(value) {
5148
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
5204
+ return isRecord(value) ? value : {};
5149
5205
  }
5150
5206
  var createEvidenceProjection = defineProjection({
5151
5207
  contractName: "create_evidence",
@@ -5427,9 +5483,7 @@ function hasProvenance(input) {
5427
5483
 
5428
5484
  // ../contracts/src/lens-filter.contract.ts
5429
5485
  function isLensFilterCriteria(value) {
5430
- if (!value || typeof value !== "object") return false;
5431
- const obj = value;
5432
- return typeof obj.version === "number" && typeof obj.kind === "string";
5486
+ return isRecord2(value) && typeof value.version === "number" && typeof value.kind === "string";
5433
5487
  }
5434
5488
  function isTaxonomyFilterCriteriaV1(value) {
5435
5489
  if (!isLensFilterCriteria(value)) return false;
@@ -5458,6 +5512,9 @@ function validateFilterCriteria(value) {
5458
5512
  ]
5459
5513
  };
5460
5514
  }
5515
+ function isRecord2(value) {
5516
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5517
+ }
5461
5518
  function validateTaxonomyFilterV1(criteria) {
5462
5519
  const errors = [];
5463
5520
  if (!Array.isArray(criteria.entityTypeFilters)) {
@@ -5919,9 +5976,16 @@ var ADD_WORKTREE = {
5919
5976
  },
5920
5977
  projectId: {
5921
5978
  type: "string",
5922
- description: "Legacy topicId alias"
5979
+ description: "Legacy topicId alias or resolver hint"
5980
+ },
5981
+ topicId: {
5982
+ type: "string",
5983
+ description: "Optional topic scope hint for resolver validation"
5984
+ },
5985
+ topicHint: {
5986
+ type: "string",
5987
+ description: "Natural-language topic hint for automatic topic resolution"
5923
5988
  },
5924
- topicId: { type: "string", description: "Optional topic scope hint" },
5925
5989
  branchId: {
5926
5990
  type: "string",
5927
5991
  description: "The branch this worktree investigates"
@@ -6019,6 +6083,22 @@ var ADD_WORKTREE = {
6019
6083
  type: "string",
6020
6084
  description: "Optional domain pack whose shaping hooks should influence generated questions and tasks"
6021
6085
  },
6086
+ tags: {
6087
+ type: "array",
6088
+ description: "Additional topic-resolution tags for the worktree"
6089
+ },
6090
+ touchedPaths: {
6091
+ type: "array",
6092
+ description: "File paths used as topic-resolution signals"
6093
+ },
6094
+ sourceRef: {
6095
+ type: "string",
6096
+ description: "Source reference used as a topic-resolution signal"
6097
+ },
6098
+ sourceKind: {
6099
+ type: "string",
6100
+ description: "Source kind used as a topic-resolution signal"
6101
+ },
6022
6102
  campaign: {
6023
6103
  type: "number",
6024
6104
  description: "Top-level pipeline campaign number. Campaigns define the outer execution slice."
@@ -6056,7 +6136,7 @@ var ADD_WORKTREE = {
6056
6136
  description: "Timestamp when worktree metadata was last reconciled"
6057
6137
  }
6058
6138
  },
6059
- required: ["title", "topicId"],
6139
+ required: ["title"],
6060
6140
  response: {
6061
6141
  description: "The created worktree",
6062
6142
  fields: {
@@ -7553,15 +7633,15 @@ var IDENTITY_WHOAMI = {
7553
7633
  };
7554
7634
  var COMPILE_CONTEXT = {
7555
7635
  name: "compile_context",
7556
- description: "Compile a focused reasoning context for a topic. Like `git log --graph --decorate` for the reasoning substrate \u2014 returns the canonical Pillar 3 context pack through the public API shape.",
7636
+ description: "Compile a focused reasoning context. If topicId is omitted, Lucern resolves the best topic from the query. Like `git log --graph --decorate` for the reasoning substrate \u2014 returns the canonical Pillar 3 context pack through the public API shape.",
7557
7637
  parameters: {
7558
7638
  topicId: {
7559
7639
  type: "string",
7560
- description: "Topic scope ID to compile"
7640
+ description: "Optional topic scope ID. Omit to resolve the topic from query."
7561
7641
  },
7562
7642
  query: {
7563
7643
  type: "string",
7564
- description: "Optional focus query used to rank context items"
7644
+ description: "Focus query used to resolve the topic and rank context items. Required when topicId is omitted."
7565
7645
  },
7566
7646
  budget: {
7567
7647
  type: "number",
@@ -7585,7 +7665,7 @@ var COMPILE_CONTEXT = {
7585
7665
  description: "Include related ontological entities in the compiled result"
7586
7666
  }
7587
7667
  },
7588
- required: ["topicId"],
7668
+ required: [],
7589
7669
  response: {
7590
7670
  description: "Compiled context pack for the requested topic",
7591
7671
  fields: {
@@ -7759,18 +7839,60 @@ var CREATE_TASK = {
7759
7839
  name: "create_task",
7760
7840
  description: "Create an execution task tied to the reasoning state. Like `git task` \u2014 tracks concrete work items (calls to make, data to gather, analyses to run) linked to questions, beliefs, or worktrees.",
7761
7841
  parameters: {
7762
- title: { type: "string", description: "Task description" },
7842
+ title: { type: "string", description: "Task title" },
7763
7843
  topicId: { type: "string", description: "Topic scope" },
7844
+ description: {
7845
+ type: "string",
7846
+ description: "Long-form task description"
7847
+ },
7764
7848
  taskType: {
7765
7849
  type: "string",
7766
- description: "Type: research, interview, analysis, data_collection",
7767
- enum: ["research", "interview", "analysis", "data_collection"]
7850
+ description: "Task taxonomy",
7851
+ enum: [
7852
+ "general",
7853
+ "find_evidence",
7854
+ "verify_claim",
7855
+ "research",
7856
+ "review",
7857
+ "interview",
7858
+ "analysis",
7859
+ "track_metrics"
7860
+ ]
7861
+ },
7862
+ priority: {
7863
+ type: "string",
7864
+ description: "Priority",
7865
+ enum: ["urgent", "high", "medium", "low"]
7866
+ },
7867
+ status: {
7868
+ type: "string",
7869
+ description: "Initial status (defaults to todo)",
7870
+ enum: ["todo", "in_progress", "blocked", "done"]
7871
+ },
7872
+ linkedWorktreeId: {
7873
+ type: "string",
7874
+ description: "Worktree this task belongs to"
7875
+ },
7876
+ linkedBeliefId: {
7877
+ type: "string",
7878
+ description: "Belief this task supports"
7768
7879
  },
7769
7880
  linkedQuestionId: {
7770
7881
  type: "string",
7771
7882
  description: "Question this task addresses"
7772
7883
  },
7773
- linkedWorktreeId: { type: "string", description: "Worktree scope" }
7884
+ assigneeId: {
7885
+ type: "string",
7886
+ description: "Principal assigned to the task"
7887
+ },
7888
+ dueDate: {
7889
+ type: "number",
7890
+ description: "Due date as epoch milliseconds"
7891
+ },
7892
+ tags: {
7893
+ type: "array",
7894
+ description: "Free-form string tags"
7895
+ }
7774
7896
  },
7775
7897
  required: ["title"],
7776
7898
  response: {
@@ -9853,9 +9975,7 @@ function mcpContractFromArgsSchema(base, args, contractName) {
9853
9975
  required: converted.filter(([, field]) => field.required).map(([fieldName]) => fieldName)
9854
9976
  };
9855
9977
  }
9856
- function defineFunctionContract(contract) {
9857
- return contract;
9858
- }
9978
+ var defineFunctionContract = (contract) => contract;
9859
9979
  function authUserId(context) {
9860
9980
  return context.userId ?? context.principalId ?? "lucern-agent";
9861
9981
  }
@@ -10009,6 +10129,9 @@ var observationContextArgs = z.object({
10009
10129
  limit: z.number().optional().describe("Maximum observations to return."),
10010
10130
  status: z.string().optional().describe("Observation status filter.")
10011
10131
  });
10132
+ function isRecord3(value) {
10133
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
10134
+ }
10012
10135
  var observationInput = (input, context) => withUserId(
10013
10136
  compactRecord4({
10014
10137
  projectId: input.projectId,
@@ -10041,7 +10164,7 @@ var contextContracts = [
10041
10164
  path: "/context/compile",
10042
10165
  sdkNamespace: "context",
10043
10166
  sdkMethod: "compileContext",
10044
- summary: "Compile a focused reasoning context for a topic.",
10167
+ summary: "Compile a focused reasoning context, resolving topic from query when omitted.",
10045
10168
  convex: {
10046
10169
  module: "contextCompiler",
10047
10170
  functionName: "compile",
@@ -10063,8 +10186,8 @@ var contextContracts = [
10063
10186
  kind: "mutation",
10064
10187
  inputProjection: observationInput,
10065
10188
  outputProjection: (output, input) => ({
10066
- ...output && typeof output === "object" ? output : {},
10067
- observationId: output && typeof output === "object" ? output.nodeId : void 0,
10189
+ ...isRecord3(output) ? output : {},
10190
+ observationId: isRecord3(output) ? output.nodeId : void 0,
10068
10191
  observationType: input.observationType
10069
10192
  })
10070
10193
  },
@@ -11545,10 +11668,11 @@ var worktreeDecisionGateInputSchema = z.object({
11545
11668
  decidedBy: z.string().optional().describe("Actor that decided the gate verdict.")
11546
11669
  }).passthrough().describe("Decision gate contract for worktree activation or exit.");
11547
11670
  var addWorktreeArgs = z.object({
11548
- title: z.string().optional().describe("Human-readable worktree name or objective."),
11671
+ title: z.string().describe("Human-readable worktree name or objective."),
11549
11672
  name: z.string().optional().describe("Storage-name alias for callers that already use backend naming."),
11550
- topicId: z.string().describe("Primary topic scope for the worktree."),
11551
- projectId: z.string().optional().describe("Legacy topicId alias."),
11673
+ topicId: z.string().optional().describe("Optional primary topic scope hint for resolver validation."),
11674
+ projectId: z.string().optional().describe("Legacy topicId alias/hint."),
11675
+ topicHint: z.string().optional().describe("Natural-language topic hint for automatic topic resolution."),
11552
11676
  branchId: z.string().optional().describe("Legacy branch identifier for compatibility with workflow callers."),
11553
11677
  objective: z.string().optional().describe("Reasoning objective this worktree is intended to resolve."),
11554
11678
  hypothesis: z.string().optional().describe("Testable claim this worktree investigates."),
@@ -11573,6 +11697,10 @@ var addWorktreeArgs = z.object({
11573
11697
  autoShape: z.boolean().optional().describe("Whether to invoke inquiry auto-shaping during creation."),
11574
11698
  autoFixPolicy: autoFixPolicyInputSchema.optional(),
11575
11699
  domainPackId: z.string().optional().describe("Domain pack whose shaping hooks should influence the worktree."),
11700
+ tags: z.array(z.string()).optional().describe("Additional topic-resolution tags for the worktree."),
11701
+ touchedPaths: z.array(z.string()).optional().describe("File paths used as topic-resolution signals."),
11702
+ sourceRef: z.string().optional().describe("Source reference used as a topic-resolution signal."),
11703
+ sourceKind: z.string().optional().describe("Source kind used as a topic-resolution signal."),
11576
11704
  campaign: z.number().optional().describe("Top-level pipeline campaign number."),
11577
11705
  lane: z.string().optional().describe("Campaign lane for the worktree."),
11578
11706
  laneOrderInCampaign: z.number().optional().describe("Ordering for this lane within its campaign."),
@@ -11902,8 +12030,46 @@ var worktreesContracts = [
11902
12030
  args: openPullRequestArgs
11903
12031
  })
11904
12032
  ];
11905
-
11906
- // ../contracts/src/function-registry/tasks.ts
12033
+ var taskPrioritySchema = z.enum(["urgent", "high", "medium", "low"]);
12034
+ var taskStatusSchema2 = z.enum(["todo", "in_progress", "blocked", "done"]);
12035
+ var taskTypeSchema = z.enum([
12036
+ "general",
12037
+ "find_evidence",
12038
+ "verify_claim",
12039
+ "research",
12040
+ "review",
12041
+ "interview",
12042
+ "analysis",
12043
+ "track_metrics"
12044
+ ]);
12045
+ var createTaskArgs = z.object({
12046
+ title: z.string().describe("Task title."),
12047
+ topicId: z.string().optional().describe("Topic scope."),
12048
+ description: z.string().optional().describe("Long-form task description."),
12049
+ taskType: taskTypeSchema.optional().describe("Task taxonomy."),
12050
+ priority: taskPrioritySchema.optional().describe("Priority. Defaults to medium when omitted by the server."),
12051
+ status: taskStatusSchema2.optional().describe("Initial status. Defaults to todo."),
12052
+ linkedWorktreeId: z.string().optional().describe("Worktree this task belongs to."),
12053
+ linkedBeliefId: z.string().optional().describe("Belief this task supports."),
12054
+ linkedQuestionId: z.string().optional().describe("Question this task addresses."),
12055
+ assigneeId: z.string().optional().describe("Principal assigned to the task."),
12056
+ dueDate: z.number().optional().describe("Due date as epoch milliseconds."),
12057
+ tags: z.array(z.string()).optional().describe("Free-form tags.")
12058
+ });
12059
+ var createTaskInput = (input) => compactRecord4({
12060
+ title: input.title,
12061
+ topicId: input.topicId,
12062
+ description: input.description,
12063
+ taskType: input.taskType,
12064
+ priority: input.priority ?? "medium",
12065
+ status: input.status ?? "todo",
12066
+ linkedWorktreeId: input.linkedWorktreeId,
12067
+ linkedBeliefId: input.linkedBeliefId,
12068
+ linkedQuestionId: input.linkedQuestionId,
12069
+ assigneeId: input.assigneeId,
12070
+ dueDate: input.dueDate,
12071
+ tags: input.tags
12072
+ });
11907
12073
  var taskInput = (input) => compactRecord4({
11908
12074
  ...input,
11909
12075
  taskId: input.taskId ?? input.id
@@ -11935,8 +12101,10 @@ var tasksContracts = [
11935
12101
  convex: {
11936
12102
  module: "tasks",
11937
12103
  functionName: "create",
11938
- kind: "mutation"
11939
- }
12104
+ kind: "mutation",
12105
+ inputProjection: createTaskInput
12106
+ },
12107
+ args: createTaskArgs
11940
12108
  }),
11941
12109
  surfaceContract({
11942
12110
  name: "list_tasks",
@@ -13054,9 +13222,12 @@ var ALL_FUNCTION_CONTRACTS = [
13054
13222
  ...legacyContracts
13055
13223
  ];
13056
13224
  assertSurfaceCoverage(ALL_FUNCTION_CONTRACTS);
13057
- new Map(
13225
+ var FUNCTION_CONTRACTS_BY_NAME = new Map(
13058
13226
  ALL_FUNCTION_CONTRACTS.map((contract) => [contract.name, contract])
13059
13227
  );
13228
+ FUNCTION_CONTRACTS_BY_NAME.get.bind(
13229
+ FUNCTION_CONTRACTS_BY_NAME
13230
+ );
13060
13231
 
13061
13232
  // ../contracts/src/tenant-bootstrap-seed.contract.ts
13062
13233
  function isCopyableSeedRequirement(entry) {
@@ -13697,7 +13868,8 @@ function readLucernJson() {
13697
13868
  topicId: parsed.topicId,
13698
13869
  topicName: parsed.topicName
13699
13870
  };
13700
- } catch {
13871
+ } catch (error) {
13872
+ debugMcpError("Failed to read .lucern.json ambient context", error);
13701
13873
  return null;
13702
13874
  }
13703
13875
  }
@@ -13719,7 +13891,8 @@ function writeLucernJson(context) {
13719
13891
  `,
13720
13892
  "utf-8"
13721
13893
  );
13722
- } catch {
13894
+ } catch (error) {
13895
+ debugMcpError("Failed to write .lucern.json ambient context", error);
13723
13896
  }
13724
13897
  }
13725
13898
  function isNonEmptyString(value) {
@@ -13761,15 +13934,25 @@ async function tryGetTopicById(topicId) {
13761
13934
  return null;
13762
13935
  }
13763
13936
  try {
13764
- return unwrapGatewayData(await getLucernClient().topics.get(normalizedTopicId));
13765
- } catch {
13937
+ return unwrapGatewayData(
13938
+ await getLucernClient().topics.get(normalizedTopicId)
13939
+ );
13940
+ } catch (error) {
13941
+ debugMcp("Failed to load topic by id", {
13942
+ topicId,
13943
+ error: formatUnknownError(error)
13944
+ });
13766
13945
  return null;
13767
13946
  }
13768
13947
  }
13769
13948
  async function tryGetNodeById(nodeId) {
13770
13949
  try {
13771
13950
  return unwrapGatewayData(await getLucernClient().nodes.get({ nodeId }));
13772
- } catch {
13951
+ } catch (error) {
13952
+ debugMcp("Failed to load node by id", {
13953
+ nodeId,
13954
+ error: formatUnknownError(error)
13955
+ });
13773
13956
  return null;
13774
13957
  }
13775
13958
  }
@@ -13778,8 +13961,14 @@ async function findTopicByMappedProjectId(legacyScopeId) {
13778
13961
  const response = await getLucernClient().topics.list({});
13779
13962
  const data = unwrapGatewayData(response);
13780
13963
  const topics2 = Array.isArray(data?.topics) ? data.topics : Array.isArray(data?.items) ? data.items : [];
13781
- return topics2.find((topic) => readTopicMappedProjectId(topic) === legacyScopeId) || null;
13782
- } catch {
13964
+ return topics2.find(
13965
+ (topic) => readTopicMappedProjectId(topic) === legacyScopeId
13966
+ ) || null;
13967
+ } catch (error) {
13968
+ debugMcp("Failed to resolve topic by mapped project id", {
13969
+ legacyScopeId,
13970
+ error: formatUnknownError(error)
13971
+ });
13783
13972
  return null;
13784
13973
  }
13785
13974
  }
@@ -13789,17 +13978,9 @@ async function resolveTopicScopeId(scopeId, toolName) {
13789
13978
  if (!isNonEmptyString(scopeId)) {
13790
13979
  if (isNonEmptyString(defaultTopicId)) {
13791
13980
  resolved = defaultTopicId;
13792
- } else {
13793
- const lucernContext = readLucernJson();
13794
- const ambientTopicId = decodePublicTopicId(lucernContext?.topicId);
13795
- if (isNonEmptyString(ambientTopicId)) {
13796
- resolved = ambientTopicId;
13797
- }
13798
13981
  }
13799
13982
  if (!resolved) {
13800
- throw new Error(
13801
- `[${toolName}] Missing topic scope. Provide topicId.`
13802
- );
13983
+ throw new Error(`[${toolName}] Missing topic scope. Provide topicId.`);
13803
13984
  }
13804
13985
  }
13805
13986
  if (!resolved) {
@@ -14017,28 +14198,30 @@ function readString2(value) {
14017
14198
  function readNullableNumber(value) {
14018
14199
  return typeof value === "number" && Number.isFinite(value) ? value : null;
14019
14200
  }
14201
+ function isRecord4(value) {
14202
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
14203
+ }
14020
14204
  function refreshLucernContextFromBuildSession(payload, args) {
14021
- if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
14205
+ if (!isRecord4(payload)) {
14022
14206
  return;
14023
14207
  }
14024
- const record = payload;
14025
- const topicId = readString2(record.topicId);
14026
- const worktreeId = readString2(record.worktreeId) ?? readString2(args.worktreeId);
14208
+ const topicId = readString2(payload.topicId);
14209
+ const worktreeId = readString2(payload.worktreeId) ?? readString2(args.worktreeId);
14027
14210
  if (!topicId || !worktreeId) {
14028
14211
  return;
14029
14212
  }
14030
14213
  writeLucernJson({
14031
14214
  topicId,
14032
- topicName: readString2(record.topicName),
14215
+ topicName: readString2(payload.topicName),
14033
14216
  activeWorktree: {
14034
14217
  worktreeId,
14035
- title: readString2(record.worktreeName) ?? readString2(record.title) ?? readString2(record.name) ?? worktreeId,
14036
- status: readString2(record.status) ?? "unknown",
14037
- campaign: readNullableNumber(record.campaign),
14038
- lane: readString2(record.lane) ?? null,
14039
- orderInLane: readNullableNumber(record.orderInLane),
14040
- gate: readString2(record.gate) ?? null,
14041
- branch: readString2(record.branch) ?? readString2(args.branch) ?? null
14218
+ title: readString2(payload.worktreeName) ?? readString2(payload.title) ?? readString2(payload.name) ?? worktreeId,
14219
+ status: readString2(payload.status) ?? "unknown",
14220
+ campaign: readNullableNumber(payload.campaign),
14221
+ lane: readString2(payload.lane) ?? null,
14222
+ orderInLane: readNullableNumber(payload.orderInLane),
14223
+ gate: readString2(payload.gate) ?? null,
14224
+ branch: readString2(payload.branch) ?? readString2(args.branch) ?? null
14042
14225
  }
14043
14226
  });
14044
14227
  }
@@ -14252,9 +14435,8 @@ var edgeHandlers = {
14252
14435
 
14253
14436
  // ../../apps/mcp-server/src/handlers/graph.ts
14254
14437
  var graphHandlers = {
14255
- async traverse_graph(args, ctx) {
14256
- return edgeHandlers.traverse_graph(args, ctx);
14257
- },
14438
+ // Backward-compatible alias to keep the graph transport API stable.
14439
+ traverse_graph: edgeHandlers.traverse_graph,
14258
14440
  async search_beliefs(args, ctx) {
14259
14441
  const topicId = await resolveTopicScopeId(
14260
14442
  readTopicIdArg(args),
@@ -14270,17 +14452,19 @@ var graphHandlers = {
14270
14452
  })
14271
14453
  );
14272
14454
  },
14273
- async find_contradictions(args, ctx) {
14274
- return contradictionHandlers.find_contradictions(args, ctx);
14275
- },
14276
- async bisect_confidence(args, ctx) {
14277
- return beliefHandlers.bisect_confidence(args, ctx);
14278
- },
14455
+ // Backward-compatible alias to keep the graph transport API stable.
14456
+ find_contradictions: contradictionHandlers.find_contradictions,
14457
+ // Backward-compatible alias to keep the graph transport API stable.
14458
+ bisect_confidence: beliefHandlers.bisect_confidence,
14279
14459
  async trace_entity_impact(args, ctx) {
14280
- const topicId = await resolveTopicScopeId(
14281
- readTopicIdArg(args),
14282
- "trace_entity_impact"
14283
- ).catch(() => void 0);
14460
+ let topicId;
14461
+ try {
14462
+ topicId = await resolveTopicScopeId(
14463
+ readTopicIdArg(args),
14464
+ "trace_entity_impact"
14465
+ );
14466
+ } catch (error) {
14467
+ }
14284
14468
  return formatSdkResult(
14285
14469
  await getSdkClient(ctx).graph.traceEntityImpact({
14286
14470
  nodeId: typeof args.nodeId === "string" ? args.nodeId : "",
@@ -15437,6 +15621,42 @@ var api = makeProxy("api");
15437
15621
  makeProxy("components");
15438
15622
  makeProxy("internal");
15439
15623
 
15624
+ // ../server-core/src/debug.ts
15625
+ function formatUnknownError2(error) {
15626
+ if (error instanceof Error) {
15627
+ const cause = error.cause;
15628
+ const message = `${error.name}: ${error.message}`;
15629
+ if (cause === void 0) {
15630
+ return message;
15631
+ }
15632
+ return `${message}; cause=${formatUnknownError2(cause)}`;
15633
+ }
15634
+ if (typeof error === "string") {
15635
+ return error;
15636
+ }
15637
+ if (error === null) {
15638
+ return "null";
15639
+ }
15640
+ if (error === void 0) {
15641
+ return "undefined";
15642
+ }
15643
+ try {
15644
+ return JSON.stringify(error);
15645
+ } catch {
15646
+ return Object.prototype.toString.call(error);
15647
+ }
15648
+ }
15649
+ function isServerCoreFallbackDebugEnabled() {
15650
+ const env = globalThis.process?.env;
15651
+ return env?.LUCERN_COMPAT_FALLBACK_DEBUG === "1" || env?.LUCERN_SERVER_CORE_DEBUG === "1";
15652
+ }
15653
+ function debugServerCoreFallback(scope, message, context) {
15654
+ if (!isServerCoreFallbackDebugEnabled()) {
15655
+ return;
15656
+ }
15657
+ console.debug(`[${scope}] ${message}`, context ?? {});
15658
+ }
15659
+
15440
15660
  // ../server-core/src/mcp-context-tools.ts
15441
15661
  function requireMcpConvex(authContext, label) {
15442
15662
  const convex = authContext.convex;
@@ -15449,6 +15669,9 @@ function requireMcpConvex(authContext, label) {
15449
15669
  // ../server-core/src/topic-resolver.ts
15450
15670
  var DEFAULT_THRESHOLD = 0.35;
15451
15671
  var TREE_MAX_DEPTH = 20;
15672
+ var BM25_K1 = 1.2;
15673
+ var BM25_B = 0.75;
15674
+ var BM25_NORMALIZATION = 8;
15452
15675
  var METADATA_TOKEN_KEYS = [
15453
15676
  "aliases",
15454
15677
  "alias",
@@ -15501,35 +15724,44 @@ function readStringArray2(value) {
15501
15724
  }
15502
15725
  return value.map((entry) => readString3(entry)).filter((entry) => Boolean(entry));
15503
15726
  }
15727
+ function isRecord5(value) {
15728
+ return value !== null && typeof value === "object" && !Array.isArray(value);
15729
+ }
15730
+ function decodePrefixedIdOrNull(value) {
15731
+ const normalized = value.trim();
15732
+ const match = /^([a-z][a-z0-9]*)_(.+)$/.exec(normalized);
15733
+ if (!match) {
15734
+ return null;
15735
+ }
15736
+ return {
15737
+ prefix: match[1],
15738
+ value: match[2]
15739
+ };
15740
+ }
15504
15741
  function asRecord2(value) {
15505
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
15742
+ return isRecord5(value) ? value : {};
15506
15743
  }
15507
15744
  function normalizeTopicId(value) {
15508
15745
  const normalized = readString3(value);
15509
15746
  if (!normalized) {
15510
15747
  return void 0;
15511
15748
  }
15512
- try {
15513
- const decoded = decodePrefixedId(normalized);
15514
- return decoded.prefix === "top" ? decoded.value : normalized;
15515
- } catch {
15516
- return normalized;
15517
- }
15749
+ const decoded = decodePrefixedIdOrNull(normalized);
15750
+ return decoded?.prefix === "top" ? decoded.value : normalized;
15518
15751
  }
15519
15752
  function normalizeExternalId(value, prefix) {
15520
15753
  const normalized = readString3(value);
15521
15754
  if (!normalized) {
15522
15755
  return void 0;
15523
15756
  }
15524
- try {
15525
- const decoded = decodePrefixedId(normalized);
15526
- if (prefix && decoded.prefix !== prefix) {
15527
- return normalized;
15528
- }
15529
- return decoded.value;
15530
- } catch {
15757
+ const decoded = decodePrefixedIdOrNull(normalized);
15758
+ if (!decoded) {
15531
15759
  return normalized;
15532
15760
  }
15761
+ if (decoded.prefix !== prefix) {
15762
+ return normalized;
15763
+ }
15764
+ return decoded.value;
15533
15765
  }
15534
15766
  function normalizeToken(value) {
15535
15767
  const normalized = value.toLowerCase().trim();
@@ -15608,15 +15840,83 @@ function collectTopicMetadataTokens(metadata) {
15608
15840
  function topicNameSlug(name) {
15609
15841
  return tokenize(name).join(" ");
15610
15842
  }
15843
+ function topicTokenList(topic) {
15844
+ return [
15845
+ ...tokenize(topic.name),
15846
+ ...tokenize(topic.description),
15847
+ ...tokenize(topic.type),
15848
+ ...collectTopicMetadataTokens(topic.metadata ?? {}).flatMap(
15849
+ (value) => tokenize(value)
15850
+ )
15851
+ ];
15852
+ }
15611
15853
  function topicTokenSet(topic) {
15612
15854
  const tokens = /* @__PURE__ */ new Set();
15613
- addTokens(tokens, tokenize(topic.name));
15614
- addTokens(tokens, tokenize(topic.description));
15615
- addTokens(tokens, tokenize(topic.type));
15616
- addTokens(tokens, collectTopicMetadataTokens(topic.metadata ?? {}));
15855
+ addTokens(tokens, topicTokenList(topic));
15617
15856
  return tokens;
15618
15857
  }
15858
+ function buildTopicScoreCorpus(topics2) {
15859
+ const documentFrequencyByToken = /* @__PURE__ */ new Map();
15860
+ let documentCount = 0;
15861
+ let totalLength = 0;
15862
+ for (const topic of topics2) {
15863
+ const tokens = topicTokenList(topic);
15864
+ const uniqueTokens = new Set(tokens);
15865
+ documentCount += 1;
15866
+ totalLength += tokens.length;
15867
+ for (const token of uniqueTokens) {
15868
+ documentFrequencyByToken.set(
15869
+ token,
15870
+ (documentFrequencyByToken.get(token) ?? 0) + 1
15871
+ );
15872
+ }
15873
+ }
15874
+ return {
15875
+ documentCount,
15876
+ averageDocumentLength: documentCount > 0 ? Math.max(1, totalLength / documentCount) : 1,
15877
+ documentFrequencyByToken
15878
+ };
15879
+ }
15880
+ function countTokens(tokens) {
15881
+ const counts = /* @__PURE__ */ new Map();
15882
+ for (const token of tokens) {
15883
+ counts.set(token, (counts.get(token) ?? 0) + 1);
15884
+ }
15885
+ return counts;
15886
+ }
15887
+ function bm25Score(topic, signals, corpus) {
15888
+ if (signals.allTokens.size === 0 || corpus.documentCount === 0) {
15889
+ return 0;
15890
+ }
15891
+ const documentTokens = topicTokenList(topic);
15892
+ if (documentTokens.length === 0) {
15893
+ return 0;
15894
+ }
15895
+ const counts = countTokens(documentTokens);
15896
+ const docLength = documentTokens.length;
15897
+ let score = 0;
15898
+ for (const token of signals.allTokens) {
15899
+ const termFrequency = counts.get(token) ?? 0;
15900
+ if (termFrequency === 0) {
15901
+ continue;
15902
+ }
15903
+ const documentFrequency = corpus.documentFrequencyByToken.get(token) ?? 0;
15904
+ const idf = Math.log(
15905
+ 1 + (corpus.documentCount - documentFrequency + 0.5) / (documentFrequency + 0.5)
15906
+ );
15907
+ const denominator = termFrequency + BM25_K1 * (1 - BM25_B + BM25_B * (docLength / corpus.averageDocumentLength));
15908
+ score += idf * (termFrequency * (BM25_K1 + 1) / denominator);
15909
+ }
15910
+ return score / (score + BM25_NORMALIZATION);
15911
+ }
15619
15912
  function scoreTopicMatch(topic, input) {
15913
+ return scoreTopicMatchWithCorpus(
15914
+ topic,
15915
+ input,
15916
+ buildTopicScoreCorpus([topic])
15917
+ );
15918
+ }
15919
+ function scoreTopicMatchWithCorpus(topic, input, corpus) {
15620
15920
  const signals = buildSignalBag(input);
15621
15921
  const topicTokens = topicTokenSet(topic);
15622
15922
  if (signals.allTokens.size === 0 || topicTokens.size === 0) {
@@ -15630,6 +15930,7 @@ function scoreTopicMatch(topic, input) {
15630
15930
  }
15631
15931
  const union = (/* @__PURE__ */ new Set([...signals.allTokens, ...topicTokens])).size;
15632
15932
  const jaccard = union > 0 ? intersection / union : 0;
15933
+ const bm25 = bm25Score(topic, signals, corpus);
15633
15934
  const topicSlug = topicNameSlug(topic.name);
15634
15935
  let boost = 0;
15635
15936
  for (const token of signals.tagTokens) {
@@ -15650,7 +15951,7 @@ function scoreTopicMatch(topic, input) {
15650
15951
  break;
15651
15952
  }
15652
15953
  }
15653
- return Math.max(0, Math.min(1, jaccard + boost));
15954
+ return Math.max(0, Math.min(1, bm25 * 0.55 + jaccard * 0.45 + boost));
15654
15955
  }
15655
15956
  function normalizeTopicCandidate(value) {
15656
15957
  const record = asRecord2(value);
@@ -15672,26 +15973,21 @@ function normalizeTopicCandidate(value) {
15672
15973
  globalId: readString3(record.globalId)
15673
15974
  };
15674
15975
  }
15675
- function topicMatchesScope(topic, tenantId, workspaceId) {
15676
- if (topic.status === "archived") {
15677
- return false;
15678
- }
15679
- if (tenantId && topic.tenantId && topic.tenantId !== tenantId) {
15680
- return false;
15681
- }
15682
- if (workspaceId && topic.workspaceId && topic.workspaceId !== workspaceId) {
15683
- return false;
15684
- }
15685
- return true;
15686
- }
15687
15976
  async function fetchTopicById(ctx, topicId) {
15688
15977
  const normalizedTopicId = normalizeTopicId(topicId);
15689
15978
  if (!normalizedTopicId) {
15690
15979
  return null;
15691
15980
  }
15692
- const topic = await getConvex(ctx).query(api.topics.get, {
15693
- id: normalizedTopicId
15694
- }).catch(() => null);
15981
+ let topic = null;
15982
+ try {
15983
+ topic = await getConvex(ctx).query(api.topics.get, {
15984
+ id: normalizedTopicId
15985
+ });
15986
+ } catch (error) {
15987
+ debugServerCoreFallback("topic-resolver", `fetchTopicById: topics.get(${normalizedTopicId})`, {
15988
+ error: formatUnknownError2(error)
15989
+ });
15990
+ }
15695
15991
  return normalizeTopicCandidate(topic);
15696
15992
  }
15697
15993
  function readRecordTopicId(value) {
@@ -15701,7 +15997,14 @@ function readRecordTopicId(value) {
15701
15997
  );
15702
15998
  }
15703
15999
  async function fetchRecordTopicId(ctx, reference, args) {
15704
- const result = await getConvex(ctx).query(reference, args).catch(() => null);
16000
+ let result = null;
16001
+ try {
16002
+ result = await getConvex(ctx).query(reference, args);
16003
+ } catch (error) {
16004
+ debugServerCoreFallback("topic-resolver", "fetchRecordTopicId", {
16005
+ error: formatUnknownError2(error)
16006
+ });
16007
+ }
15705
16008
  return readRecordTopicId(result);
15706
16009
  }
15707
16010
  async function resolveBeliefTopicId(ctx, beliefId) {
@@ -15722,7 +16025,9 @@ async function resolveCommonBeliefTopicId(ctx, beliefIds) {
15722
16025
  return void 0;
15723
16026
  }
15724
16027
  const resolvedTopicIds = (await Promise.all(
15725
- normalizedBeliefIds.map((beliefId) => resolveBeliefTopicId(ctx, beliefId))
16028
+ normalizedBeliefIds.map(
16029
+ (beliefId) => resolveBeliefTopicId(ctx, beliefId)
16030
+ )
15726
16031
  )).filter((topicId) => Boolean(topicId));
15727
16032
  if (resolvedTopicIds.length !== normalizedBeliefIds.length) {
15728
16033
  return void 0;
@@ -15731,17 +16036,31 @@ async function resolveCommonBeliefTopicId(ctx, beliefIds) {
15731
16036
  }
15732
16037
  async function fetchTopicTree(ctx) {
15733
16038
  const convex = getConvex(ctx);
15734
- const tree = await convex.query(api.topics.getTree, {
15735
- rootId: ROOT_TOPIC_ID,
15736
- maxDepth: TREE_MAX_DEPTH
15737
- }).catch(() => null);
16039
+ let tree = null;
16040
+ try {
16041
+ tree = await convex.query(api.topics.getTree, {
16042
+ rootId: ROOT_TOPIC_ID,
16043
+ maxDepth: TREE_MAX_DEPTH
16044
+ });
16045
+ } catch (error) {
16046
+ debugServerCoreFallback("topic-resolver", "fetchTopicTree: topics.getTree", {
16047
+ error: formatUnknownError2(error)
16048
+ });
16049
+ }
15738
16050
  const normalizedTree = Array.isArray(tree) ? tree.map((topic) => normalizeTopicCandidate(topic)).filter((topic) => Boolean(topic)) : [];
15739
16051
  if (normalizedTree.length > 0) {
15740
16052
  return normalizedTree;
15741
16053
  }
15742
- const listed = await convex.query(api.topics.list, {
15743
- status: "active"
15744
- }).catch(() => []);
16054
+ let listed = [];
16055
+ try {
16056
+ listed = await convex.query(api.topics.list, {
16057
+ status: "active"
16058
+ });
16059
+ } catch (error) {
16060
+ debugServerCoreFallback("topic-resolver", "fetchTopicTree: topics.list fallback", {
16061
+ error: formatUnknownError2(error)
16062
+ });
16063
+ }
15745
16064
  return Array.isArray(listed) ? listed.map((topic) => normalizeTopicCandidate(topic)).filter((topic) => Boolean(topic)) : [];
15746
16065
  }
15747
16066
  function buildTopicMaps(topics2) {
@@ -15774,42 +16093,43 @@ function buildPath(topicId, byId) {
15774
16093
  function titleCase(value) {
15775
16094
  return value.replace(/\b[a-z]/g, (match) => match.toUpperCase());
15776
16095
  }
15777
- function compactWhitespace(value) {
15778
- return value.replace(/\s+/g, " ").trim();
15779
- }
15780
16096
  function deriveTopicName(input, parentTopic) {
15781
16097
  const textualHint = readString3(input.topicHint);
15782
16098
  if (textualHint && tokenize(textualHint).length > 0) {
15783
- return titleCase(compactWhitespace(textualHint)).slice(0, 60);
16099
+ return titleCase(textualHint.replace(/\s+/g, " ").trim()).slice(0, 60);
15784
16100
  }
15785
16101
  const firstTag = (input.tags ?? []).find((tag) => tokenize(tag).length > 0);
15786
16102
  if (firstTag) {
15787
- return titleCase(compactWhitespace(firstTag)).slice(0, 60);
16103
+ return titleCase(firstTag.replace(/\s+/g, " ").trim()).slice(0, 60);
15788
16104
  }
15789
16105
  const firstPathStem = (input.touchedPaths ?? []).map((filePath) => basenameStem(filePath)).find((stem) => stem && tokenize(stem).length > 0);
15790
16106
  if (firstPathStem) {
15791
- return titleCase(compactWhitespace(firstPathStem.replace(/[-_]+/g, " "))).slice(0, 60);
16107
+ return titleCase(
16108
+ firstPathStem.replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim()
16109
+ ).slice(0, 60);
15792
16110
  }
15793
- const summary = compactWhitespace(input.summary);
16111
+ const summary = input.summary.replace(/\s+/g, " ").trim();
15794
16112
  if (summary.length > 0) {
15795
16113
  return titleCase(summary.slice(0, 60));
15796
16114
  }
15797
16115
  const sourceRef = readString3(input.sourceRef);
15798
16116
  if (sourceRef) {
15799
- return titleCase(compactWhitespace(sourceRef.replace(/[-_:/]+/g, " "))).slice(0, 60);
16117
+ return titleCase(
16118
+ sourceRef.replace(/[-_:/]+/g, " ").replace(/\s+/g, " ").trim()
16119
+ ).slice(0, 60);
15800
16120
  }
15801
- return `${titleCase(parentTopic.name)} Subtopic`;
16121
+ return parentTopic ? `${titleCase(parentTopic.name)} Subtopic` : "Lucern";
15802
16122
  }
15803
- async function createChildTopic(ctx, parentTopic, input) {
16123
+ async function createTopic(ctx, input, parentTopic) {
15804
16124
  const convex = getConvex(ctx);
15805
16125
  const desiredTenantId = ctx.tenantId;
15806
16126
  const desiredWorkspaceId = readString3(input.workspaceId) ?? ctx.workspaceId;
15807
16127
  const createdBy = ctx.principalId ?? ctx.userId;
15808
16128
  const created = await convex.mutation(api.topics.create, {
15809
16129
  name: deriveTopicName(input, parentTopic),
15810
- description: compactWhitespace(input.summary).slice(0, 280) || void 0,
16130
+ description: input.summary.replace(/\s+/g, " ").trim().slice(0, 280) || void 0,
15811
16131
  type: "theme",
15812
- parentTopicId: parentTopic.rawId,
16132
+ ...parentTopic ? { parentTopicId: parentTopic.rawId } : {},
15813
16133
  tenantId: desiredTenantId,
15814
16134
  workspaceId: desiredWorkspaceId,
15815
16135
  visibility: "team",
@@ -15826,10 +16146,18 @@ async function createChildTopic(ctx, parentTopic, input) {
15826
16146
  const createdId = readString3(createdRecord.id) ?? readString3(createdRecord.topicId) ?? readString3(createdRecord._id);
15827
16147
  const hydrated = await fetchTopicById(ctx, createdId);
15828
16148
  if (!hydrated) {
15829
- throw new Error("[topic-resolver] Auto-created topic could not be reloaded.");
16149
+ throw new Error(
16150
+ "[topic-resolver] Auto-created topic could not be reloaded."
16151
+ );
15830
16152
  }
15831
16153
  return hydrated;
15832
16154
  }
16155
+ function createChildTopic(ctx, parentTopic, input) {
16156
+ return createTopic(ctx, input, parentTopic);
16157
+ }
16158
+ function createRootTopic(ctx, input) {
16159
+ return createTopic(ctx, input);
16160
+ }
15833
16161
  function pushTrace(trace, topic, score) {
15834
16162
  const existing = trace[trace.length - 1];
15835
16163
  if (existing?.topicId === topic.rawId) {
@@ -15844,9 +16172,10 @@ function pushTrace(trace, topic, score) {
15844
16172
  });
15845
16173
  }
15846
16174
  function pickBestTopic(topics2, input) {
16175
+ const corpus = buildTopicScoreCorpus(topics2);
15847
16176
  const ranked = topics2.filter((topic) => topic.status !== "archived").map((topic) => ({
15848
16177
  topic,
15849
- score: scoreTopicMatch(topic, input)
16178
+ score: scoreTopicMatchWithCorpus(topic, input, corpus)
15850
16179
  })).sort((left, right) => {
15851
16180
  if (right.score !== left.score) {
15852
16181
  return right.score - left.score;
@@ -15862,27 +16191,31 @@ function pickFallbackRootTopic(topics2, input) {
15862
16191
  return pickBestTopic(parentless, input) ?? pickBestTopic(topics2, input);
15863
16192
  }
15864
16193
  async function loadTopicUniverse(ctx, input) {
15865
- const desiredTenantId = ctx.tenantId;
15866
- const desiredWorkspaceId = readString3(input.workspaceId) ?? ctx.workspaceId;
15867
16194
  const rawTopics = await fetchTopicTree(ctx);
15868
- const scopedTopics = rawTopics.filter(
15869
- (topic) => topicMatchesScope(topic, desiredTenantId, desiredWorkspaceId)
15870
- );
15871
- const root = rawTopics.find((topic) => topic.rawId === ROOT_TOPIC_ID) ?? await fetchTopicById(ctx, ROOT_TOPIC_ID) ?? pickFallbackRootTopic(scopedTopics, input) ?? null;
16195
+ let candidatePool = rawTopics;
16196
+ let root = pickFallbackRootTopic(candidatePool, input) ?? candidatePool.find((topic) => !topic.parentTopicId) ?? candidatePool[0] ?? null;
16197
+ let rootCreated = false;
15872
16198
  if (!root) {
15873
- throw new Error("[topic-resolver] Root topic not found.");
16199
+ if (input.autoCreate === false) {
16200
+ throw new Error(
16201
+ "[topic-resolver] No topics available for resolution."
16202
+ );
16203
+ }
16204
+ root = await createRootTopic(ctx, input);
16205
+ rootCreated = true;
16206
+ candidatePool = [root];
15874
16207
  }
15875
- const topics2 = rawTopics.filter(
15876
- (topic) => topic.rawId === root.rawId || topicMatchesScope(topic, desiredTenantId, desiredWorkspaceId)
15877
- );
15878
16208
  const dedupedTopics = /* @__PURE__ */ new Map();
15879
16209
  dedupedTopics.set(root.rawId, root);
15880
- for (const topic of topics2) {
16210
+ for (const topic of candidatePool) {
15881
16211
  dedupedTopics.set(topic.rawId, topic);
15882
16212
  }
15883
- const { byId, childrenByParent } = buildTopicMaps([...dedupedTopics.values()]);
16213
+ const { byId, childrenByParent } = buildTopicMaps([
16214
+ ...dedupedTopics.values()
16215
+ ]);
15884
16216
  return {
15885
16217
  root,
16218
+ rootCreated,
15886
16219
  byId,
15887
16220
  childrenByParent
15888
16221
  };
@@ -15893,7 +16226,9 @@ async function validateTopicOrNull(ctx, topicId) {
15893
16226
  return null;
15894
16227
  }
15895
16228
  const { byId } = await loadTopicUniverse(ctx, {
15896
- summary: topic.name});
16229
+ summary: topic.name,
16230
+ autoCreate: false
16231
+ });
15897
16232
  if (!byId.has(topic.rawId)) {
15898
16233
  byId.set(topic.rawId, topic);
15899
16234
  }
@@ -15915,8 +16250,10 @@ async function resolveTopicForWrite(ctx, input) {
15915
16250
  const threshold = typeof input.threshold === "number" && Number.isFinite(input.threshold) ? Math.max(0, Math.min(1, input.threshold)) : DEFAULT_THRESHOLD;
15916
16251
  const createThreshold = Math.max(0.5, Math.min(1, threshold + 0.15));
15917
16252
  const autoCreate = input.autoCreate !== false;
15918
- const { root, byId, childrenByParent } = await loadTopicUniverse(ctx, input);
16253
+ const { root, rootCreated, byId, childrenByParent } = await loadTopicUniverse(ctx, input);
15919
16254
  const trace = [];
16255
+ const scoreCorpus = buildTopicScoreCorpus(byId.values());
16256
+ const scoreTopic = (topic) => scoreTopicMatchWithCorpus(topic, input, scoreCorpus);
15920
16257
  const ownScore = /* @__PURE__ */ new Map();
15921
16258
  const subtreeScore = /* @__PURE__ */ new Map();
15922
16259
  const computeSubtreeScore = (topicId, visited) => {
@@ -15933,7 +16270,7 @@ async function resolveTopicForWrite(ctx, input) {
15933
16270
  return 0;
15934
16271
  }
15935
16272
  if (!ownScore.has(topicId)) {
15936
- ownScore.set(topicId, scoreTopicMatch(topic, input));
16273
+ ownScore.set(topicId, scoreTopic(topic));
15937
16274
  }
15938
16275
  let best = ownScore.get(topicId);
15939
16276
  for (const child of childrenByParent.get(topicId) ?? []) {
@@ -15945,7 +16282,7 @@ async function resolveTopicForWrite(ctx, input) {
15945
16282
  };
15946
16283
  computeSubtreeScore(root.rawId, /* @__PURE__ */ new Set());
15947
16284
  let current = root;
15948
- let currentScore = ownScore.get(root.rawId) ?? scoreTopicMatch(root, input);
16285
+ let currentScore = ownScore.get(root.rawId) ?? scoreTopic(root);
15949
16286
  pushTrace(trace, current, currentScore);
15950
16287
  while (true) {
15951
16288
  const children = (childrenByParent.get(current.rawId) ?? []).filter(
@@ -15956,7 +16293,7 @@ async function resolveTopicForWrite(ctx, input) {
15956
16293
  }
15957
16294
  const rankedChildren = children.map((topic) => {
15958
16295
  if (!ownScore.has(topic.rawId)) {
15959
- ownScore.set(topic.rawId, scoreTopicMatch(topic, input));
16296
+ ownScore.set(topic.rawId, scoreTopic(topic));
15960
16297
  }
15961
16298
  return {
15962
16299
  topic,
@@ -15968,13 +16305,13 @@ async function resolveTopicForWrite(ctx, input) {
15968
16305
  break;
15969
16306
  }
15970
16307
  current = bestChild.topic;
15971
- currentScore = ownScore.get(current.rawId) ?? scoreTopicMatch(current, input);
16308
+ currentScore = ownScore.get(current.rawId) ?? scoreTopic(current);
15972
16309
  pushTrace(trace, current, currentScore);
15973
16310
  }
15974
- let created = false;
16311
+ let created = rootCreated;
15975
16312
  let resolvedTopic = current;
15976
16313
  let finalScore = currentScore;
15977
- if (autoCreate && finalScore < createThreshold) {
16314
+ if (autoCreate && !rootCreated && finalScore < createThreshold) {
15978
16315
  const siblings = childrenByParent.get(current.rawId) ?? [];
15979
16316
  const derivedName = deriveTopicName(input, current).toLowerCase();
15980
16317
  const existingSibling = siblings.find(
@@ -15982,7 +16319,7 @@ async function resolveTopicForWrite(ctx, input) {
15982
16319
  );
15983
16320
  if (existingSibling) {
15984
16321
  resolvedTopic = existingSibling;
15985
- finalScore = scoreTopicMatch(existingSibling, input);
16322
+ finalScore = scoreTopic(existingSibling);
15986
16323
  pushTrace(trace, resolvedTopic, finalScore);
15987
16324
  } else {
15988
16325
  resolvedTopic = await createChildTopic(ctx, current, input);
@@ -16360,9 +16697,7 @@ var ontologyHandlers = {
16360
16697
 
16361
16698
  // ../../apps/mcp-server/src/handlers/ontology-matching.ts
16362
16699
  var ontologyMatchingHandlers = {
16363
- async match_entity_type(args, ctx) {
16364
- return ontologyHandlers.match_entity_type(args, ctx);
16365
- },
16700
+ match_entity_type: ontologyHandlers.match_entity_type,
16366
16701
  async discover_entity_connections(args, ctx) {
16367
16702
  return formatSdkResult(
16368
16703
  await getSdkClient(ctx).context.discoverEntityConnections({
@@ -16539,20 +16874,30 @@ var researchVerificationHandlers = {
16539
16874
  function cleanString(value) {
16540
16875
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
16541
16876
  }
16877
+ function isRecord6(value) {
16878
+ return value !== null && typeof value === "object" && !Array.isArray(value);
16879
+ }
16542
16880
  function prefixId(prefix, value) {
16543
16881
  const cleaned = cleanString(value);
16544
16882
  if (!cleaned) {
16545
16883
  return `${prefix}_unknown`;
16546
16884
  }
16547
- try {
16548
- const decoded = decodePrefixedId(cleaned);
16549
- if (decoded.prefix === prefix) {
16550
- return cleaned;
16551
- }
16552
- } catch {
16885
+ const decoded = tryDecodePrefixedId(cleaned);
16886
+ if (decoded?.prefix === prefix) {
16887
+ return cleaned;
16553
16888
  }
16554
16889
  return encodePrefixedId(prefix, cleaned);
16555
16890
  }
16891
+ function tryDecodePrefixedId(value) {
16892
+ try {
16893
+ return decodePrefixedId(value);
16894
+ } catch (error) {
16895
+ return ignorePrefixedIdDecodeFailure();
16896
+ }
16897
+ }
16898
+ function ignorePrefixedIdDecodeFailure(_error) {
16899
+ return void 0;
16900
+ }
16556
16901
  function mapSelectedIds(value) {
16557
16902
  return {
16558
16903
  invariants: (value.invariants ?? []).map((id) => prefixId("bel", id)),
@@ -16585,7 +16930,7 @@ function toPublicCompiledContext(pack) {
16585
16930
  scopedTopicIds: (pack.scopedTopicIds ?? []).map((id) => prefixId("top", id)),
16586
16931
  generatedAt: pack.generatedAt,
16587
16932
  ranking: pack.rankingProfile,
16588
- summary: pack.summary,
16933
+ summary: isRecord6(pack.summary) ? pack.summary : {},
16589
16934
  invariants: (pack.invariants ?? []).map((belief) => ({
16590
16935
  beliefId: prefixId("bel", belief.nodeId),
16591
16936
  text: belief.canonicalText,
@@ -16635,7 +16980,7 @@ function toPublicCompiledContext(pack) {
16635
16980
  entityId: cleanString(entity.nodeId) ?? "",
16636
16981
  entityType: entity.entityType,
16637
16982
  title: entity.title,
16638
- text: cleanString(entity.canonicalText),
16983
+ text: cleanString(entity.title),
16639
16984
  connectedBeliefCount: entity.connectedBeliefCount,
16640
16985
  connectedEvidenceCount: entity.connectedEvidenceCount,
16641
16986
  score: entity.score,
@@ -16656,7 +17001,7 @@ function toPublicCompiledContext(pack) {
16656
17001
  }))
16657
17002
  } : {}
16658
17003
  },
16659
- diagnostics: pack.diagnostics,
17004
+ diagnostics: isRecord6(pack.diagnostics) ? pack.diagnostics : {},
16660
17005
  ...pack.compilationMode ? { compilationMode: pack.compilationMode } : {},
16661
17006
  ...pack.failureContext ? {
16662
17007
  failureContext: {
@@ -16687,10 +17032,20 @@ function toPublicCompiledContext(pack) {
16687
17032
  var scopeContextHandlers = {
16688
17033
  async compile_context(args, ctx) {
16689
17034
  const requestedScope = readTopicIdArg(args);
16690
- if (!requestedScope) {
16691
- throw new Error("[compile_context] topicId is required.");
16692
- }
16693
- const topicId = await resolveTopicScopeId(requestedScope, "compile_context");
17035
+ const query = readString(args.query);
17036
+ if (!requestedScope && !query) {
17037
+ throw new Error("[compile_context] query is required when topicId is omitted.");
17038
+ }
17039
+ const topicId = requestedScope ? await resolveTopicScopeId(requestedScope, "compile_context") : (await resolveTopicForWrite(ctx, {
17040
+ workspaceId: readString(args.workspaceId),
17041
+ topicHint: readString(args.topicHint),
17042
+ summary: query ?? "",
17043
+ tags: readStringArray(args.tags),
17044
+ touchedPaths: readStringArray(args.touchedPaths),
17045
+ sourceRef: readString(args.sourceRef),
17046
+ sourceKind: readString(args.sourceKind) ?? "compile_context",
17047
+ autoCreate: false
17048
+ })).topicId;
16694
17049
  if (ctx.sessionType === "user" && Array.isArray(ctx.allowedTopics) && (!ctx.allowedTopics.includes(topicId) || ctx.allowedTopics.length === 0)) {
16695
17050
  throw new Error(
16696
17051
  `[compile_context] Access denied to compile context for topic ${topicId}.`
@@ -17082,9 +17437,10 @@ var MUTATION_TOOL_NAMES = /* @__PURE__ */ new Set([
17082
17437
  "deprecate_ontology_version",
17083
17438
  "manage_write_policy"
17084
17439
  ]);
17085
- function isMutationTool(toolName) {
17086
- return MUTATION_TOOL_NAMES.has(toolName);
17087
- }
17440
+ var isMutationTool = (toolName) => {
17441
+ const normalized = toolName.trim();
17442
+ return normalized.length > 0 && MUTATION_TOOL_NAMES.has(normalized);
17443
+ };
17088
17444
  var sessionWritesBySession = /* @__PURE__ */ new Map();
17089
17445
  function recordWrite(toolName, topicId, sessionId = "default") {
17090
17446
  const record = {
@@ -17216,8 +17572,6 @@ async function checkWritePolicy(toolName, topicId, authCtx) {
17216
17572
  };
17217
17573
  }
17218
17574
  }
17219
-
17220
- // ../../apps/mcp-server/src/credentials.ts
17221
17575
  var LUCERN_HOME = path2.join(os.homedir(), ".lucern");
17222
17576
  path2.join(LUCERN_HOME, "credentials");
17223
17577