@codazen/harmonica-mcp 3.9.0 → 3.9.2

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 (2) hide show
  1. package/dist/index.js +142 -150
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21542,18 +21542,18 @@ async function validateAuth(client, orgIdentifier, isOrgSlug, userId) {
21542
21542
  }
21543
21543
 
21544
21544
  // ../../libs/harmonica-services/src/mcp/org-guard.ts
21545
- async function assertProjectInOrg(client, systemId, orgId) {
21546
- const project = await client.getSystem(systemId);
21547
- if (!project) {
21545
+ async function assertSystemInOrg(client, systemId, orgId) {
21546
+ const system = await client.getSystem(systemId);
21547
+ if (!system) {
21548
21548
  throw new Error(`System not found: "${systemId}"`);
21549
21549
  }
21550
- if (project.orgId !== orgId) {
21550
+ if (system.orgId !== orgId) {
21551
21551
  throw new Error(
21552
21552
  `System "${systemId}" does not belong to the configured organization. Access denied.`
21553
21553
  );
21554
21554
  }
21555
21555
  }
21556
- async function assertRevisionInProject(client, revisionId, systemId) {
21556
+ async function assertRevisionInSystem(client, revisionId, systemId) {
21557
21557
  const revision = await client.getRevision(revisionId);
21558
21558
  if (!revision) {
21559
21559
  throw new Error(`Revision not found: "${revisionId}"`);
@@ -21575,18 +21575,6 @@ async function assertBeatInOrg(client, beatId, orgId) {
21575
21575
  );
21576
21576
  }
21577
21577
  }
21578
- async function fetchProjectInOrg(client, systemId, orgId) {
21579
- const project = await client.getSystem(systemId);
21580
- if (!project) {
21581
- throw new Error(`System not found: "${systemId}"`);
21582
- }
21583
- if (project.orgId !== orgId) {
21584
- throw new Error(
21585
- `System "${systemId}" does not belong to the configured organization. Access denied.`
21586
- );
21587
- }
21588
- return project;
21589
- }
21590
21578
  async function fetchSystemInOrg(client, systemId, orgId) {
21591
21579
  const system = await client.getSystem(systemId);
21592
21580
  if (!system) {
@@ -21979,7 +21967,7 @@ function registerActivityTools(server, ctx, client) {
21979
21967
  importance: external_exports.enum(IMPORTANCE_VALUES).optional().describe("Filter by minimum importance level")
21980
21968
  },
21981
21969
  async ({ systemId, limit, importance }) => {
21982
- await assertProjectInOrg(client, systemId, ctx.orgId);
21970
+ await assertSystemInOrg(client, systemId, ctx.orgId);
21983
21971
  const { entries, hasMore } = await client.listSystemActivities(systemId, {
21984
21972
  limit: limit ?? 30,
21985
21973
  minImportance: importance
@@ -22011,7 +21999,7 @@ function registerAnalysisTools(server, ctx, client) {
22011
21999
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum cluster size. Default 3")
22012
22000
  },
22013
22001
  async ({ systemId, entityType, threshold, minClusterSize }) => {
22014
- await assertProjectInOrg(client, systemId, ctx.orgId);
22002
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22015
22003
  const result = await client.clusterEntities(systemId, entityType, { threshold, minClusterSize });
22016
22004
  if (result.clusters.length === 0) {
22017
22005
  return {
@@ -22043,7 +22031,7 @@ Analyzed: ${result.totalAnalyzed} | Clusters: ${result.clusters.length} | Noise:
22043
22031
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum cluster size. Default 2")
22044
22032
  },
22045
22033
  async ({ systemId, analysisType, entityType, threshold, minClusterSize }) => {
22046
- await assertProjectInOrg(client, systemId, ctx.orgId);
22034
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22047
22035
  const result = await client.runClusteredAnalysis(systemId, analysisType, {
22048
22036
  entityType,
22049
22037
  threshold,
@@ -22078,7 +22066,7 @@ function registerAuditIntegrityTools(server, ctx, client) {
22078
22066
  },
22079
22067
  async ({ systemId, limit }) => {
22080
22068
  try {
22081
- await assertProjectInOrg(client, systemId, ctx.orgId);
22069
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22082
22070
  const report = await client.verifyAuditIntegrity(systemId, { limit });
22083
22071
  const lines = [
22084
22072
  `## Audit Integrity Report`,
@@ -22207,7 +22195,7 @@ function registerBaselineTools(server, ctx, client) {
22207
22195
  },
22208
22196
  async ({ systemId, beatIds, runChecks, localPath }) => {
22209
22197
  try {
22210
- await assertProjectInOrg(client, systemId, ctx.orgId);
22198
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22211
22199
  const task = await client.baselineSystem(systemId, { beatIds, runChecks, localPath });
22212
22200
  return {
22213
22201
  content: [{
@@ -22296,7 +22284,7 @@ Rationale: ${rationale}`;
22296
22284
  },
22297
22285
  async ({ systemId, items, reason }) => {
22298
22286
  try {
22299
- await assertProjectInOrg(client, systemId, ctx.orgId);
22287
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22300
22288
  const result = await client.bulkReassignRevisions(items, reason, ctx.user.userId);
22301
22289
  const lines = [
22302
22290
  `# Bulk reassignment complete: ${result.reassigned.length} succeeded, ${result.failed.length} failed`,
@@ -22335,7 +22323,7 @@ Rationale: ${rationale}`;
22335
22323
  },
22336
22324
  async ({ systemId, revisionId, toBeatId, reason }) => {
22337
22325
  try {
22338
- await assertProjectInOrg(client, systemId, ctx.orgId);
22326
+ await assertSystemInOrg(client, systemId, ctx.orgId);
22339
22327
  const result = await client.reassignRevision(revisionId, toBeatId, reason, ctx.user.userId);
22340
22328
  const text = [
22341
22329
  `Revision reassigned successfully.`,
@@ -23103,7 +23091,7 @@ function registerBeatTools(server, ctx, client) {
23103
23091
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived and deprecated beats (excluded by default)")
23104
23092
  },
23105
23093
  async ({ systemId, offset = 0, limit = 100, includeArchived }) => {
23106
- await assertProjectInOrg(client, systemId, ctx.orgId);
23094
+ await assertSystemInOrg(client, systemId, ctx.orgId);
23107
23095
  let beats = await client.listSystemBeats(systemId);
23108
23096
  if (!includeArchived) {
23109
23097
  beats = beats.filter((b) => b.beatStatus !== "archived");
@@ -23169,7 +23157,7 @@ function registerBeatTools(server, ctx, client) {
23169
23157
  },
23170
23158
  async ({ systemId, title, description, tags, priority, estimatedEffort, beatId, humanAssignee, agentAssignee, branch }) => {
23171
23159
  try {
23172
- await fetchProjectInOrg(client, systemId, ctx.orgId);
23160
+ await fetchSystemInOrg(client, systemId, ctx.orgId);
23173
23161
  const resolvedBeatId = beatId ?? await client.getNextBeatId(systemId);
23174
23162
  const beat = await client.createBeat({
23175
23163
  beatId: resolvedBeatId,
@@ -23286,7 +23274,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23286
23274
  },
23287
23275
  async ({ systemId, beatId, status, includeArchived }) => {
23288
23276
  try {
23289
- await assertProjectInOrg(client, systemId, ctx.orgId);
23277
+ await assertSystemInOrg(client, systemId, ctx.orgId);
23290
23278
  const revisions = beatId ? await client.listBeatRevisions(systemId, beatId, { status, includeArchived }) : await client.listSystemRevisions(systemId, { status, includeArchived });
23291
23279
  return { content: [{ type: "text", text: formatRevisionSummaryTable(revisions) }] };
23292
23280
  } catch (err) {
@@ -23972,7 +23960,7 @@ function registerCheckTools(server, ctx, client) {
23972
23960
  },
23973
23961
  async ({ checkType, targetId, systemId, branch, localPath }) => {
23974
23962
  try {
23975
- await assertProjectInOrg(client, systemId, ctx.orgId);
23963
+ await assertSystemInOrg(client, systemId, ctx.orgId);
23976
23964
  const targetError = checkType === "build_quality" ? buildQualityTargetError(targetId) : checkType === "beat_version_quality" || checkType === "plan_quality" ? beatVersionQualityTargetError(targetId) : null;
23977
23965
  if (targetError) {
23978
23966
  return { content: [{ type: "text", text: targetError }], isError: true };
@@ -24560,7 +24548,7 @@ function registerDropQualityTools(server, ctx, client) {
24560
24548
  },
24561
24549
  async ({ dropId, systemId, wait }) => {
24562
24550
  try {
24563
- await assertProjectInOrg(client, systemId, ctx.orgId);
24551
+ await assertSystemInOrg(client, systemId, ctx.orgId);
24564
24552
  const drop = await client.getDrop(dropId);
24565
24553
  if (!drop) {
24566
24554
  return { content: [{ type: "text", text: `Drop "${dropId}" not found.` }], isError: true };
@@ -24954,8 +24942,8 @@ function registerEmbeddingTools(server, ctx, client) {
24954
24942
  systemId: external_exports.string().describe("The system ID to embed entities for")
24955
24943
  },
24956
24944
  async ({ systemId }) => {
24957
- await assertProjectInOrg(client, systemId, ctx.orgId);
24958
- const result = await client.embedProjectEntities(systemId);
24945
+ await assertSystemInOrg(client, systemId, ctx.orgId);
24946
+ const result = await client.embedSystemEntities(systemId);
24959
24947
  const text = `## Embedding Results
24960
24948
 
24961
24949
  - Embedded: ${result.embedded}
@@ -24986,7 +24974,7 @@ function registerEmbeddingTools(server, ctx, client) {
24986
24974
  }
24987
24975
  let scope;
24988
24976
  if (systemId) {
24989
- await assertProjectInOrg(client, systemId, ctx.orgId);
24977
+ await assertSystemInOrg(client, systemId, ctx.orgId);
24990
24978
  scope = { systemId };
24991
24979
  } else if (teamspaceId) {
24992
24980
  await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
@@ -25012,7 +25000,7 @@ function registerEmbeddingTools(server, ctx, client) {
25012
25000
  beatId: external_exports.string().optional().describe("Restrict results to notes belonging to a specific beat (DynamoDB post-filter)")
25013
25001
  },
25014
25002
  async ({ noteId, systemId, threshold, limit, includeArchived = false, noteStatus, beatId }) => {
25015
- await assertProjectInOrg(client, systemId, ctx.orgId);
25003
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25016
25004
  const ids = Array.isArray(noteId) ? noteId : [noteId];
25017
25005
  if (ids.length === 1) {
25018
25006
  const results = await client.findSimilarNotes(ids[0], systemId, { threshold, limit, includeArchived, noteStatus, beatId });
@@ -25053,7 +25041,7 @@ function registerEmbeddingTools(server, ctx, client) {
25053
25041
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10")
25054
25042
  },
25055
25043
  async ({ noteId, systemId, threshold, limit }) => {
25056
- await assertProjectInOrg(client, systemId, ctx.orgId);
25044
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25057
25045
  const results = await client.findRelatedBeats(noteId, systemId, { threshold, limit });
25058
25046
  const text = formatSimilarityResults(results, noteId, "Related Beats");
25059
25047
  return { content: [{ type: "text", text }] };
@@ -25069,7 +25057,7 @@ function registerEmbeddingTools(server, ctx, client) {
25069
25057
  includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false (excludes them). Set true for thematic grouping pass.")
25070
25058
  },
25071
25059
  async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25072
- await assertProjectInOrg(client, systemId, ctx.orgId);
25060
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25073
25061
  const result = await client.clusterNotes(systemId, { threshold, minClusterSize, excludeAlreadyLinked: !includeLinked });
25074
25062
  if (result.clusters.length === 0) {
25075
25063
  return { content: [{ type: "text", text: `No clusters found among ${result.totalAnalyzed} analyzed notes. Try lowering the threshold.` }] };
@@ -25100,7 +25088,7 @@ ${members}`;
25100
25088
  includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false.")
25101
25089
  },
25102
25090
  async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25103
- await assertProjectInOrg(client, systemId, ctx.orgId);
25091
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25104
25092
  const result = await client.consolidateNotes(systemId, { threshold, minClusterSize, includeLinked });
25105
25093
  if (result.proposals.length === 0) {
25106
25094
  return { content: [{ type: "text", text: `No consolidation candidates found among ${result.totalAnalyzed} analyzed notes.` }] };
@@ -25341,7 +25329,7 @@ function registerLayerTools(server, ctx, client) {
25341
25329
  },
25342
25330
  async ({ systemId }) => {
25343
25331
  try {
25344
- await assertProjectInOrg(client, systemId, ctx.orgId);
25332
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25345
25333
  } catch (err) {
25346
25334
  const message = err instanceof Error ? err.message : String(err);
25347
25335
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25366,7 +25354,7 @@ function registerLayerTools(server, ctx, client) {
25366
25354
  },
25367
25355
  async ({ systemId, layerId }) => {
25368
25356
  try {
25369
- await assertProjectInOrg(client, systemId, ctx.orgId);
25357
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25370
25358
  } catch (err) {
25371
25359
  const message = err instanceof Error ? err.message : String(err);
25372
25360
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25401,7 +25389,7 @@ function registerLayerTools(server, ctx, client) {
25401
25389
  },
25402
25390
  async ({ systemId, layerKey, name, description, parentLayerId, repoPatterns }) => {
25403
25391
  try {
25404
- await assertProjectInOrg(client, systemId, ctx.orgId);
25392
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25405
25393
  } catch (err) {
25406
25394
  const message = err instanceof Error ? err.message : String(err);
25407
25395
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25440,7 +25428,7 @@ ${formatLayerDetail(layer)}` }] };
25440
25428
  },
25441
25429
  async ({ systemId, layerId, name, description, repoPatterns }) => {
25442
25430
  try {
25443
- await assertProjectInOrg(client, systemId, ctx.orgId);
25431
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25444
25432
  } catch (err) {
25445
25433
  const message = err instanceof Error ? err.message : String(err);
25446
25434
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25484,7 +25472,7 @@ ${formatLayerDetail(layer)}` }] };
25484
25472
  },
25485
25473
  async ({ systemId, layerId, status, reason }) => {
25486
25474
  try {
25487
- await assertProjectInOrg(client, systemId, ctx.orgId);
25475
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25488
25476
  } catch (err) {
25489
25477
  const message = err instanceof Error ? err.message : String(err);
25490
25478
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25828,7 +25816,7 @@ function registerNextActionsTools(server, ctx, client) {
25828
25816
  },
25829
25817
  async ({ systemId, limit }) => {
25830
25818
  try {
25831
- await assertProjectInOrg(client, systemId, ctx.orgId);
25819
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25832
25820
  const actions = await client.getNextActions(systemId, { limit });
25833
25821
  return { content: [{ type: "text", text: formatNextActions(actions) }] };
25834
25822
  } catch (err) {
@@ -25845,7 +25833,7 @@ function registerNextActionsTools(server, ctx, client) {
25845
25833
  },
25846
25834
  async ({ systemId }) => {
25847
25835
  try {
25848
- await assertProjectInOrg(client, systemId, ctx.orgId);
25836
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25849
25837
  const actions = await client.listTriageActions(systemId);
25850
25838
  return { content: [{ type: "text", text: formatTriageActions(actions) }] };
25851
25839
  } catch (err) {
@@ -25863,7 +25851,7 @@ function registerNextActionsTools(server, ctx, client) {
25863
25851
  },
25864
25852
  async ({ systemId, actionKey }) => {
25865
25853
  try {
25866
- await assertProjectInOrg(client, systemId, ctx.orgId);
25854
+ await assertSystemInOrg(client, systemId, ctx.orgId);
25867
25855
  await client.dismissTriageAction(systemId, actionKey, ctx.user?.userId ?? "mcp");
25868
25856
  return { content: [{ type: "text", text: `Action ${actionKey} dismissed.` }] };
25869
25857
  } catch (err) {
@@ -26341,7 +26329,17 @@ var normalizeSystemId = (raw) => {
26341
26329
  const b = raw;
26342
26330
  const id = b["systemId"] || b["projectId"];
26343
26331
  if (!id) return raw;
26344
- return { ...b, systemId: id, projectId: id };
26332
+ return { ...b, systemId: id };
26333
+ };
26334
+ var normalizeSystemIdAndTitle = (raw) => {
26335
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26336
+ const b = raw;
26337
+ const id = b["systemId"] || b["projectId"];
26338
+ const title = b["systemTitle"] || b["projectTitle"];
26339
+ const result = { ...b };
26340
+ if (id) result["systemId"] = id;
26341
+ if (title) result["systemTitle"] = title;
26342
+ return result;
26345
26343
  };
26346
26344
  var BeatApiSchema = external_exports.preprocess(
26347
26345
  (raw) => {
@@ -26349,12 +26347,11 @@ var BeatApiSchema = external_exports.preprocess(
26349
26347
  const b = raw;
26350
26348
  const id = b["systemId"] || b["projectId"];
26351
26349
  if (!id) return raw;
26352
- return { ...b, systemId: id, projectId: id };
26350
+ return { ...b, systemId: id };
26353
26351
  },
26354
26352
  external_exports.object({
26355
26353
  beatId: external_exports.string(),
26356
26354
  systemId: external_exports.string(),
26357
- projectId: external_exports.string(),
26358
26355
  /** Source project's title; attached by the flat /api/beats endpoint for
26359
26356
  * multi-project (teamspace/org) responses. */
26360
26357
  projectName: external_exports.string().nullish(),
@@ -26386,7 +26383,6 @@ var RevisionApiSchema = external_exports.preprocess(
26386
26383
  revisionId: external_exports.string(),
26387
26384
  beatId: external_exports.string(),
26388
26385
  systemId: external_exports.string(),
26389
- projectId: external_exports.string(),
26390
26386
  status: RevisionStatusSchema,
26391
26387
  title: external_exports.string().default(""),
26392
26388
  versionNumber: external_exports.number(),
@@ -26413,7 +26409,6 @@ var NoteApiSchema = external_exports.preprocess(normalizeSystemId, external_expo
26413
26409
  status: NoteStatusSchema,
26414
26410
  content: external_exports.string(),
26415
26411
  systemId: external_exports.string().nullish(),
26416
- projectId: external_exports.string().nullish(),
26417
26412
  beatId: external_exports.string().nullish(),
26418
26413
  revisionId: external_exports.string().nullish(),
26419
26414
  rationale: external_exports.string().nullish(),
@@ -26429,7 +26424,6 @@ var SystemApiSchema = external_exports.preprocess(
26429
26424
  normalizeSystemId,
26430
26425
  external_exports.object({
26431
26426
  systemId: external_exports.string(),
26432
- projectId: external_exports.string(),
26433
26427
  title: external_exports.string(),
26434
26428
  status: SystemStatusSchema,
26435
26429
  description: external_exports.string().optional(),
@@ -26631,8 +26625,7 @@ var CheckApiSchema = external_exports.preprocess(
26631
26625
  checkType: CheckTypeSchema,
26632
26626
  targetType: CheckTargetTypeSchema,
26633
26627
  targetId: external_exports.string(),
26634
- systemId: external_exports.string().optional(),
26635
- projectId: external_exports.string().optional(),
26628
+ systemId: external_exports.string(),
26636
26629
  overallScore: external_exports.number(),
26637
26630
  dimensions: external_exports.array(CheckDimensionScoreSchema),
26638
26631
  summary: external_exports.string(),
@@ -26651,8 +26644,7 @@ var SystemCheckApiSchema = external_exports.preprocess(
26651
26644
  checkType: CheckTypeSchema,
26652
26645
  targetType: CheckTargetTypeSchema,
26653
26646
  targetId: external_exports.string(),
26654
- systemId: external_exports.string().optional(),
26655
- projectId: external_exports.string().optional(),
26647
+ systemId: external_exports.string(),
26656
26648
  overallScore: external_exports.number(),
26657
26649
  summary: external_exports.string(),
26658
26650
  triggeredBy: external_exports.string().optional(),
@@ -26712,7 +26704,6 @@ var ActivitySchema = external_exports.preprocess(
26712
26704
  external_exports.object({
26713
26705
  activityId: external_exports.string(),
26714
26706
  systemId: external_exports.string(),
26715
- projectId: external_exports.string(),
26716
26707
  action: external_exports.string(),
26717
26708
  importance: external_exports.enum(["minor", "moderate", "major"]),
26718
26709
  reason: external_exports.string(),
@@ -26758,7 +26749,6 @@ var TriageActionSchema = external_exports.preprocess(
26758
26749
  external_exports.object({
26759
26750
  actionKey: external_exports.string(),
26760
26751
  systemId: external_exports.string(),
26761
- projectId: external_exports.string(),
26762
26752
  entityId: external_exports.string().optional(),
26763
26753
  entityType: external_exports.enum(["beat", "revision", "note"]).optional(),
26764
26754
  beatId: external_exports.string().optional(),
@@ -26799,7 +26789,6 @@ var SearchResultSchema = external_exports.preprocess(normalizeSystemId, external
26799
26789
  score: external_exports.number(),
26800
26790
  snippet: external_exports.string(),
26801
26791
  systemId: external_exports.string().optional(),
26802
- projectId: external_exports.string().optional(),
26803
26792
  projectName: external_exports.string().optional(),
26804
26793
  teamspaceId: external_exports.string().optional()
26805
26794
  }).passthrough());
@@ -26838,33 +26827,28 @@ var LandscapeMetaSchema = external_exports.object({
26838
26827
  cacheHit: external_exports.boolean(),
26839
26828
  projectionHash: external_exports.string()
26840
26829
  }).passthrough();
26841
- var LandscapeResponseSchema = external_exports.preprocess(
26842
- (raw) => {
26843
- if (typeof raw !== "object" || raw === null) return raw;
26844
- const r = raw;
26845
- const sb = r["scopeBoundary"];
26846
- if (typeof sb === "object" && sb !== null) {
26847
- const s = sb;
26848
- const id = s["systemId"] || s["projectId"];
26849
- if (id) return { ...r, scopeBoundary: { ...s, systemId: id, projectId: id } };
26850
- }
26851
- return r;
26852
- },
26853
- external_exports.object({
26854
- points: external_exports.array(LandscapePointSchema),
26855
- links: external_exports.array(external_exports.object({ from: external_exports.string(), to: external_exports.string() }).passthrough()),
26856
- clusters: external_exports.array(LandscapeClusterSchema),
26857
- scopeBoundary: external_exports.object({
26858
- systemId: external_exports.string().optional(),
26859
- projectId: external_exports.string().optional(),
26830
+ var LandscapeResponseSchema = external_exports.object({
26831
+ points: external_exports.array(LandscapePointSchema),
26832
+ links: external_exports.array(external_exports.object({ from: external_exports.string(), to: external_exports.string() }).passthrough()),
26833
+ clusters: external_exports.array(LandscapeClusterSchema),
26834
+ scopeBoundary: external_exports.preprocess(
26835
+ (raw) => {
26836
+ if (typeof raw !== "object" || raw === null) return raw;
26837
+ const o = raw;
26838
+ const id = o["systemId"] ?? o["projectId"];
26839
+ if (!id) return raw;
26840
+ return { ...o, systemId: id };
26841
+ },
26842
+ external_exports.object({
26843
+ systemId: external_exports.string(),
26860
26844
  cx: external_exports.number(),
26861
26845
  cy: external_exports.number(),
26862
26846
  radius: external_exports.number(),
26863
26847
  label: external_exports.string()
26864
- }).passthrough().optional(),
26865
- meta: LandscapeMetaSchema
26866
- }).passthrough()
26867
- );
26848
+ }).passthrough()
26849
+ ).optional(),
26850
+ meta: LandscapeMetaSchema
26851
+ }).passthrough();
26868
26852
  var ClusterDescriptionSchema = external_exports.object({
26869
26853
  title: external_exports.string(),
26870
26854
  description: external_exports.string()
@@ -26873,7 +26857,6 @@ var BeatVersionItemSchema = external_exports.preprocess(normalizeSystemId, exter
26873
26857
  beatVersionId: external_exports.string(),
26874
26858
  beatId: external_exports.string(),
26875
26859
  systemId: external_exports.string().optional(),
26876
- projectId: external_exports.string().optional(),
26877
26860
  status: external_exports.string()
26878
26861
  }).passthrough());
26879
26862
  var BeatVersionsResponseSchema = external_exports.object({
@@ -26882,7 +26865,6 @@ var BeatVersionsResponseSchema = external_exports.object({
26882
26865
  var ConversationSummaryBaseSchema = external_exports.object({
26883
26866
  conversationId: external_exports.string(),
26884
26867
  systemId: external_exports.string().optional(),
26885
- projectId: external_exports.string().optional(),
26886
26868
  teamspaceId: external_exports.string().optional(),
26887
26869
  userId: external_exports.string(),
26888
26870
  conversationType: external_exports.string(),
@@ -27001,6 +26983,13 @@ var WorkItemApiSchema = external_exports.object({
27001
26983
  costPerValueUnit: external_exports.number().optional(),
27002
26984
  /** Optional capability-lineage link to a Beat Version (v2, N-4E09-6574). */
27003
26985
  beatVersionId: external_exports.string().optional(),
26986
+ /**
26987
+ * Optional link to the Work Order this item delivers part of (B-331,
26988
+ * N-4E09-7160). Absent means unattached, which is a supported state — a
26989
+ * reader grouping a Track's work by Order must render those as their own
26990
+ * named group, not hide them.
26991
+ */
26992
+ workOrderId: external_exports.string().min(1).optional(),
27004
26993
  createdBy: external_exports.string().optional(),
27005
26994
  version: external_exports.number().optional(),
27006
26995
  createdAt: external_exports.string(),
@@ -27137,12 +27126,11 @@ var LayerApiSchema = external_exports.preprocess(
27137
27126
  const l = raw;
27138
27127
  const id = l["systemId"] || l["projectId"];
27139
27128
  if (!id) return raw;
27140
- return { ...l, systemId: id, projectId: id };
27129
+ return { ...l, systemId: id };
27141
27130
  },
27142
27131
  external_exports.object({
27143
27132
  layerId: external_exports.string(),
27144
27133
  systemId: external_exports.string(),
27145
- projectId: external_exports.string(),
27146
27134
  layerKey: external_exports.string(),
27147
27135
  name: external_exports.string(),
27148
27136
  description: external_exports.string().nullish(),
@@ -27199,7 +27187,6 @@ var DeliverableGroupApiSchema = external_exports.preprocess(normalizeSystemId, e
27199
27187
  groupCode: external_exports.string(),
27200
27188
  teamspaceId: external_exports.string(),
27201
27189
  systemId: external_exports.string().nullish(),
27202
- projectId: external_exports.string().nullish(),
27203
27190
  movementId: external_exports.string().nullish(),
27204
27191
  orgId: external_exports.string(),
27205
27192
  title: external_exports.string(),
@@ -27238,8 +27225,7 @@ var WorkPlanBeatVersionApiSchema = external_exports.preprocess(
27238
27225
  external_exports.object({
27239
27226
  beatVersionId: external_exports.string(),
27240
27227
  beatId: external_exports.string(),
27241
- systemId: external_exports.string().optional(),
27242
- projectId: external_exports.string().optional(),
27228
+ systemId: external_exports.string(),
27243
27229
  versionNumber: external_exports.number(),
27244
27230
  title: external_exports.string(),
27245
27231
  status: BeatVersionStatusSchema,
@@ -27259,7 +27245,7 @@ var RevisionClaimActorSchema = external_exports.union([
27259
27245
  external_exports.object({ kind: external_exports.literal("user"), userId: external_exports.string() }),
27260
27246
  external_exports.preprocess(
27261
27247
  normalizeSystemId,
27262
- external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), systemId: external_exports.string(), projectId: external_exports.string() })
27248
+ external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), systemId: external_exports.string() })
27263
27249
  )
27264
27250
  ]);
27265
27251
  var RevisionClaimSchema = external_exports.object({
@@ -27272,16 +27258,15 @@ var RevisionClaimsResponseSchema = external_exports.object({
27272
27258
  claims: external_exports.array(RevisionClaimSchema)
27273
27259
  }).passthrough();
27274
27260
  var ProposedRevisionSchema = external_exports.preprocess(
27275
- normalizeSystemId,
27261
+ normalizeSystemIdAndTitle,
27276
27262
  external_exports.object({
27277
27263
  revisionId: external_exports.string(),
27278
27264
  title: external_exports.string(),
27279
27265
  state: external_exports.string(),
27280
27266
  beatId: external_exports.string(),
27281
27267
  beatTitle: external_exports.string().optional(),
27282
- systemId: external_exports.string().optional(),
27283
- projectId: external_exports.string().optional(),
27284
- projectTitle: external_exports.string().optional()
27268
+ systemId: external_exports.string(),
27269
+ systemTitle: external_exports.string().optional()
27285
27270
  }).passthrough()
27286
27271
  );
27287
27272
  var ReleaseIntervalSchema = external_exports.object({
@@ -27290,16 +27275,15 @@ var ReleaseIntervalSchema = external_exports.object({
27290
27275
  isFallback: external_exports.boolean()
27291
27276
  }).passthrough();
27292
27277
  var EligibleBeatVersionForDropSchema = external_exports.preprocess(
27293
- normalizeSystemId,
27278
+ normalizeSystemIdAndTitle,
27294
27279
  external_exports.object({
27295
27280
  beatVersionId: external_exports.string(),
27296
27281
  title: external_exports.string(),
27297
27282
  state: external_exports.string(),
27298
27283
  beatId: external_exports.string(),
27299
27284
  beatTitle: external_exports.string().optional(),
27300
- systemId: external_exports.string().optional(),
27301
- projectId: external_exports.string().optional(),
27302
- projectTitle: external_exports.string().optional(),
27285
+ systemId: external_exports.string(),
27286
+ systemTitle: external_exports.string().optional(),
27303
27287
  createdAt: external_exports.string(),
27304
27288
  updatedAt: external_exports.string(),
27305
27289
  humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
@@ -27572,7 +27556,7 @@ function registerNoteTools(server, ctx, client) {
27572
27556
  return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
27573
27557
  }
27574
27558
  try {
27575
- await assertProjectInOrg(client, systemId, ctx.orgId);
27559
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27576
27560
  } catch (err) {
27577
27561
  const message = err instanceof Error ? err.message : String(err);
27578
27562
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -27653,7 +27637,7 @@ function registerNoteTools(server, ctx, client) {
27653
27637
  );
27654
27638
  server.tool(
27655
27639
  "list_org_notes",
27656
- "List Notes at the organization level (cross-cutting constraints, org-wide decisions, and context that applies to all projects). Filterable by type and status. Use offset/limit to paginate.",
27640
+ "List Notes at the organization level (cross-cutting constraints, org-wide decisions, and context that applies to all systems). Filterable by type and status. Use offset/limit to paginate.",
27657
27641
  {
27658
27642
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Filter by note type"),
27659
27643
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
@@ -27679,7 +27663,7 @@ function registerNoteTools(server, ctx, client) {
27679
27663
  );
27680
27664
  server.tool(
27681
27665
  "list_movement_notes",
27682
- "List Notes scoped directly to a Movement (commercial SOW container) \u2014 engagement-level knowledge such as SOW constraints, scope boundaries, and commercial decisions. Not aggregated from projects. Filterable by type and status.",
27666
+ "List Notes scoped directly to a Movement (commercial SOW container) \u2014 engagement-level knowledge such as SOW constraints, scope boundaries, and commercial decisions. Not aggregated from systems. Filterable by type and status.",
27683
27667
  {
27684
27668
  movementId: external_exports.string().min(1).describe("The Movement ID"),
27685
27669
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Filter by note type"),
@@ -27718,7 +27702,7 @@ function registerNoteTools(server, ctx, client) {
27718
27702
  systemId: external_exports.string().describe("The system ID")
27719
27703
  },
27720
27704
  async ({ systemId }) => {
27721
- await assertProjectInOrg(client, systemId, ctx.orgId);
27705
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27722
27706
  const DOCUMENT_FETCH_LIMIT = 5e3;
27723
27707
  const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
27724
27708
  const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
@@ -27753,7 +27737,7 @@ function registerNoteTools(server, ctx, client) {
27753
27737
  }
27754
27738
  return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
27755
27739
  }
27756
- await assertProjectInOrg(client, systemId, ctx.orgId);
27740
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27757
27741
  const note = await client.getNote(noteId);
27758
27742
  if (!note) {
27759
27743
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27792,7 +27776,7 @@ function registerNoteTools(server, ctx, client) {
27792
27776
  const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
27793
27777
  let note;
27794
27778
  if (systemId) {
27795
- await assertProjectInOrg(client, systemId, ctx.orgId);
27779
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27796
27780
  const projectCode = systemId.substring(0, 4).toUpperCase();
27797
27781
  const noteId = await client.getNextNoteId(projectCode, systemId);
27798
27782
  note = await client.createNote({
@@ -27920,7 +27904,7 @@ ${text}` }] };
27920
27904
 
27921
27905
  ${formatNoteDetail(updated2)}` }] };
27922
27906
  }
27923
- await assertProjectInOrg(client, systemId, ctx.orgId);
27907
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27924
27908
  const updated = await client.updateNote(noteId, {
27925
27909
  ...noteType !== void 0 && { noteType },
27926
27910
  status,
@@ -27962,7 +27946,7 @@ ${text}` }] };
27962
27946
  },
27963
27947
  async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
27964
27948
  try {
27965
- await assertProjectInOrg(client, systemId, ctx.orgId);
27949
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27966
27950
  const result = await client.bulkUpdateNotes(noteIds, {
27967
27951
  ...noteType !== void 0 && { noteType },
27968
27952
  status,
@@ -28001,7 +27985,7 @@ ${text}` }] };
28001
27985
  },
28002
27986
  async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
28003
27987
  try {
28004
- await assertProjectInOrg(client, systemId, ctx.orgId);
27988
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28005
27989
  const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
28006
27990
  if (!updated) {
28007
27991
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -28023,7 +28007,7 @@ ${text}` }] };
28023
28007
  },
28024
28008
  async ({ systemId, noteId, reason }) => {
28025
28009
  try {
28026
- await assertProjectInOrg(client, systemId, ctx.orgId);
28010
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28027
28011
  const note = await client.getNote(noteId);
28028
28012
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
28029
28013
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -28050,7 +28034,7 @@ ${text}` }] };
28050
28034
  },
28051
28035
  async ({ systemId, noteId, reason }) => {
28052
28036
  try {
28053
- await assertProjectInOrg(client, systemId, ctx.orgId);
28037
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28054
28038
  const note = await client.getNote(noteId);
28055
28039
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
28056
28040
  return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
@@ -28080,7 +28064,7 @@ ${text}` }] };
28080
28064
  systemId: external_exports.string().describe("The system ID")
28081
28065
  },
28082
28066
  async ({ systemId }) => {
28083
- await assertProjectInOrg(client, systemId, ctx.orgId);
28067
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28084
28068
  const assumptions = await client.getKeyAssumptions(systemId);
28085
28069
  if (assumptions.length === 0) {
28086
28070
  return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
@@ -28783,7 +28767,7 @@ function registerOnboardingTools(server, ctx, client) {
28783
28767
  analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
28784
28768
  },
28785
28769
  async ({ systemId, systemTitle, projectDescription, analysisJson }) => {
28786
- await assertProjectInOrg(client, systemId, ctx.orgId);
28770
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28787
28771
  const result = await client.draftOnboardingBeats(systemId, { systemTitle, projectDescription, analysisJson });
28788
28772
  if (!result.suggestions || result.suggestions.length === 0) {
28789
28773
  return {
@@ -28861,7 +28845,7 @@ Error: ${msg}`);
28861
28845
  } catch (err) {
28862
28846
  const msg = err instanceof Error ? err.message : String(err);
28863
28847
  lines.push(`
28864
- Projects created: ${createdProjects.length}`);
28848
+ Systems created: ${createdProjects.length}`);
28865
28849
  lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: system "${p.title}"
28866
28850
  Error: ${msg}`);
28867
28851
  return { content: [{ type: "text", text: lines.join("\n") }] };
@@ -28869,7 +28853,7 @@ Error: ${msg}`);
28869
28853
  }
28870
28854
  if (createdProjects.length > 0) {
28871
28855
  lines.push(`
28872
- Projects (${createdProjects.length}):`);
28856
+ Systems (${createdProjects.length}):`);
28873
28857
  for (const p of createdProjects) lines.push(` [${p.systemId}] ${p.title}`);
28874
28858
  }
28875
28859
  const createdNotes = [];
@@ -29084,7 +29068,7 @@ function registerOrganizationTools(server, ctx, client) {
29084
29068
  if (system.orgId !== ctx.orgId) {
29085
29069
  return { content: [{ type: "text", text: `System "${systemId}" does not belong to the configured organization.` }], isError: true };
29086
29070
  }
29087
- const result = await client.transferProject(ctx.orgId, systemId, targetOrgId);
29071
+ const result = await client.transferSystem(ctx.orgId, systemId, targetOrgId);
29088
29072
  if (result.error) {
29089
29073
  return { content: [{ type: "text", text: result.error }], isError: true };
29090
29074
  }
@@ -29235,7 +29219,7 @@ function registerPlanQualityTools(server, ctx, client) {
29235
29219
  isError: true
29236
29220
  };
29237
29221
  }
29238
- await assertProjectInOrg(client, systemId, ctx.orgId);
29222
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29239
29223
  const opts = branch || localPath ? { branch, localPath } : void 0;
29240
29224
  const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
29241
29225
  if (!wait) {
@@ -29382,7 +29366,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29382
29366
  },
29383
29367
  async ({ systemId, wait }) => {
29384
29368
  try {
29385
- await assertProjectInOrg(client, systemId, ctx.orgId);
29369
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29386
29370
  const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
29387
29371
  if (!wait) {
29388
29372
  return {
@@ -29419,7 +29403,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29419
29403
  const archivedBeatIds = [];
29420
29404
  let totalNotesReassigned = 0;
29421
29405
  try {
29422
- await assertProjectInOrg(client, systemId, ctx.orgId);
29406
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29423
29407
  const uniqueBeatIds = [...new Set(beatIds)];
29424
29408
  const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
29425
29409
  if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
@@ -30172,8 +30156,8 @@ function registerRevisionQualityTools(server, ctx, client) {
30172
30156
  isError: true
30173
30157
  };
30174
30158
  }
30175
- await assertProjectInOrg(client, systemId, ctx.orgId);
30176
- await assertRevisionInProject(client, revisionId, systemId);
30159
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30160
+ await assertRevisionInSystem(client, revisionId, systemId);
30177
30161
  const task = await client.runCheck(systemId, "revision_quality", revisionId);
30178
30162
  if (!wait) {
30179
30163
  return {
@@ -30478,7 +30462,7 @@ function registerSnapshotTools(server, ctx, client) {
30478
30462
  "Export a complete system snapshot for environment sync. On a remote server (production/staging) returns an S3 presigned URL; on a local server writes to a temp file and returns the path.",
30479
30463
  { systemId: external_exports.string().describe("The system ID to export") },
30480
30464
  async ({ systemId }) => {
30481
- await assertProjectInOrg(client, systemId, ctx.orgId);
30465
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30482
30466
  const result = await client.exportSystemSnapshot(systemId);
30483
30467
  if (typeof result === "string") {
30484
30468
  if (!result.startsWith("https://")) {
@@ -30726,7 +30710,7 @@ function registerSubscriptionTools(server, ctx, client) {
30726
30710
  },
30727
30711
  async ({ entityType, entityId, systemId }) => {
30728
30712
  try {
30729
- await assertProjectInOrg(client, systemId, ctx.orgId);
30713
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30730
30714
  await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
30731
30715
  return {
30732
30716
  content: [{
@@ -30790,7 +30774,7 @@ function registerSubscriptionTools(server, ctx, client) {
30790
30774
  }
30791
30775
 
30792
30776
  // ../../libs/harmonica-services/src/mcp/tools/system-lifecycle-tools.ts
30793
- function registerProjectLifecycleTools(server, ctx, client) {
30777
+ function registerSystemLifecycleTools(server, ctx, client) {
30794
30778
  server.tool(
30795
30779
  "transition_system_lifecycle",
30796
30780
  "Advance a System's capability-maturity lifecycle state. Allowed transitions follow the state machine (Concept \u2192 Incubating \u2192 Piloting \u2192 Activated \u2192 Commercializing \u2192 Scaled, plus paused/killed/sunset/archived exits). The activated \u2192 commercializing edge requires a `decisionNoteId` pointing to a system-scoped Decision Note (the governance review). Other gates are advisory.",
@@ -30896,7 +30880,7 @@ function registerSystemTools(server, ctx, client) {
30896
30880
  }) => {
30897
30881
  try {
30898
30882
  const [project, org] = await Promise.all([
30899
- fetchProjectInOrg(client, systemId, ctx.orgId),
30883
+ fetchSystemInOrg(client, systemId, ctx.orgId),
30900
30884
  client.getOrg(ctx.orgId)
30901
30885
  ]);
30902
30886
  const notes = await client.listSystemNotes(systemId, {
@@ -30940,7 +30924,7 @@ function registerSystemTools(server, ctx, client) {
30940
30924
  if (Object.keys(nonEmpty).length === 0) {
30941
30925
  return { content: [{ type: "text", text: "No updates provided." }], isError: true };
30942
30926
  }
30943
- await assertProjectInOrg(client, systemId, ctx.orgId);
30927
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30944
30928
  if (nonEmpty["accountId"] === "") delete nonEmpty["accountId"];
30945
30929
  if (typeof updates.teamspaceId === "string") {
30946
30930
  const teamspace = await client.getTeamspace(updates.teamspaceId);
@@ -31118,10 +31102,10 @@ function registerTeamspaceTools(server, ctx, client) {
31118
31102
  `Created: ${ts.createdAt}`
31119
31103
  ];
31120
31104
  if (tsProjects.length > 0) {
31121
- lines.push("", `Projects (${tsProjects.length}):`);
31105
+ lines.push("", `Systems (${tsProjects.length}):`);
31122
31106
  for (const p of tsProjects) lines.push(` [${p.systemId}] ${p.title} \u2014 ${p.status ?? "unknown"}`);
31123
31107
  } else {
31124
- lines.push("Projects: (none)");
31108
+ lines.push("Systems: (none)");
31125
31109
  }
31126
31110
  if (ts.ensemble && ts.ensemble.length > 0) {
31127
31111
  lines.push("", "Ensemble:");
@@ -31279,7 +31263,7 @@ function registerValueVelocityTools(server, ctx, client) {
31279
31263
  },
31280
31264
  async ({ systemId, lens, withLeverage }) => {
31281
31265
  try {
31282
- await assertProjectInOrg(client, systemId, ctx.orgId);
31266
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31283
31267
  const [rankedResult, staleWResult] = await Promise.allSettled([
31284
31268
  withLeverage ? client.rankSystemBeatVersionsWithLeverage(systemId, lens) : client.rankSystemBeatVersions(systemId, lens),
31285
31269
  client.getStaleWBeatIds(systemId)
@@ -31386,7 +31370,7 @@ function registerValueVelocityTools(server, ctx, client) {
31386
31370
  },
31387
31371
  async ({ systemId, beatId }) => {
31388
31372
  try {
31389
- await assertProjectInOrg(client, systemId, ctx.orgId);
31373
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31390
31374
  const actorId = resolveActorId(ctx);
31391
31375
  if (!actorId) {
31392
31376
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31569,6 +31553,7 @@ function registerWorkItemTools(server, _ctx, client) {
31569
31553
  wi.valueUnits !== void 0 ? `Value Units: ${wi.valueUnits}` : null,
31570
31554
  wi.costPerValueUnit !== void 0 ? `Cost per Value Unit: ${wi.costPerValueUnit}` : null,
31571
31555
  wi.beatVersionId ? `Linked Beat Version: ${wi.beatVersionId}` : null,
31556
+ wi.workOrderId ? `Work Order: ${wi.workOrderId}` : "Work Order: none (unattached)",
31572
31557
  estimateHint ? `Estimate Hint: ${estimateHint.hours}h (source: ${estimateHint.source})` : null,
31573
31558
  wi.createdBy ? `Created By: ${wi.createdBy}` : null,
31574
31559
  `Created: ${wi.createdAt}`,
@@ -31579,7 +31564,7 @@ function registerWorkItemTools(server, _ctx, client) {
31579
31564
  );
31580
31565
  server.tool(
31581
31566
  "create_work_item",
31582
- 'Create a work item under a Track (DeliverableGroup). A work item is an owned, statused unit of operational work \u2014 it is NOT a Beat. Starts in "not_started" status. Optionally link it to a Beat Version for capability lineage (v2, N-4E09-6574) \u2014 the link is one-directional and never required.',
31567
+ 'Create a work item under a Track (DeliverableGroup). A work item is an owned, statused unit of operational work \u2014 it is NOT a Beat. Starts in "not_started" status. Optionally link it to a Beat Version for capability lineage (v2, N-4E09-6574) \u2014 the link is one-directional and never required. Optionally link it to a Work Order it delivers part of; leaving it unattached is a supported state, not a gap to fill (N-4E09-7160).',
31583
31568
  {
31584
31569
  trackId: external_exports.string().describe("The Track (DeliverableGroup) ID this work item belongs to"),
31585
31570
  title: external_exports.string().min(1).max(300).describe("Work item title"),
@@ -31590,9 +31575,10 @@ function registerWorkItemTools(server, _ctx, client) {
31590
31575
  valueUnits: external_exports.number().nonnegative().optional().describe("Optional Value Units (commercial lens) \u2014 independent of effortSize"),
31591
31576
  costPerValueUnit: external_exports.number().nonnegative().optional().describe("Optional price per Value Unit"),
31592
31577
  createdBy: external_exports.string().optional().describe("Optional creator identity for audit"),
31593
- beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist.")
31578
+ beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist."),
31579
+ workOrderId: external_exports.string().min(1).max(128).optional().describe("Optional Work Order this item delivers part of. Must exist. Omit when there is no Order \u2014 unattached work is legitimate, so do not invent one.")
31594
31580
  },
31595
- async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId }) => {
31581
+ async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId, workOrderId }) => {
31596
31582
  if (ownerName === void 0 !== (ownerEmail === void 0)) {
31597
31583
  return {
31598
31584
  content: [{ type: "text", text: "Setting the owner requires both ownerName and ownerEmail. Omit both to create an unowned work item." }],
@@ -31608,7 +31594,8 @@ function registerWorkItemTools(server, _ctx, client) {
31608
31594
  ...valueUnits !== void 0 && { valueUnits },
31609
31595
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
31610
31596
  ...createdBy !== void 0 && { createdBy },
31611
- ...beatVersionId !== void 0 && { beatVersionId }
31597
+ ...beatVersionId !== void 0 && { beatVersionId },
31598
+ ...workOrderId !== void 0 && { workOrderId }
31612
31599
  });
31613
31600
  return {
31614
31601
  content: [
@@ -31622,7 +31609,7 @@ function registerWorkItemTools(server, _ctx, client) {
31622
31609
  );
31623
31610
  server.tool(
31624
31611
  "update_work_item",
31625
- "Update mutable content fields on a work item (title, owner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId). To move it between Tracks use move_work_item; to change its status use transition_work_item_status.",
31612
+ "Update mutable content fields on a work item (title, owner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId, workOrderId). To move it between Tracks use move_work_item; to change its status use transition_work_item_status.",
31626
31613
  {
31627
31614
  workItemId: external_exports.string().describe("The work item ID"),
31628
31615
  title: external_exports.string().min(1).max(300).optional().describe("New title"),
@@ -31634,9 +31621,10 @@ function registerWorkItemTools(server, _ctx, client) {
31634
31621
  effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe("Rough t-shirt effort size (delivery lens). Pass null to clear; omit to leave untouched."),
31635
31622
  valueUnits: external_exports.number().nonnegative().nullable().optional().describe("Value Units (commercial lens). Pass null to clear; omit to leave untouched."),
31636
31623
  costPerValueUnit: external_exports.number().nonnegative().nullable().optional().describe("Price per Value Unit. Pass null to clear; omit to leave untouched."),
31637
- beatVersionId: external_exports.string().min(1).nullable().optional().describe("Link to a Beat Version for capability lineage. Pass null to unlink; omit to leave the current link untouched.")
31624
+ beatVersionId: external_exports.string().min(1).nullable().optional().describe("Link to a Beat Version for capability lineage. Pass null to unlink; omit to leave the current link untouched."),
31625
+ workOrderId: external_exports.string().min(1).max(128).nullable().optional().describe("Link to the Work Order this item delivers part of. Pass null to unlink, leaving the item unattached; omit to leave the current link untouched.")
31638
31626
  },
31639
- async ({ workItemId, title, ownerName, ownerEmail, unassignOwner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId }) => {
31627
+ async ({ workItemId, title, ownerName, ownerEmail, unassignOwner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId, workOrderId }) => {
31640
31628
  const hasOwnerUpdate = ownerName !== void 0 || ownerEmail !== void 0;
31641
31629
  if (hasOwnerUpdate && (ownerName === void 0 || ownerEmail === void 0)) {
31642
31630
  return {
@@ -31650,7 +31638,9 @@ function registerWorkItemTools(server, _ctx, client) {
31650
31638
  isError: true
31651
31639
  };
31652
31640
  }
31653
- const hasUpdates = title !== void 0 || hasOwnerUpdate || unassignOwner === true || committedEstimateHours !== void 0 || actualHours !== void 0 || effortSize !== void 0 || valueUnits !== void 0 || costPerValueUnit !== void 0 || beatVersionId !== void 0;
31641
+ const hasUpdates = title !== void 0 || hasOwnerUpdate || unassignOwner === true || committedEstimateHours !== void 0 || actualHours !== void 0 || effortSize !== void 0 || valueUnits !== void 0 || costPerValueUnit !== void 0 || beatVersionId !== void 0 || // `!== undefined`, never truthiness: an unlink arrives as `null`, and
31642
+ // a truthy check here would report "no updates provided" and drop it.
31643
+ workOrderId !== void 0;
31654
31644
  if (!hasUpdates) {
31655
31645
  return {
31656
31646
  content: [{ type: "text", text: `No updates provided for work item: ${workItemId}` }],
@@ -31666,7 +31656,8 @@ function registerWorkItemTools(server, _ctx, client) {
31666
31656
  ...effortSize !== void 0 && { effortSize },
31667
31657
  ...valueUnits !== void 0 && { valueUnits },
31668
31658
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
31669
- ...beatVersionId !== void 0 && { beatVersionId }
31659
+ ...beatVersionId !== void 0 && { beatVersionId },
31660
+ ...workOrderId !== void 0 && { workOrderId }
31670
31661
  });
31671
31662
  if (!wi) {
31672
31663
  return {
@@ -32105,7 +32096,7 @@ ${JSON.stringify(task.result, null, 2)}
32105
32096
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
32106
32097
  },
32107
32098
  async ({ systemId, status, limit }) => {
32108
- await assertProjectInOrg(client, systemId, ctx.orgId);
32099
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32109
32100
  const tasks = await client.listSystemTasks(systemId, {
32110
32101
  status,
32111
32102
  limit
@@ -32163,7 +32154,7 @@ ${JSON.stringify(task.result, null, 2)}
32163
32154
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
32164
32155
  },
32165
32156
  async ({ systemId, status, limit }) => {
32166
- await assertProjectInOrg(client, systemId, ctx.orgId);
32157
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32167
32158
  const tasks = await client.listSystemTasks(systemId, {
32168
32159
  status,
32169
32160
  limit
@@ -32187,7 +32178,7 @@ ${JSON.stringify(task.result, null, 2)}
32187
32178
  },
32188
32179
  async ({ systemId, beatId }) => {
32189
32180
  try {
32190
- await assertProjectInOrg(client, systemId, ctx.orgId);
32181
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32191
32182
  const task = await client.draftCoda(systemId, beatId);
32192
32183
  return {
32193
32184
  content: [{
@@ -32220,7 +32211,7 @@ ${JSON.stringify(task.result, null, 2)}
32220
32211
  },
32221
32212
  async ({ systemId, noteIds }) => {
32222
32213
  try {
32223
- await assertProjectInOrg(client, systemId, ctx.orgId);
32214
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32224
32215
  const task = await client.validateAssumptions(systemId, noteIds);
32225
32216
  return {
32226
32217
  content: [{
@@ -32255,7 +32246,7 @@ ${JSON.stringify(task.result, null, 2)}
32255
32246
  },
32256
32247
  async ({ systemId, message, revisionId }) => {
32257
32248
  try {
32258
- await assertProjectInOrg(client, systemId, ctx.orgId);
32249
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32259
32250
  const task = await client.startSession(
32260
32251
  systemId,
32261
32252
  message,
@@ -32292,7 +32283,7 @@ ${JSON.stringify(task.result, null, 2)}
32292
32283
  },
32293
32284
  async ({ systemId, revisionIds }) => {
32294
32285
  try {
32295
- await assertProjectInOrg(client, systemId, ctx.orgId);
32286
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32296
32287
  const task = await client.planRevisionBatch(systemId, revisionIds);
32297
32288
  return {
32298
32289
  content: [{
@@ -32324,7 +32315,7 @@ ${JSON.stringify(task.result, null, 2)}
32324
32315
  },
32325
32316
  async ({ systemId }) => {
32326
32317
  try {
32327
- await assertProjectInOrg(client, systemId, ctx.orgId);
32318
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32328
32319
  const task = await client.runTriage(systemId);
32329
32320
  return {
32330
32321
  content: [{
@@ -32370,7 +32361,7 @@ ${JSON.stringify(task.result, null, 2)}
32370
32361
  },
32371
32362
  async ({ systemId, content, filename, encoding }) => {
32372
32363
  try {
32373
- await assertProjectInOrg(client, systemId, ctx.orgId);
32364
+ await assertSystemInOrg(client, systemId, ctx.orgId);
32374
32365
  const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
32375
32366
  return {
32376
32367
  content: [{
@@ -32447,7 +32438,7 @@ var TOOL_PROFILES = Object.freeze({
32447
32438
  registerTeamspaceTools,
32448
32439
  registerMembershipTools,
32449
32440
  registerSystemTools,
32450
- registerProjectLifecycleTools,
32441
+ registerSystemLifecycleTools,
32451
32442
  allow(registerBeatVersionTools, ["relocate_beat_version"])
32452
32443
  ]),
32453
32444
  "onboarding": Object.freeze([
@@ -32485,7 +32476,7 @@ function registerAllTools(server, ctx, client, profile) {
32485
32476
  return;
32486
32477
  }
32487
32478
  registerSystemTools(server, ctx, client);
32488
- registerProjectLifecycleTools(server, ctx, client);
32479
+ registerSystemLifecycleTools(server, ctx, client);
32489
32480
  registerMembershipTools(server, ctx, client);
32490
32481
  registerBeatTools(server, ctx, client);
32491
32482
  registerBeatConvergenceTools(server, ctx, client);
@@ -32808,7 +32799,7 @@ function createHttpClient(config2) {
32808
32799
  return { error: String(err) };
32809
32800
  }
32810
32801
  },
32811
- transferProject: async (sourceOrgId, projectId, targetOrgId) => {
32802
+ transferSystem: async (sourceOrgId, projectId, targetOrgId) => {
32812
32803
  try {
32813
32804
  const result = await request(
32814
32805
  "POST",
@@ -33936,7 +33927,7 @@ function createHttpClient(config2) {
33936
33927
  return importResult;
33937
33928
  },
33938
33929
  // Embedding similarity
33939
- embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
33930
+ embedSystemEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
33940
33931
  findSimilarNotes: async (noteId, projectId, options) => {
33941
33932
  const qs = buildSimilarityQS(options);
33942
33933
  const res = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes/${encodeURIComponent(noteId)}/similar${qs}`);
@@ -34180,7 +34171,7 @@ function createHttpClient(config2) {
34180
34171
  },
34181
34172
  // WorkItems (WorkItem exposure layer — owned, statused unit of operational work under a Track)
34182
34173
  createWorkItem: async (input) => {
34183
- const { trackId, title, owner, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId } = input;
34174
+ const { trackId, title, owner, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId, workOrderId } = input;
34184
34175
  const body = {
34185
34176
  title,
34186
34177
  ...owner !== void 0 && { owner },
@@ -34189,7 +34180,8 @@ function createHttpClient(config2) {
34189
34180
  ...valueUnits !== void 0 && { valueUnits },
34190
34181
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
34191
34182
  ...createdBy !== void 0 && { createdBy },
34192
- ...beatVersionId !== void 0 && { beatVersionId }
34183
+ ...beatVersionId !== void 0 && { beatVersionId },
34184
+ ...workOrderId !== void 0 && { workOrderId }
34193
34185
  };
34194
34186
  const res = await request(
34195
34187
  "POST",
@@ -34930,7 +34922,7 @@ function loadConfig() {
34930
34922
  };
34931
34923
  }
34932
34924
  async function main() {
34933
- console.error(`[harmonica-mcp] v${"3.9.0"} starting\u2026`);
34925
+ console.error(`[harmonica-mcp] v${"3.9.2"} starting\u2026`);
34934
34926
  const config2 = loadConfig();
34935
34927
  const client = createHttpClient({
34936
34928
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "3.9.0",
3
+ "version": "3.9.2",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {