@codazen/harmonica-mcp 3.8.6 → 3.9.1

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 +874 -279
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21542,25 +21542,25 @@ 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) {
21548
- throw new Error(`Project not found: "${systemId}"`);
21545
+ async function assertSystemInOrg(client, systemId, orgId) {
21546
+ const system = await client.getSystem(systemId);
21547
+ if (!system) {
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
- `Project "${systemId}" does not belong to the configured organization. Access denied.`
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}"`);
21560
21560
  }
21561
21561
  if (revision.systemId !== systemId) {
21562
21562
  throw new Error(
21563
- `Revision "${revisionId}" does not belong to project "${systemId}". Access denied.`
21563
+ `Revision "${revisionId}" does not belong to system "${systemId}". Access denied.`
21564
21564
  );
21565
21565
  }
21566
21566
  }
@@ -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(`Project not found: "${systemId}"`);
21582
- }
21583
- if (project.orgId !== orgId) {
21584
- throw new Error(
21585
- `Project "${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.`,
@@ -22853,7 +22841,7 @@ function formatNoteList(notes) {
22853
22841
  return parts.join("\n");
22854
22842
  }
22855
22843
  function formatDocumentList(docs, allNotes) {
22856
- if (docs.length === 0) return "No documents uploaded for this project.";
22844
+ if (docs.length === 0) return "No documents uploaded for this system.";
22857
22845
  const lines = docs.map((doc) => {
22858
22846
  const meta = doc.documentMeta;
22859
22847
  const status = meta?.documentStatus ?? "unknown";
@@ -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) {
@@ -23927,7 +23915,7 @@ function registerCheckTools(server, ctx, client) {
23927
23915
  } else {
23928
23916
  const result = await client.listSystemChecks(systemId, checkType, { limit: 200 });
23929
23917
  checks = result.checks;
23930
- scope = `project ${systemId}`;
23918
+ scope = `system ${systemId}`;
23931
23919
  }
23932
23920
  if (checks.length === 0) {
23933
23921
  return { content: [{ type: "text", text: `No checks found for ${scope}.` }] };
@@ -23950,9 +23938,9 @@ function registerCheckTools(server, ctx, client) {
23950
23938
  try {
23951
23939
  const checks = await client.listLatestSystemChecks(systemId);
23952
23940
  if (checks.length === 0) {
23953
- return { content: [{ type: "text", text: `No checks found for project ${systemId}.` }] };
23941
+ return { content: [{ type: "text", text: `No checks found for system ${systemId}.` }] };
23954
23942
  }
23955
- const text = formatCheckList(checks, `latest per target \xB7 project ${systemId}`);
23943
+ const text = formatCheckList(checks, `latest per target \xB7 system ${systemId}`);
23956
23944
  return { content: [{ type: "text", text }] };
23957
23945
  } catch (err) {
23958
23946
  const message = err instanceof Error ? err.message : String(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 };
@@ -24690,7 +24678,7 @@ function registerDropTools(server, ctx, client) {
24690
24678
  );
24691
24679
  server.tool(
24692
24680
  "create_drop",
24693
- "Create a Drop (release container) in an Account. Drops group Beat Revisions for coordinated release. The dropCode is auto-generated as D-NNN per Account. Drops are cross-project: a single Drop can include Revisions from any Project in the Account.",
24681
+ "Create a Drop (release container) in an Account. Drops group Beat Revisions for coordinated release. The dropCode is auto-generated as D-NNN per Account. Drops are cross-system: a single Drop can include Revisions from any System in the Account.",
24694
24682
  {
24695
24683
  accountId: external_exports.string().describe("The account ID"),
24696
24684
  name: external_exports.string().min(1).max(200).describe('Drop name (e.g. "MVP", "Phase 2", "Hotfix 2026-Q2")'),
@@ -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) {
@@ -26336,18 +26324,24 @@ var NoteStatusSchema = external_exports.enum([
26336
26324
  ]);
26337
26325
  var SystemStatusSchema = external_exports.enum(["active", "archived", "draft"]);
26338
26326
  var ChangeImportanceSchema = external_exports.enum(["minor", "moderate", "major"]);
26327
+ var normalizeSystemId = (raw) => {
26328
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
26329
+ const b = raw;
26330
+ const id = b["systemId"] || b["projectId"];
26331
+ if (!id) return raw;
26332
+ return { ...b, systemId: id };
26333
+ };
26339
26334
  var BeatApiSchema = external_exports.preprocess(
26340
26335
  (raw) => {
26341
26336
  if (typeof raw !== "object" || raw === null) return raw;
26342
26337
  const b = raw;
26343
- const id = b["systemId"] ?? b["projectId"];
26338
+ const id = b["systemId"] || b["projectId"];
26344
26339
  if (!id) return raw;
26345
- return { ...b, systemId: id, projectId: id };
26340
+ return { ...b, systemId: id };
26346
26341
  },
26347
26342
  external_exports.object({
26348
26343
  beatId: external_exports.string(),
26349
26344
  systemId: external_exports.string(),
26350
- projectId: external_exports.string(),
26351
26345
  /** Source project's title; attached by the flat /api/beats endpoint for
26352
26346
  * multi-project (teamspace/org) responses. */
26353
26347
  projectName: external_exports.string().nullish(),
@@ -26373,35 +26367,38 @@ var RevisionScmSchema = external_exports.object({
26373
26367
  state: external_exports.enum(["draft", "open", "merged", "closed"]),
26374
26368
  mergeSha: external_exports.string().optional()
26375
26369
  });
26376
- var RevisionApiSchema = external_exports.object({
26377
- revisionId: external_exports.string(),
26378
- beatId: external_exports.string(),
26379
- projectId: external_exports.string(),
26380
- status: RevisionStatusSchema,
26381
- title: external_exports.string().default(""),
26382
- versionNumber: external_exports.number(),
26383
- changeSummary: external_exports.string().nullish(),
26384
- changeImportance: ChangeImportanceSchema.nullish(),
26385
- primaryUser: external_exports.string(),
26386
- description: external_exports.string().nullish(),
26387
- tags: external_exports.array(external_exports.string()).nullish(),
26388
- priority: external_exports.number().nullish(),
26389
- estimatedEffort: external_exports.string().nullish(),
26390
- humanAssignee: HumanAssigneeSchema.nullish(),
26391
- agentAssignee: AgentAssigneeSchema.nullish(),
26392
- scm: RevisionScmSchema.optional(),
26393
- // The Layer this Revision was created under (immutable). Absent on Revisions
26394
- // predating Layers — never backfilled — so consumers group those as "Unassigned".
26395
- layerId: external_exports.string().nullish(),
26396
- createdAt: external_exports.string(),
26397
- updatedAt: external_exports.string()
26398
- }).passthrough();
26399
- var NoteApiSchema = external_exports.object({
26370
+ var RevisionApiSchema = external_exports.preprocess(
26371
+ normalizeSystemId,
26372
+ external_exports.object({
26373
+ revisionId: external_exports.string(),
26374
+ beatId: external_exports.string(),
26375
+ systemId: external_exports.string(),
26376
+ status: RevisionStatusSchema,
26377
+ title: external_exports.string().default(""),
26378
+ versionNumber: external_exports.number(),
26379
+ changeSummary: external_exports.string().nullish(),
26380
+ changeImportance: ChangeImportanceSchema.nullish(),
26381
+ primaryUser: external_exports.string(),
26382
+ description: external_exports.string().nullish(),
26383
+ tags: external_exports.array(external_exports.string()).nullish(),
26384
+ priority: external_exports.number().nullish(),
26385
+ estimatedEffort: external_exports.string().nullish(),
26386
+ humanAssignee: HumanAssigneeSchema.nullish(),
26387
+ agentAssignee: AgentAssigneeSchema.nullish(),
26388
+ scm: RevisionScmSchema.optional(),
26389
+ // The Layer this Revision was created under (immutable). Absent on Revisions
26390
+ // predating Layers — never backfilled — so consumers group those as "Unassigned".
26391
+ layerId: external_exports.string().nullish(),
26392
+ createdAt: external_exports.string(),
26393
+ updatedAt: external_exports.string()
26394
+ }).passthrough()
26395
+ );
26396
+ var NoteApiSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26400
26397
  noteId: external_exports.string(),
26401
26398
  noteType: NoteTypeSchema,
26402
26399
  status: NoteStatusSchema,
26403
26400
  content: external_exports.string(),
26404
- projectId: external_exports.string().nullish(),
26401
+ systemId: external_exports.string().nullish(),
26405
26402
  beatId: external_exports.string().nullish(),
26406
26403
  revisionId: external_exports.string().nullish(),
26407
26404
  rationale: external_exports.string().nullish(),
@@ -26412,18 +26409,21 @@ var NoteApiSchema = external_exports.object({
26412
26409
  agentAssignee: AgentAssigneeSchema.nullish(),
26413
26410
  createdAt: external_exports.string(),
26414
26411
  updatedAt: external_exports.string()
26415
- }).passthrough();
26416
- var SystemApiSchema = external_exports.object({
26417
- projectId: external_exports.string(),
26418
- title: external_exports.string(),
26419
- status: SystemStatusSchema,
26420
- description: external_exports.string().optional(),
26421
- strategy: external_exports.string().optional(),
26422
- orgId: external_exports.string().optional(),
26423
- ownerUserId: external_exports.string().optional(),
26424
- createdAt: external_exports.string(),
26425
- updatedAt: external_exports.string()
26426
- }).passthrough();
26412
+ }).passthrough());
26413
+ var SystemApiSchema = external_exports.preprocess(
26414
+ normalizeSystemId,
26415
+ external_exports.object({
26416
+ systemId: external_exports.string(),
26417
+ title: external_exports.string(),
26418
+ status: SystemStatusSchema,
26419
+ description: external_exports.string().optional(),
26420
+ strategy: external_exports.string().optional(),
26421
+ orgId: external_exports.string().optional(),
26422
+ ownerUserId: external_exports.string().optional(),
26423
+ createdAt: external_exports.string(),
26424
+ updatedAt: external_exports.string()
26425
+ }).passthrough()
26426
+ );
26427
26427
  var BeatsResponseSchema = external_exports.object({
26428
26428
  beats: external_exports.array(BeatApiSchema),
26429
26429
  total: external_exports.number().optional(),
@@ -26608,37 +26608,43 @@ var CHECK_TARGET_TYPES = [
26608
26608
  "organization"
26609
26609
  ];
26610
26610
  var CheckTargetTypeSchema = external_exports.enum(CHECK_TARGET_TYPES);
26611
- var CheckApiSchema = external_exports.object({
26612
- checkId: external_exports.string(),
26613
- checkType: CheckTypeSchema,
26614
- targetType: CheckTargetTypeSchema,
26615
- targetId: external_exports.string(),
26616
- projectId: external_exports.string(),
26617
- overallScore: external_exports.number(),
26618
- dimensions: external_exports.array(CheckDimensionScoreSchema),
26619
- summary: external_exports.string(),
26620
- topSuggestion: external_exports.string().optional(),
26621
- gaps: external_exports.array(CheckGapSchema).optional(),
26622
- taskId: external_exports.string().optional(),
26623
- triggeredBy: external_exports.string().optional(),
26624
- createdAt: external_exports.string(),
26625
- updatedAt: external_exports.string()
26626
- }).passthrough();
26627
- var SystemCheckApiSchema = external_exports.object({
26628
- checkId: external_exports.string(),
26629
- checkType: CheckTypeSchema,
26630
- targetType: CheckTargetTypeSchema,
26631
- targetId: external_exports.string(),
26632
- projectId: external_exports.string(),
26633
- overallScore: external_exports.number(),
26634
- summary: external_exports.string(),
26635
- triggeredBy: external_exports.string().optional(),
26636
- createdAt: external_exports.string(),
26637
- updatedAt: external_exports.string(),
26638
- /** Populated only when the API was called with ?includeDimensions=true.
26639
- * Read-time enrichment from the underlying Check entity. */
26640
- dimensions: external_exports.array(CheckDimensionScoreSchema).optional()
26641
- }).passthrough();
26611
+ var CheckApiSchema = external_exports.preprocess(
26612
+ normalizeSystemId,
26613
+ external_exports.object({
26614
+ checkId: external_exports.string(),
26615
+ checkType: CheckTypeSchema,
26616
+ targetType: CheckTargetTypeSchema,
26617
+ targetId: external_exports.string(),
26618
+ systemId: external_exports.string(),
26619
+ overallScore: external_exports.number(),
26620
+ dimensions: external_exports.array(CheckDimensionScoreSchema),
26621
+ summary: external_exports.string(),
26622
+ topSuggestion: external_exports.string().optional(),
26623
+ gaps: external_exports.array(CheckGapSchema).optional(),
26624
+ taskId: external_exports.string().optional(),
26625
+ triggeredBy: external_exports.string().optional(),
26626
+ createdAt: external_exports.string(),
26627
+ updatedAt: external_exports.string()
26628
+ }).passthrough()
26629
+ );
26630
+ var SystemCheckApiSchema = external_exports.preprocess(
26631
+ normalizeSystemId,
26632
+ external_exports.object({
26633
+ checkId: external_exports.string(),
26634
+ checkType: CheckTypeSchema,
26635
+ targetType: CheckTargetTypeSchema,
26636
+ targetId: external_exports.string(),
26637
+ systemId: external_exports.string(),
26638
+ overallScore: external_exports.number(),
26639
+ summary: external_exports.string(),
26640
+ triggeredBy: external_exports.string().optional(),
26641
+ createdAt: external_exports.string(),
26642
+ updatedAt: external_exports.string(),
26643
+ /** Populated only when the API was called with ?includeDimensions=true.
26644
+ * Read-time enrichment from the underlying Check entity. */
26645
+ dimensions: external_exports.array(CheckDimensionScoreSchema).optional()
26646
+ }).passthrough()
26647
+ );
26642
26648
  var CheckResponseSchema = external_exports.object({ check: CheckApiSchema }).passthrough();
26643
26649
  var ChecksResponseSchema = external_exports.object({ checks: external_exports.array(SystemCheckApiSchema), nextCursor: external_exports.string().optional() }).passthrough();
26644
26650
  var TaskStatusSchema2 = external_exports.enum(["pending", "running", "completed", "failed"]);
@@ -26683,21 +26689,24 @@ var OrgMemberSchema = external_exports.object({
26683
26689
  var OrganizationsResponseSchema = external_exports.array(OrganizationSummarySchema);
26684
26690
  var OrgMembersResponseSchema = external_exports.array(OrgMemberSchema);
26685
26691
  var WrappedOrgMembersResponseSchema = external_exports.object({ members: OrgMembersResponseSchema });
26686
- var ActivitySchema = external_exports.object({
26687
- activityId: external_exports.string(),
26688
- projectId: external_exports.string(),
26689
- action: external_exports.string(),
26690
- importance: external_exports.enum(["minor", "moderate", "major"]),
26691
- reason: external_exports.string(),
26692
- // actorType is typed as string (not a strict enum) because the API accepts values
26693
- // beyond 'user'/'system' (e.g. 'human', 'agent') depending on the call site.
26694
- actorType: external_exports.string().optional(),
26695
- actorId: external_exports.string().optional(),
26696
- affectedBeatIds: external_exports.array(external_exports.string()).optional(),
26697
- affectedBeatTitles: external_exports.array(external_exports.string()).optional(),
26698
- metadata: external_exports.record(external_exports.unknown()).optional(),
26699
- createdAt: external_exports.string()
26700
- }).passthrough();
26692
+ var ActivitySchema = external_exports.preprocess(
26693
+ normalizeSystemId,
26694
+ external_exports.object({
26695
+ activityId: external_exports.string(),
26696
+ systemId: external_exports.string(),
26697
+ action: external_exports.string(),
26698
+ importance: external_exports.enum(["minor", "moderate", "major"]),
26699
+ reason: external_exports.string(),
26700
+ // actorType is typed as string (not a strict enum) because the API accepts values
26701
+ // beyond 'user'/'system' (e.g. 'human', 'agent') depending on the call site.
26702
+ actorType: external_exports.string().optional(),
26703
+ actorId: external_exports.string().optional(),
26704
+ affectedBeatIds: external_exports.array(external_exports.string()).optional(),
26705
+ affectedBeatTitles: external_exports.array(external_exports.string()).optional(),
26706
+ metadata: external_exports.record(external_exports.unknown()).optional(),
26707
+ createdAt: external_exports.string()
26708
+ }).passthrough()
26709
+ );
26701
26710
  var ActivitiesResponseSchema = external_exports.object({
26702
26711
  activities: external_exports.array(ActivitySchema),
26703
26712
  hasMore: external_exports.boolean(),
@@ -26725,26 +26734,29 @@ var NotificationsResponseSchema = external_exports.object({
26725
26734
  nextCursor: external_exports.string().optional()
26726
26735
  }).passthrough();
26727
26736
  var UnreadCountResponseSchema = external_exports.object({ count: external_exports.number() }).passthrough();
26728
- var TriageActionSchema = external_exports.object({
26729
- actionKey: external_exports.string(),
26730
- projectId: external_exports.string(),
26731
- entityId: external_exports.string().optional(),
26732
- entityType: external_exports.enum(["beat", "revision", "note"]).optional(),
26733
- beatId: external_exports.string().optional(),
26734
- category: external_exports.string(),
26735
- priority: external_exports.string(),
26736
- beatStatus: external_exports.string().optional(),
26737
- title: external_exports.string(),
26738
- description: external_exports.string(),
26739
- status: external_exports.enum(["open", "acted", "dismissed", "resolved"]),
26740
- decision: external_exports.enum(["auto-execute", "escalate", "skip"]).optional(),
26741
- taskId: external_exports.string().optional(),
26742
- taskStatus: external_exports.string().optional(),
26743
- firstSeenAt: external_exports.string(),
26744
- lastSeenAt: external_exports.string(),
26745
- dismissedBy: external_exports.string().optional(),
26746
- dismissedAt: external_exports.string().optional()
26747
- }).passthrough();
26737
+ var TriageActionSchema = external_exports.preprocess(
26738
+ normalizeSystemId,
26739
+ external_exports.object({
26740
+ actionKey: external_exports.string(),
26741
+ systemId: external_exports.string(),
26742
+ entityId: external_exports.string().optional(),
26743
+ entityType: external_exports.enum(["beat", "revision", "note"]).optional(),
26744
+ beatId: external_exports.string().optional(),
26745
+ category: external_exports.string(),
26746
+ priority: external_exports.string(),
26747
+ beatStatus: external_exports.string().optional(),
26748
+ title: external_exports.string(),
26749
+ description: external_exports.string(),
26750
+ status: external_exports.enum(["open", "acted", "dismissed", "resolved"]),
26751
+ decision: external_exports.enum(["auto-execute", "escalate", "skip"]).optional(),
26752
+ taskId: external_exports.string().optional(),
26753
+ taskStatus: external_exports.string().optional(),
26754
+ firstSeenAt: external_exports.string(),
26755
+ lastSeenAt: external_exports.string(),
26756
+ dismissedBy: external_exports.string().optional(),
26757
+ dismissedAt: external_exports.string().optional()
26758
+ }).passthrough()
26759
+ );
26748
26760
  var TriageActionsResponseSchema = external_exports.object({
26749
26761
  actions: external_exports.array(TriageActionSchema)
26750
26762
  }).passthrough();
@@ -26761,15 +26773,15 @@ var NextActionSchema = external_exports.object({
26761
26773
  var NextActionsResponseSchema = external_exports.object({
26762
26774
  actions: external_exports.array(NextActionSchema)
26763
26775
  }).passthrough();
26764
- var SearchResultSchema = external_exports.object({
26776
+ var SearchResultSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26765
26777
  entityId: external_exports.string(),
26766
- entityType: external_exports.enum(["beat", "note", "revision", "beatVersion", "drop", "project"]),
26778
+ entityType: external_exports.enum(["beat", "note", "revision", "beatVersion", "drop", "project", "system"]),
26767
26779
  score: external_exports.number(),
26768
26780
  snippet: external_exports.string(),
26769
- projectId: external_exports.string().optional(),
26781
+ systemId: external_exports.string().optional(),
26770
26782
  projectName: external_exports.string().optional(),
26771
26783
  teamspaceId: external_exports.string().optional()
26772
- }).passthrough();
26784
+ }).passthrough());
26773
26785
  var SearchScopeSchema = external_exports.object({
26774
26786
  kind: external_exports.enum(["project", "teamspace", "org"]),
26775
26787
  id: external_exports.string()
@@ -26809,31 +26821,40 @@ var LandscapeResponseSchema = external_exports.object({
26809
26821
  points: external_exports.array(LandscapePointSchema),
26810
26822
  links: external_exports.array(external_exports.object({ from: external_exports.string(), to: external_exports.string() }).passthrough()),
26811
26823
  clusters: external_exports.array(LandscapeClusterSchema),
26812
- scopeBoundary: external_exports.object({
26813
- projectId: external_exports.string(),
26814
- cx: external_exports.number(),
26815
- cy: external_exports.number(),
26816
- radius: external_exports.number(),
26817
- label: external_exports.string()
26818
- }).passthrough().optional(),
26824
+ scopeBoundary: external_exports.preprocess(
26825
+ (raw) => {
26826
+ if (typeof raw !== "object" || raw === null) return raw;
26827
+ const o = raw;
26828
+ const id = o["systemId"] ?? o["projectId"];
26829
+ if (!id) return raw;
26830
+ return { ...o, systemId: id };
26831
+ },
26832
+ external_exports.object({
26833
+ systemId: external_exports.string(),
26834
+ cx: external_exports.number(),
26835
+ cy: external_exports.number(),
26836
+ radius: external_exports.number(),
26837
+ label: external_exports.string()
26838
+ }).passthrough()
26839
+ ).optional(),
26819
26840
  meta: LandscapeMetaSchema
26820
26841
  }).passthrough();
26821
26842
  var ClusterDescriptionSchema = external_exports.object({
26822
26843
  title: external_exports.string(),
26823
26844
  description: external_exports.string()
26824
26845
  }).passthrough();
26825
- var BeatVersionItemSchema = external_exports.object({
26846
+ var BeatVersionItemSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
26826
26847
  beatVersionId: external_exports.string(),
26827
26848
  beatId: external_exports.string(),
26828
- projectId: external_exports.string().optional(),
26849
+ systemId: external_exports.string().optional(),
26829
26850
  status: external_exports.string()
26830
- }).passthrough();
26851
+ }).passthrough());
26831
26852
  var BeatVersionsResponseSchema = external_exports.object({
26832
26853
  beatVersions: external_exports.array(BeatVersionItemSchema)
26833
26854
  }).passthrough();
26834
26855
  var ConversationSummaryBaseSchema = external_exports.object({
26835
26856
  conversationId: external_exports.string(),
26836
- projectId: external_exports.string().optional(),
26857
+ systemId: external_exports.string().optional(),
26837
26858
  teamspaceId: external_exports.string().optional(),
26838
26859
  userId: external_exports.string(),
26839
26860
  conversationType: external_exports.string(),
@@ -26846,7 +26867,7 @@ var ConversationSummaryBaseSchema = external_exports.object({
26846
26867
  createdAt: external_exports.string(),
26847
26868
  updatedAt: external_exports.string()
26848
26869
  });
26849
- var ConversationSummarySchema = ConversationSummaryBaseSchema.passthrough();
26870
+ var ConversationSummarySchema = external_exports.preprocess(normalizeSystemId, ConversationSummaryBaseSchema.passthrough());
26850
26871
  var StoredMessageSchema = external_exports.object({
26851
26872
  messageId: external_exports.string(),
26852
26873
  conversationId: external_exports.string(),
@@ -26952,6 +26973,13 @@ var WorkItemApiSchema = external_exports.object({
26952
26973
  costPerValueUnit: external_exports.number().optional(),
26953
26974
  /** Optional capability-lineage link to a Beat Version (v2, N-4E09-6574). */
26954
26975
  beatVersionId: external_exports.string().optional(),
26976
+ /**
26977
+ * Optional link to the Work Order this item delivers part of (B-331,
26978
+ * N-4E09-7160). Absent means unattached, which is a supported state — a
26979
+ * reader grouping a Track's work by Order must render those as their own
26980
+ * named group, not hide them.
26981
+ */
26982
+ workOrderId: external_exports.string().min(1).optional(),
26955
26983
  createdBy: external_exports.string().optional(),
26956
26984
  version: external_exports.number().optional(),
26957
26985
  createdAt: external_exports.string(),
@@ -26963,6 +26991,93 @@ var WorkItemEstimateHintApiSchema = external_exports.object({
26963
26991
  });
26964
26992
  var WorkItemsResponseSchema = external_exports.object({ workItems: external_exports.array(WorkItemApiSchema) }).passthrough();
26965
26993
  var WorkItemResponseSchema = external_exports.object({ workItem: WorkItemApiSchema, estimateHint: WorkItemEstimateHintApiSchema.optional() }).passthrough();
26994
+ var WORK_INTAKE_STATUSES = [
26995
+ "requested",
26996
+ "needs_decision",
26997
+ "accepted",
26998
+ "done"
26999
+ ];
27000
+ var WorkIntakeStatusApiSchema = external_exports.enum(WORK_INTAKE_STATUSES);
27001
+ var NEEDS_DECISION_REASONS = [
27002
+ "out_of_scope",
27003
+ "exceeds_capacity",
27004
+ "incoherent_with_decision"
27005
+ ];
27006
+ var NeedsDecisionReasonApiSchema = external_exports.enum(NEEDS_DECISION_REASONS);
27007
+ var WORK_ORDER_SIZES = [
27008
+ "XS",
27009
+ "S",
27010
+ "M",
27011
+ "L",
27012
+ "XL"
27013
+ ];
27014
+ var WorkOrderSizeApiSchema = external_exports.enum(WORK_ORDER_SIZES);
27015
+ var WORK_REQUEST_ORIGINS = [
27016
+ "client",
27017
+ "internal"
27018
+ ];
27019
+ var WorkRequestOriginApiSchema = external_exports.enum(WORK_REQUEST_ORIGINS);
27020
+ var WorkRequestApiSchema = external_exports.object({
27021
+ workRequestId: external_exports.string(),
27022
+ movementId: external_exports.string(),
27023
+ teamspaceId: external_exports.string(),
27024
+ orgId: external_exports.string(),
27025
+ title: external_exports.string(),
27026
+ /** The ask as written, in the requester's own language. */
27027
+ body: external_exports.string().optional(),
27028
+ requester: HumanAssigneeApiSchema,
27029
+ /** Where the ask entered — one entity covers both, only the entry point differs. */
27030
+ origin: WorkRequestOriginApiSchema,
27031
+ createdBy: external_exports.string().optional(),
27032
+ version: external_exports.number().optional(),
27033
+ createdAt: external_exports.string(),
27034
+ updatedAt: external_exports.string()
27035
+ }).passthrough();
27036
+ var WorkOrderApiSchema = external_exports.object({
27037
+ workOrderId: external_exports.string(),
27038
+ /** Immutable lineage back to the ask this Order was split from. */
27039
+ workRequestId: external_exports.string(),
27040
+ /** Mutable — an Order moves Tracks when an early split guess turns out wrong. */
27041
+ trackId: external_exports.string(),
27042
+ teamspaceId: external_exports.string(),
27043
+ orgId: external_exports.string(),
27044
+ title: external_exports.string(),
27045
+ description: external_exports.string().optional(),
27046
+ status: WorkIntakeStatusApiSchema,
27047
+ /** Present if and only if status is `needs_decision` — the reason IS the routing. */
27048
+ needsDecisionReason: NeedsDecisionReasonApiSchema.optional(),
27049
+ /** Absent when the Order arrived before it was sized, which the composition counts. */
27050
+ size: WorkOrderSizeApiSchema.optional(),
27051
+ createdBy: external_exports.string().optional(),
27052
+ version: external_exports.number().optional(),
27053
+ createdAt: external_exports.string(),
27054
+ updatedAt: external_exports.string()
27055
+ }).passthrough();
27056
+ var WorkRequestSizeEntryApiSchema = external_exports.object({
27057
+ size: WorkOrderSizeApiSchema,
27058
+ count: external_exports.number()
27059
+ });
27060
+ var WorkRequestSizeCompositionApiSchema = external_exports.object({
27061
+ /** Largest first, so a display reads "a large and a medium". */
27062
+ entries: external_exports.array(WorkRequestSizeEntryApiSchema),
27063
+ /** Orders carrying no usable size — counted, never silently dropped. */
27064
+ unsized: external_exports.number(),
27065
+ /** Sized orders only. */
27066
+ totalValueUnits: external_exports.number()
27067
+ });
27068
+ var WorkRequestOrderApiSchema = WorkOrderApiSchema.extend({
27069
+ /** Absent — not null — when the Track no longer resolves. */
27070
+ trackTitle: external_exports.string().optional()
27071
+ }).passthrough();
27072
+ var WorkRequestsResponseSchema = external_exports.object({ workRequests: external_exports.array(WorkRequestApiSchema) }).passthrough();
27073
+ var WorkOrdersResponseSchema = external_exports.object({ workOrders: external_exports.array(WorkOrderApiSchema) }).passthrough();
27074
+ var WorkRequestResponseSchema = external_exports.object({ workRequest: WorkRequestApiSchema }).passthrough();
27075
+ var WorkOrderResponseSchema = external_exports.object({ workOrder: WorkOrderApiSchema }).passthrough();
27076
+ var WorkRequestDetailResponseSchema = external_exports.object({
27077
+ workRequest: WorkRequestApiSchema,
27078
+ orders: external_exports.array(WorkRequestOrderApiSchema),
27079
+ size: WorkRequestSizeCompositionApiSchema
27080
+ }).passthrough();
26966
27081
  var AccountStatusSchema = external_exports.enum(["active", "inactive", "archived"]);
26967
27082
  var AccountApiSchema = external_exports.object({
26968
27083
  accountId: external_exports.string(),
@@ -26999,14 +27114,13 @@ var LayerApiSchema = external_exports.preprocess(
26999
27114
  (raw) => {
27000
27115
  if (typeof raw !== "object" || raw === null) return raw;
27001
27116
  const l = raw;
27002
- const id = l["systemId"] ?? l["projectId"];
27117
+ const id = l["systemId"] || l["projectId"];
27003
27118
  if (!id) return raw;
27004
- return { ...l, systemId: id, projectId: id };
27119
+ return { ...l, systemId: id };
27005
27120
  },
27006
27121
  external_exports.object({
27007
27122
  layerId: external_exports.string(),
27008
27123
  systemId: external_exports.string(),
27009
- projectId: external_exports.string(),
27010
27124
  layerKey: external_exports.string(),
27011
27125
  name: external_exports.string(),
27012
27126
  description: external_exports.string().nullish(),
@@ -27058,11 +27172,11 @@ var DELIVERABLE_GROUP_STATUSES = [
27058
27172
  "cancelled"
27059
27173
  ];
27060
27174
  var DeliverableGroupStatusSchema = external_exports.enum(DELIVERABLE_GROUP_STATUSES);
27061
- var DeliverableGroupApiSchema = external_exports.object({
27175
+ var DeliverableGroupApiSchema = external_exports.preprocess(normalizeSystemId, external_exports.object({
27062
27176
  deliverableGroupId: external_exports.string(),
27063
27177
  groupCode: external_exports.string(),
27064
27178
  teamspaceId: external_exports.string(),
27065
- projectId: external_exports.string().nullish(),
27179
+ systemId: external_exports.string().nullish(),
27066
27180
  movementId: external_exports.string().nullish(),
27067
27181
  orgId: external_exports.string(),
27068
27182
  title: external_exports.string(),
@@ -27079,7 +27193,7 @@ var DeliverableGroupApiSchema = external_exports.object({
27079
27193
  version: external_exports.number().nullish(),
27080
27194
  createdAt: external_exports.string(),
27081
27195
  updatedAt: external_exports.string()
27082
- }).passthrough();
27196
+ }).passthrough());
27083
27197
  var DeliverableGroupsResponseSchema = external_exports.object({
27084
27198
  deliverableGroups: external_exports.array(DeliverableGroupApiSchema)
27085
27199
  }).passthrough();
@@ -27096,27 +27210,33 @@ var MovementDropSummaryApiSchema = external_exports.object({
27096
27210
  var MovementDropsResponseSchema = external_exports.object({
27097
27211
  drops: external_exports.array(MovementDropSummaryApiSchema)
27098
27212
  }).passthrough();
27099
- var WorkPlanBeatVersionApiSchema = external_exports.object({
27100
- beatVersionId: external_exports.string(),
27101
- beatId: external_exports.string(),
27102
- projectId: external_exports.string(),
27103
- versionNumber: external_exports.number(),
27104
- title: external_exports.string(),
27105
- status: BeatVersionStatusSchema,
27106
- // Derived Value Units (VU = Δf × W), read-only. 0 when the BV has no confirmed
27107
- // value-velocity inputs. Computed server-side from the parent Beat's weight.
27108
- // `.default(0)` tolerates a rolling deploy where an older API omits the field —
27109
- // validatedApiFetch would otherwise throw and drop the whole hook into error.
27110
- valueUnits: external_exports.number().default(0),
27111
- createdAt: external_exports.string(),
27112
- updatedAt: external_exports.string()
27113
- }).passthrough();
27213
+ var WorkPlanBeatVersionApiSchema = external_exports.preprocess(
27214
+ normalizeSystemId,
27215
+ external_exports.object({
27216
+ beatVersionId: external_exports.string(),
27217
+ beatId: external_exports.string(),
27218
+ systemId: external_exports.string(),
27219
+ versionNumber: external_exports.number(),
27220
+ title: external_exports.string(),
27221
+ status: BeatVersionStatusSchema,
27222
+ // Derived Value Units (VU = Δf × W), read-only. 0 when the BV has no confirmed
27223
+ // value-velocity inputs. Computed server-side from the parent Beat's weight.
27224
+ // `.default(0)` tolerates a rolling deploy where an older API omits the field —
27225
+ // validatedApiFetch would otherwise throw and drop the whole hook into error.
27226
+ valueUnits: external_exports.number().default(0),
27227
+ createdAt: external_exports.string(),
27228
+ updatedAt: external_exports.string()
27229
+ }).passthrough()
27230
+ );
27114
27231
  var DeliverableGroupBeatVersionsResponseSchema = external_exports.object({
27115
27232
  beatVersions: external_exports.array(WorkPlanBeatVersionApiSchema)
27116
27233
  }).passthrough();
27117
27234
  var RevisionClaimActorSchema = external_exports.union([
27118
27235
  external_exports.object({ kind: external_exports.literal("user"), userId: external_exports.string() }),
27119
- external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), projectId: external_exports.string() })
27236
+ external_exports.preprocess(
27237
+ normalizeSystemId,
27238
+ external_exports.object({ kind: external_exports.literal("agent"), agentId: external_exports.string(), systemId: external_exports.string() })
27239
+ )
27120
27240
  ]);
27121
27241
  var RevisionClaimSchema = external_exports.object({
27122
27242
  claimId: external_exports.string(),
@@ -27127,33 +27247,39 @@ var RevisionClaimSchema = external_exports.object({
27127
27247
  var RevisionClaimsResponseSchema = external_exports.object({
27128
27248
  claims: external_exports.array(RevisionClaimSchema)
27129
27249
  }).passthrough();
27130
- var ProposedRevisionSchema = external_exports.object({
27131
- revisionId: external_exports.string(),
27132
- title: external_exports.string(),
27133
- state: external_exports.string(),
27134
- beatId: external_exports.string(),
27135
- beatTitle: external_exports.string().optional(),
27136
- projectId: external_exports.string(),
27137
- projectTitle: external_exports.string().optional()
27138
- }).passthrough();
27250
+ var ProposedRevisionSchema = external_exports.preprocess(
27251
+ normalizeSystemId,
27252
+ external_exports.object({
27253
+ revisionId: external_exports.string(),
27254
+ title: external_exports.string(),
27255
+ state: external_exports.string(),
27256
+ beatId: external_exports.string(),
27257
+ beatTitle: external_exports.string().optional(),
27258
+ systemId: external_exports.string(),
27259
+ projectTitle: external_exports.string().optional()
27260
+ }).passthrough()
27261
+ );
27139
27262
  var ReleaseIntervalSchema = external_exports.object({
27140
27263
  averageIntervalDays: external_exports.number(),
27141
27264
  sampleSize: external_exports.number(),
27142
27265
  isFallback: external_exports.boolean()
27143
27266
  }).passthrough();
27144
- var EligibleBeatVersionForDropSchema = external_exports.object({
27145
- beatVersionId: external_exports.string(),
27146
- title: external_exports.string(),
27147
- state: external_exports.string(),
27148
- beatId: external_exports.string(),
27149
- beatTitle: external_exports.string().optional(),
27150
- projectId: external_exports.string(),
27151
- projectTitle: external_exports.string().optional(),
27152
- createdAt: external_exports.string(),
27153
- updatedAt: external_exports.string(),
27154
- humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
27155
- currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string().optional(), state: external_exports.string() }).optional()
27156
- }).passthrough();
27267
+ var EligibleBeatVersionForDropSchema = external_exports.preprocess(
27268
+ normalizeSystemId,
27269
+ external_exports.object({
27270
+ beatVersionId: external_exports.string(),
27271
+ title: external_exports.string(),
27272
+ state: external_exports.string(),
27273
+ beatId: external_exports.string(),
27274
+ beatTitle: external_exports.string().optional(),
27275
+ systemId: external_exports.string(),
27276
+ projectTitle: external_exports.string().optional(),
27277
+ createdAt: external_exports.string(),
27278
+ updatedAt: external_exports.string(),
27279
+ humanAssignee: external_exports.object({ email: external_exports.string(), name: external_exports.string() }).optional(),
27280
+ currentDropAssignment: external_exports.object({ dropId: external_exports.string(), dropCode: external_exports.string().optional(), state: external_exports.string() }).optional()
27281
+ }).passthrough()
27282
+ );
27157
27283
  var ScoredDropCandidateSchema = external_exports.object({
27158
27284
  bv: EligibleBeatVersionForDropSchema,
27159
27285
  // Score fields are nullable — the ranking engine returns null when there is
@@ -27393,7 +27519,7 @@ ${formatDeliveryPolicy(
27393
27519
  function registerNoteTools(server, ctx, client) {
27394
27520
  server.tool(
27395
27521
  "list_notes",
27396
- "List Notes for a system. Without beatId: returns all notes (system-level + beat-level). With beatId: returns only notes scoped to that beat. Filterable by type, status, revisionId, beatVersionId, and significance. Use offset/limit to paginate beat-scoped results; use cursor for project-wide results.",
27522
+ "List Notes for a system. Without beatId: returns all notes (system-level + beat-level). With beatId: returns only notes scoped to that beat. Filterable by type, status, revisionId, beatVersionId, and significance. Use offset/limit to paginate beat-scoped results; use cursor for system-wide results.",
27397
27523
  {
27398
27524
  systemId: external_exports.string().describe("The system ID"),
27399
27525
  beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all system notes)"),
@@ -27405,7 +27531,7 @@ function registerNoteTools(server, ctx, client) {
27405
27531
  significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Filter decision Notes by significance tier (strategic | structural | implementation). Notes without a significance value are treated as implementation."),
27406
27532
  offset: external_exports.coerce.number().int().min(0).default(0).describe("Notes to skip for pagination (applies to beat-scoped and source-document-filtered results only)"),
27407
27533
  limit: external_exports.coerce.number().int().min(1).max(500).default(100).describe("Max notes to return (default 100, max 500)"),
27408
- cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response \u2014 use for project-wide note pagination (no beatId)"),
27534
+ cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response \u2014 use for system-wide note pagination (no beatId)"),
27409
27535
  orderBy: external_exports.enum(["newest", "oldest"]).optional().describe("Sort order \u2014 newest or oldest first. Applies to beat-scoped queries (beatId provided); omit to use DynamoDB scan order.")
27410
27536
  },
27411
27537
  async ({ systemId, beatId, noteType, status, revisionId, beatVersionId, sourceDocumentNoteId, significance, offset = 0, limit = 100, cursor, orderBy }) => {
@@ -27420,7 +27546,7 @@ function registerNoteTools(server, ctx, client) {
27420
27546
  return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
27421
27547
  }
27422
27548
  try {
27423
- await assertProjectInOrg(client, systemId, ctx.orgId);
27549
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27424
27550
  } catch (err) {
27425
27551
  const message = err instanceof Error ? err.message : String(err);
27426
27552
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -27463,7 +27589,7 @@ function registerNoteTools(server, ctx, client) {
27463
27589
  allNotesResult = await client.listAllSystemNotes(systemId, filters, SOURCE_DOC_FETCH_LIMIT);
27464
27590
  } catch (err) {
27465
27591
  const message = err instanceof Error ? err.message : String(err);
27466
- return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
27592
+ return { content: [{ type: "text", text: `Failed to list system notes: ${message}` }], isError: true };
27467
27593
  }
27468
27594
  const { notes: allNotes, hasMore: corpusTruncated } = allNotesResult;
27469
27595
  const filtered = allNotes.filter((n) => n.sourceSubmissionId === sourceDocumentNoteId);
@@ -27471,7 +27597,7 @@ function registerNoteTools(server, ctx, client) {
27471
27597
  page = filtered.slice(offset, offset + limit);
27472
27598
  hasMore = offset + limit < total;
27473
27599
  const truncationWarning = corpusTruncated ? `
27474
- > \u26A0\uFE0F Project has >5000 notes; sourceDocumentNoteId filter may be incomplete.
27600
+ > \u26A0\uFE0F System has >5000 notes; sourceDocumentNoteId filter may be incomplete.
27475
27601
  ` : "";
27476
27602
  header = (hasMore ? `Showing ${offset + 1}\u2013${offset + page.length} of ${total} notes. Use offset=${offset + limit} to continue.
27477
27603
 
@@ -27484,7 +27610,7 @@ function registerNoteTools(server, ctx, client) {
27484
27610
  projectNotesResult = await client.listAllSystemNotes(systemId, filters, limit, cursor);
27485
27611
  } catch (err) {
27486
27612
  const message = err instanceof Error ? err.message : String(err);
27487
- return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
27613
+ return { content: [{ type: "text", text: `Failed to list system notes: ${message}` }], isError: true };
27488
27614
  }
27489
27615
  const { notes: rawNotes, cursor: nextCursor, hasMore: serviceHasMore } = projectNotesResult;
27490
27616
  hasMore = serviceHasMore;
@@ -27566,11 +27692,11 @@ function registerNoteTools(server, ctx, client) {
27566
27692
  systemId: external_exports.string().describe("The system ID")
27567
27693
  },
27568
27694
  async ({ systemId }) => {
27569
- await assertProjectInOrg(client, systemId, ctx.orgId);
27695
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27570
27696
  const DOCUMENT_FETCH_LIMIT = 5e3;
27571
27697
  const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
27572
27698
  const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
27573
- const truncationNotice = truncated && visibleDocs.length > 0 ? "\n\n> \u26A0\uFE0F This project has more than 5000 documents. Some documents may not be shown.\n" : "";
27699
+ const truncationNotice = truncated && visibleDocs.length > 0 ? "\n\n> \u26A0\uFE0F This system has more than 5000 documents. Some documents may not be shown.\n" : "";
27574
27700
  const text = formatDocumentList(visibleDocs) + truncationNotice;
27575
27701
  return { content: [{ type: "text", text }] };
27576
27702
  }
@@ -27601,7 +27727,7 @@ function registerNoteTools(server, ctx, client) {
27601
27727
  }
27602
27728
  return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
27603
27729
  }
27604
- await assertProjectInOrg(client, systemId, ctx.orgId);
27730
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27605
27731
  const note = await client.getNote(noteId);
27606
27732
  if (!note) {
27607
27733
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27619,8 +27745,8 @@ function registerNoteTools(server, ctx, client) {
27619
27745
  "Create a new Note. Provide exactly one of systemId, teamspaceId, movementId, or none (org-level): systemId scopes the note to a system (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child systems), movementId scopes it to a Movement (commercial SOW container \u2014 engagement-level knowledge), and omitting all creates an org-level note that applies across all systems.",
27620
27746
  {
27621
27747
  systemId: external_exports.string().optional().describe("The system ID (omit for teamspace-, movement-, or org-level note)"),
27622
- teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for project-, movement-, or org-level note)"),
27623
- movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for project-, teamspace-, or org-level note)"),
27748
+ teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for system-, movement-, or org-level note)"),
27749
+ movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for system-, teamspace-, or org-level note)"),
27624
27750
  noteType: external_exports.enum(NOTE_TYPE_VALUES).describe("The type of note"),
27625
27751
  content: external_exports.string().describe("The note content"),
27626
27752
  beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires systemId)"),
@@ -27640,7 +27766,7 @@ function registerNoteTools(server, ctx, client) {
27640
27766
  const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
27641
27767
  let note;
27642
27768
  if (systemId) {
27643
- await assertProjectInOrg(client, systemId, ctx.orgId);
27769
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27644
27770
  const projectCode = systemId.substring(0, 4).toUpperCase();
27645
27771
  const noteId = await client.getNextNoteId(projectCode, systemId);
27646
27772
  note = await client.createNote({
@@ -27725,7 +27851,7 @@ ${text}` }] };
27725
27851
  content: external_exports.string().optional().describe("Updated content"),
27726
27852
  rationale: external_exports.string().optional().describe("Why this note exists or why this decision was made"),
27727
27853
  response: external_exports.string().optional().describe("User response (for resolving assumptions)"),
27728
- beatId: external_exports.string().nullable().optional().describe("Move this note to a different beat, or null to unscope it back to project level (updates all index items transactionally)"),
27854
+ beatId: external_exports.string().nullable().optional().describe("Move this note to a different beat, or null to unscope it back to system level (updates all index items transactionally)"),
27729
27855
  revisionId: external_exports.string().optional().describe("Revision ID to associate this note with (revision-scoped assumptions, decisions, or guidance)"),
27730
27856
  dependsOnNotes: external_exports.array(external_exports.string()).optional().describe("Note IDs this note depends on (child \u2192 parent links for consolidation)"),
27731
27857
  humanAssignee: humanAssigneeSchema.nullable().optional().describe("Assign or reassign this note to a person (null to clear)"),
@@ -27768,7 +27894,7 @@ ${text}` }] };
27768
27894
 
27769
27895
  ${formatNoteDetail(updated2)}` }] };
27770
27896
  }
27771
- await assertProjectInOrg(client, systemId, ctx.orgId);
27897
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27772
27898
  const updated = await client.updateNote(noteId, {
27773
27899
  ...noteType !== void 0 && { noteType },
27774
27900
  status,
@@ -27810,7 +27936,7 @@ ${text}` }] };
27810
27936
  },
27811
27937
  async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
27812
27938
  try {
27813
- await assertProjectInOrg(client, systemId, ctx.orgId);
27939
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27814
27940
  const result = await client.bulkUpdateNotes(noteIds, {
27815
27941
  ...noteType !== void 0 && { noteType },
27816
27942
  status,
@@ -27849,7 +27975,7 @@ ${text}` }] };
27849
27975
  },
27850
27976
  async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
27851
27977
  try {
27852
- await assertProjectInOrg(client, systemId, ctx.orgId);
27978
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27853
27979
  const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
27854
27980
  if (!updated) {
27855
27981
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27871,7 +27997,7 @@ ${text}` }] };
27871
27997
  },
27872
27998
  async ({ systemId, noteId, reason }) => {
27873
27999
  try {
27874
- await assertProjectInOrg(client, systemId, ctx.orgId);
28000
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27875
28001
  const note = await client.getNote(noteId);
27876
28002
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
27877
28003
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27898,7 +28024,7 @@ ${text}` }] };
27898
28024
  },
27899
28025
  async ({ systemId, noteId, reason }) => {
27900
28026
  try {
27901
- await assertProjectInOrg(client, systemId, ctx.orgId);
28027
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27902
28028
  const note = await client.getNote(noteId);
27903
28029
  if (!note || (note.systemId ?? note.projectId) !== systemId) {
27904
28030
  return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
@@ -27928,7 +28054,7 @@ ${text}` }] };
27928
28054
  systemId: external_exports.string().describe("The system ID")
27929
28055
  },
27930
28056
  async ({ systemId }) => {
27931
- await assertProjectInOrg(client, systemId, ctx.orgId);
28057
+ await assertSystemInOrg(client, systemId, ctx.orgId);
27932
28058
  const assumptions = await client.getKeyAssumptions(systemId);
27933
28059
  if (assumptions.length === 0) {
27934
28060
  return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
@@ -28631,7 +28757,7 @@ function registerOnboardingTools(server, ctx, client) {
28631
28757
  analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
28632
28758
  },
28633
28759
  async ({ systemId, systemTitle, projectDescription, analysisJson }) => {
28634
- await assertProjectInOrg(client, systemId, ctx.orgId);
28760
+ await assertSystemInOrg(client, systemId, ctx.orgId);
28635
28761
  const result = await client.draftOnboardingBeats(systemId, { systemTitle, projectDescription, analysisJson });
28636
28762
  if (!result.suggestions || result.suggestions.length === 0) {
28637
28763
  return {
@@ -28660,7 +28786,7 @@ function registerOnboardingTools(server, ctx, client) {
28660
28786
  server.tool(
28661
28787
  "create_teamspace_onboarding_batch",
28662
28788
  [
28663
- "Create a Teamspace, Projects, and Notes",
28789
+ "Create a Teamspace, Systems, and Notes",
28664
28790
  "from a PM-approved onboarding payload in a single batch operation.",
28665
28791
  "Call this after the PM approves the pre-flight review card from initiate_teamspace_onboarding.",
28666
28792
  "Returns a creation receipt with IDs and counts for all created entities.",
@@ -28668,8 +28794,8 @@ function registerOnboardingTools(server, ctx, client) {
28668
28794
  ].join(" "),
28669
28795
  {
28670
28796
  teamspaceName: external_exports.string().min(1).max(200).describe("Teamspace display name"),
28671
- projects: external_exports.array(OnboardingProjectInputSchema).min(1).describe("Phase 1 projects to create, each linked to the Teamspace"),
28672
- teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/project level")
28797
+ projects: external_exports.array(OnboardingProjectInputSchema).min(1).describe("Phase 1 systems to create, each linked to the Teamspace"),
28798
+ teamspaceNotes: external_exports.array(OnboardingNoteInputSchema).describe("Constraints, assumptions, and context notes at the Teamspace/system level")
28673
28799
  },
28674
28800
  async ({
28675
28801
  teamspaceName,
@@ -28710,7 +28836,7 @@ Error: ${msg}`);
28710
28836
  const msg = err instanceof Error ? err.message : String(err);
28711
28837
  lines.push(`
28712
28838
  Projects created: ${createdProjects.length}`);
28713
- lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: project "${p.title}"
28839
+ lines.unshift(`\u26A0\uFE0F Onboarding batch partially created \u2014 stopped at: system "${p.title}"
28714
28840
  Error: ${msg}`);
28715
28841
  return { content: [{ type: "text", text: lines.join("\n") }] };
28716
28842
  }
@@ -28932,7 +29058,7 @@ function registerOrganizationTools(server, ctx, client) {
28932
29058
  if (system.orgId !== ctx.orgId) {
28933
29059
  return { content: [{ type: "text", text: `System "${systemId}" does not belong to the configured organization.` }], isError: true };
28934
29060
  }
28935
- const result = await client.transferProject(ctx.orgId, systemId, targetOrgId);
29061
+ const result = await client.transferSystem(ctx.orgId, systemId, targetOrgId);
28936
29062
  if (result.error) {
28937
29063
  return { content: [{ type: "text", text: result.error }], isError: true };
28938
29064
  }
@@ -29083,7 +29209,7 @@ function registerPlanQualityTools(server, ctx, client) {
29083
29209
  isError: true
29084
29210
  };
29085
29211
  }
29086
- await assertProjectInOrg(client, systemId, ctx.orgId);
29212
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29087
29213
  const opts = branch || localPath ? { branch, localPath } : void 0;
29088
29214
  const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
29089
29215
  if (!wait) {
@@ -29230,7 +29356,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29230
29356
  },
29231
29357
  async ({ systemId, wait }) => {
29232
29358
  try {
29233
- await assertProjectInOrg(client, systemId, ctx.orgId);
29359
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29234
29360
  const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
29235
29361
  if (!wait) {
29236
29362
  return {
@@ -29267,7 +29393,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29267
29393
  const archivedBeatIds = [];
29268
29394
  let totalNotesReassigned = 0;
29269
29395
  try {
29270
- await assertProjectInOrg(client, systemId, ctx.orgId);
29396
+ await assertSystemInOrg(client, systemId, ctx.orgId);
29271
29397
  const uniqueBeatIds = [...new Set(beatIds)];
29272
29398
  const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
29273
29399
  if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
@@ -30020,8 +30146,8 @@ function registerRevisionQualityTools(server, ctx, client) {
30020
30146
  isError: true
30021
30147
  };
30022
30148
  }
30023
- await assertProjectInOrg(client, systemId, ctx.orgId);
30024
- await assertRevisionInProject(client, revisionId, systemId);
30149
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30150
+ await assertRevisionInSystem(client, revisionId, systemId);
30025
30151
  const task = await client.runCheck(systemId, "revision_quality", revisionId);
30026
30152
  if (!wait) {
30027
30153
  return {
@@ -30326,7 +30452,7 @@ function registerSnapshotTools(server, ctx, client) {
30326
30452
  "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.",
30327
30453
  { systemId: external_exports.string().describe("The system ID to export") },
30328
30454
  async ({ systemId }) => {
30329
- await assertProjectInOrg(client, systemId, ctx.orgId);
30455
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30330
30456
  const result = await client.exportSystemSnapshot(systemId);
30331
30457
  if (typeof result === "string") {
30332
30458
  if (!result.startsWith("https://")) {
@@ -30574,7 +30700,7 @@ function registerSubscriptionTools(server, ctx, client) {
30574
30700
  },
30575
30701
  async ({ entityType, entityId, systemId }) => {
30576
30702
  try {
30577
- await assertProjectInOrg(client, systemId, ctx.orgId);
30703
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30578
30704
  await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
30579
30705
  return {
30580
30706
  content: [{
@@ -30638,7 +30764,7 @@ function registerSubscriptionTools(server, ctx, client) {
30638
30764
  }
30639
30765
 
30640
30766
  // ../../libs/harmonica-services/src/mcp/tools/system-lifecycle-tools.ts
30641
- function registerProjectLifecycleTools(server, ctx, client) {
30767
+ function registerSystemLifecycleTools(server, ctx, client) {
30642
30768
  server.tool(
30643
30769
  "transition_system_lifecycle",
30644
30770
  "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.",
@@ -30744,7 +30870,7 @@ function registerSystemTools(server, ctx, client) {
30744
30870
  }) => {
30745
30871
  try {
30746
30872
  const [project, org] = await Promise.all([
30747
- fetchProjectInOrg(client, systemId, ctx.orgId),
30873
+ fetchSystemInOrg(client, systemId, ctx.orgId),
30748
30874
  client.getOrg(ctx.orgId)
30749
30875
  ]);
30750
30876
  const notes = await client.listSystemNotes(systemId, {
@@ -30788,7 +30914,7 @@ function registerSystemTools(server, ctx, client) {
30788
30914
  if (Object.keys(nonEmpty).length === 0) {
30789
30915
  return { content: [{ type: "text", text: "No updates provided." }], isError: true };
30790
30916
  }
30791
- await assertProjectInOrg(client, systemId, ctx.orgId);
30917
+ await assertSystemInOrg(client, systemId, ctx.orgId);
30792
30918
  if (nonEmpty["accountId"] === "") delete nonEmpty["accountId"];
30793
30919
  if (typeof updates.teamspaceId === "string") {
30794
30920
  const teamspace = await client.getTeamspace(updates.teamspaceId);
@@ -31127,7 +31253,7 @@ function registerValueVelocityTools(server, ctx, client) {
31127
31253
  },
31128
31254
  async ({ systemId, lens, withLeverage }) => {
31129
31255
  try {
31130
- await assertProjectInOrg(client, systemId, ctx.orgId);
31256
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31131
31257
  const [rankedResult, staleWResult] = await Promise.allSettled([
31132
31258
  withLeverage ? client.rankSystemBeatVersionsWithLeverage(systemId, lens) : client.rankSystemBeatVersions(systemId, lens),
31133
31259
  client.getStaleWBeatIds(systemId)
@@ -31234,7 +31360,7 @@ function registerValueVelocityTools(server, ctx, client) {
31234
31360
  },
31235
31361
  async ({ systemId, beatId }) => {
31236
31362
  try {
31237
- await assertProjectInOrg(client, systemId, ctx.orgId);
31363
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31238
31364
  const actorId = resolveActorId(ctx);
31239
31365
  if (!actorId) {
31240
31366
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
@@ -31417,6 +31543,7 @@ function registerWorkItemTools(server, _ctx, client) {
31417
31543
  wi.valueUnits !== void 0 ? `Value Units: ${wi.valueUnits}` : null,
31418
31544
  wi.costPerValueUnit !== void 0 ? `Cost per Value Unit: ${wi.costPerValueUnit}` : null,
31419
31545
  wi.beatVersionId ? `Linked Beat Version: ${wi.beatVersionId}` : null,
31546
+ wi.workOrderId ? `Work Order: ${wi.workOrderId}` : "Work Order: none (unattached)",
31420
31547
  estimateHint ? `Estimate Hint: ${estimateHint.hours}h (source: ${estimateHint.source})` : null,
31421
31548
  wi.createdBy ? `Created By: ${wi.createdBy}` : null,
31422
31549
  `Created: ${wi.createdAt}`,
@@ -31427,7 +31554,7 @@ function registerWorkItemTools(server, _ctx, client) {
31427
31554
  );
31428
31555
  server.tool(
31429
31556
  "create_work_item",
31430
- '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.',
31557
+ '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).',
31431
31558
  {
31432
31559
  trackId: external_exports.string().describe("The Track (DeliverableGroup) ID this work item belongs to"),
31433
31560
  title: external_exports.string().min(1).max(300).describe("Work item title"),
@@ -31438,9 +31565,10 @@ function registerWorkItemTools(server, _ctx, client) {
31438
31565
  valueUnits: external_exports.number().nonnegative().optional().describe("Optional Value Units (commercial lens) \u2014 independent of effortSize"),
31439
31566
  costPerValueUnit: external_exports.number().nonnegative().optional().describe("Optional price per Value Unit"),
31440
31567
  createdBy: external_exports.string().optional().describe("Optional creator identity for audit"),
31441
- beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist.")
31568
+ beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist."),
31569
+ 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.")
31442
31570
  },
31443
- async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId }) => {
31571
+ async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId, workOrderId }) => {
31444
31572
  if (ownerName === void 0 !== (ownerEmail === void 0)) {
31445
31573
  return {
31446
31574
  content: [{ type: "text", text: "Setting the owner requires both ownerName and ownerEmail. Omit both to create an unowned work item." }],
@@ -31456,7 +31584,8 @@ function registerWorkItemTools(server, _ctx, client) {
31456
31584
  ...valueUnits !== void 0 && { valueUnits },
31457
31585
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
31458
31586
  ...createdBy !== void 0 && { createdBy },
31459
- ...beatVersionId !== void 0 && { beatVersionId }
31587
+ ...beatVersionId !== void 0 && { beatVersionId },
31588
+ ...workOrderId !== void 0 && { workOrderId }
31460
31589
  });
31461
31590
  return {
31462
31591
  content: [
@@ -31470,7 +31599,7 @@ function registerWorkItemTools(server, _ctx, client) {
31470
31599
  );
31471
31600
  server.tool(
31472
31601
  "update_work_item",
31473
- "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.",
31602
+ "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.",
31474
31603
  {
31475
31604
  workItemId: external_exports.string().describe("The work item ID"),
31476
31605
  title: external_exports.string().min(1).max(300).optional().describe("New title"),
@@ -31482,9 +31611,10 @@ function registerWorkItemTools(server, _ctx, client) {
31482
31611
  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."),
31483
31612
  valueUnits: external_exports.number().nonnegative().nullable().optional().describe("Value Units (commercial lens). Pass null to clear; omit to leave untouched."),
31484
31613
  costPerValueUnit: external_exports.number().nonnegative().nullable().optional().describe("Price per Value Unit. Pass null to clear; omit to leave untouched."),
31485
- 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.")
31614
+ 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."),
31615
+ 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.")
31486
31616
  },
31487
- async ({ workItemId, title, ownerName, ownerEmail, unassignOwner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId }) => {
31617
+ async ({ workItemId, title, ownerName, ownerEmail, unassignOwner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId, workOrderId }) => {
31488
31618
  const hasOwnerUpdate = ownerName !== void 0 || ownerEmail !== void 0;
31489
31619
  if (hasOwnerUpdate && (ownerName === void 0 || ownerEmail === void 0)) {
31490
31620
  return {
@@ -31498,7 +31628,9 @@ function registerWorkItemTools(server, _ctx, client) {
31498
31628
  isError: true
31499
31629
  };
31500
31630
  }
31501
- 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;
31631
+ 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
31632
+ // a truthy check here would report "no updates provided" and drop it.
31633
+ workOrderId !== void 0;
31502
31634
  if (!hasUpdates) {
31503
31635
  return {
31504
31636
  content: [{ type: "text", text: `No updates provided for work item: ${workItemId}` }],
@@ -31514,7 +31646,8 @@ function registerWorkItemTools(server, _ctx, client) {
31514
31646
  ...effortSize !== void 0 && { effortSize },
31515
31647
  ...valueUnits !== void 0 && { valueUnits },
31516
31648
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
31517
- ...beatVersionId !== void 0 && { beatVersionId }
31649
+ ...beatVersionId !== void 0 && { beatVersionId },
31650
+ ...workOrderId !== void 0 && { workOrderId }
31518
31651
  });
31519
31652
  if (!wi) {
31520
31653
  return {
@@ -31579,6 +31712,337 @@ function registerWorkItemTools(server, _ctx, client) {
31579
31712
  );
31580
31713
  }
31581
31714
 
31715
+ // ../../libs/harmonica-services/src/mcp/tools/work-order-tools.ts
31716
+ var WORK_INTAKE_STATUS_ENUM = [
31717
+ "requested",
31718
+ "needs_decision",
31719
+ "accepted",
31720
+ "done"
31721
+ ];
31722
+ var NEEDS_DECISION_REASON_ENUM = [
31723
+ "out_of_scope",
31724
+ "exceeds_capacity",
31725
+ "incoherent_with_decision"
31726
+ ];
31727
+ var WORK_ORDER_SIZE_ENUM = [
31728
+ "XS",
31729
+ "S",
31730
+ "M",
31731
+ "L",
31732
+ "XL"
31733
+ ];
31734
+ function oneLine(value) {
31735
+ return value.replace(/[\r\n]+/g, " ");
31736
+ }
31737
+ function formatOrderLine(o) {
31738
+ const reason = o.needsDecisionReason ? ` [${o.needsDecisionReason}]` : "";
31739
+ return `[${o.workOrderId}] ${oneLine(o.title)} \u2014 ${o.status}${reason}, ${o.size ?? "unsized"}, on ${o.trackId}`;
31740
+ }
31741
+ function registerWorkOrderTools(server, _ctx, client) {
31742
+ server.tool(
31743
+ "list_track_work_orders",
31744
+ 'List the Work Orders placed on a Track (DeliverableGroup). A Work Order is the internal unit of agreed work. Recognition cues: "what is committed on this track", "list the orders" \u2192 list_track_work_orders.',
31745
+ {
31746
+ trackId: external_exports.string().describe("The Track (DeliverableGroup) ID")
31747
+ },
31748
+ async ({ trackId }) => {
31749
+ const orders = await client.listTrackWorkOrders(trackId);
31750
+ const text = orders.length === 0 ? "No work orders found." : orders.map(formatOrderLine).join("\n");
31751
+ return { content: [{ type: "text", text }] };
31752
+ }
31753
+ );
31754
+ server.tool(
31755
+ "list_work_request_work_orders",
31756
+ "List the Work Orders one Work Request split into \u2014 the lineage view. A four-way split shows four orders here, each possibly on a different Track and in a different state.",
31757
+ {
31758
+ workRequestId: external_exports.string().describe("The Work Request ID")
31759
+ },
31760
+ async ({ workRequestId }) => {
31761
+ const orders = await client.listWorkRequestWorkOrders(workRequestId);
31762
+ const text = orders.length === 0 ? "No work orders found \u2014 this request has not been split yet." : orders.map(formatOrderLine).join("\n");
31763
+ return { content: [{ type: "text", text }] };
31764
+ }
31765
+ );
31766
+ server.tool(
31767
+ "get_work_order",
31768
+ "Get a Work Order by ID. Returns its title, status (with the routing reason when it is awaiting a decision), t-shirt size, the Track it sits on, and the Work Request it was split from.",
31769
+ {
31770
+ workOrderId: external_exports.string().describe("The Work Order ID (e.g., wo-abc123)")
31771
+ },
31772
+ async ({ workOrderId }) => {
31773
+ const wo = await client.getWorkOrder(workOrderId);
31774
+ if (!wo) {
31775
+ return { content: [{ type: "text", text: `Work order not found: ${workOrderId}` }] };
31776
+ }
31777
+ const lines = [
31778
+ `ID: ${wo.workOrderId}`,
31779
+ `Title: ${oneLine(wo.title)}`,
31780
+ `From Work Request: ${wo.workRequestId}`,
31781
+ `Track: ${wo.trackId}`,
31782
+ `Status: ${wo.status}`,
31783
+ wo.needsDecisionReason ? `Routing Reason: ${wo.needsDecisionReason}` : null,
31784
+ wo.size !== void 0 ? `Size: ${wo.size}` : "Size: unsized",
31785
+ wo.description ? `Description: ${oneLine(wo.description)}` : null,
31786
+ wo.createdBy ? `Created By: ${wo.createdBy}` : null,
31787
+ `Created: ${wo.createdAt}`,
31788
+ `Updated: ${wo.updatedAt}`
31789
+ ].filter(Boolean).join("\n");
31790
+ return { content: [{ type: "text", text: lines }] };
31791
+ }
31792
+ );
31793
+ server.tool(
31794
+ "create_work_order",
31795
+ 'Split a Work Request into an agreed Work Order on a Track. Called once per piece: a request covering two separate lines of work becomes two orders. Starts in "requested" status \u2014 moving it past the coherence gate is transition_work_order_status. The Track must belong to the same teamspace and Account as the Request; the service refuses a placement that crosses either boundary.',
31796
+ {
31797
+ trackId: external_exports.string().describe("The Track (DeliverableGroup) this order is placed on"),
31798
+ workRequestId: external_exports.string().min(1).describe("The Work Request this order was split from \u2014 immutable lineage"),
31799
+ title: external_exports.string().min(1).max(300).describe("Work order title"),
31800
+ description: external_exports.string().max(1e4).optional().describe("Optional detail beyond the title"),
31801
+ size: external_exports.enum(WORK_ORDER_SIZE_ENUM).optional().describe("Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them."),
31802
+ createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
31803
+ },
31804
+ async ({ trackId, workRequestId, title, description, size, createdBy }) => {
31805
+ const wo = await client.createWorkOrder({
31806
+ trackId,
31807
+ workRequestId,
31808
+ title,
31809
+ ...description !== void 0 && { description },
31810
+ ...size !== void 0 && { size },
31811
+ ...createdBy !== void 0 && { createdBy }
31812
+ });
31813
+ return {
31814
+ content: [{ type: "text", text: `Created work order: ${formatOrderLine(wo)}` }]
31815
+ };
31816
+ }
31817
+ );
31818
+ server.tool(
31819
+ "update_work_order",
31820
+ "Update content fields on a Work Order (title, description, size). To move it between Tracks use move_work_order; to change its status use transition_work_order_status. The Work Request it came from cannot be changed \u2014 that lineage is immutable.",
31821
+ {
31822
+ workOrderId: external_exports.string().describe("The Work Order ID"),
31823
+ title: external_exports.string().min(1).max(300).optional().describe("New title"),
31824
+ description: external_exports.string().max(1e4).nullable().optional().describe("New description. Pass null to clear; omit to leave untouched."),
31825
+ size: external_exports.enum(WORK_ORDER_SIZE_ENUM).nullable().optional().describe("New t-shirt size. Pass null to return it to unsized; omit to leave untouched.")
31826
+ },
31827
+ async ({ workOrderId, title, description, size }) => {
31828
+ if (title === void 0 && description === void 0 && size === void 0) {
31829
+ return {
31830
+ content: [
31831
+ { type: "text", text: "Nothing to update: pass at least one of title, description, or size." }
31832
+ ],
31833
+ isError: true
31834
+ };
31835
+ }
31836
+ const wo = await client.updateWorkOrder(workOrderId, {
31837
+ ...title !== void 0 && { title },
31838
+ ...description !== void 0 && { description },
31839
+ ...size !== void 0 && { size }
31840
+ });
31841
+ if (!wo) {
31842
+ return { content: [{ type: "text", text: `Work order not found: ${workOrderId}` }] };
31843
+ }
31844
+ return { content: [{ type: "text", text: `Updated work order: ${formatOrderLine(wo)}` }] };
31845
+ }
31846
+ );
31847
+ server.tool(
31848
+ "move_work_order",
31849
+ "Move a Work Order to a different Track \u2014 the reversal path for a bad early split guess. The destination must belong to the same teamspace and Account as the order's Work Request; the service refuses a placement that crosses either boundary.",
31850
+ {
31851
+ workOrderId: external_exports.string().describe("The Work Order ID"),
31852
+ newTrackId: external_exports.string().describe("The destination Track (DeliverableGroup) ID")
31853
+ },
31854
+ async ({ workOrderId, newTrackId }) => {
31855
+ const wo = await client.moveWorkOrder(workOrderId, newTrackId);
31856
+ if (!wo) {
31857
+ return { content: [{ type: "text", text: `Work order not found: ${workOrderId}` }] };
31858
+ }
31859
+ return {
31860
+ content: [{ type: "text", text: `Moved work order to Track ${wo.trackId}: ${formatOrderLine(wo)}` }]
31861
+ };
31862
+ }
31863
+ );
31864
+ server.tool(
31865
+ "transition_work_order_status",
31866
+ "Move a Work Order through the intake gate: requested \u2192 accepted is the coherence gate itself, and there is no shortcut past it. Routing an order to needs_decision REQUIRES a reason, because the reason is the routing: out_of_scope goes back to the customer, exceeds_capacity goes to the Orchestrator, incoherent_with_decision goes back for reframing. A routed order returns to requested once resolved.",
31867
+ {
31868
+ workOrderId: external_exports.string().describe("The Work Order ID"),
31869
+ status: external_exports.enum(WORK_INTAKE_STATUS_ENUM).describe("Target status"),
31870
+ reason: external_exports.enum(NEEDS_DECISION_REASON_ENUM).optional().describe("Required when \u2014 and only when \u2014 status is needs_decision. The reason IS the routing.")
31871
+ },
31872
+ async ({ workOrderId, status, reason }) => {
31873
+ if (status === "needs_decision" && reason === void 0) {
31874
+ return {
31875
+ content: [
31876
+ {
31877
+ type: "text",
31878
+ text: "Routing an order to needs_decision requires a reason \u2014 the reason is the routing. Pass one of: out_of_scope, exceeds_capacity, incoherent_with_decision."
31879
+ }
31880
+ ],
31881
+ isError: true
31882
+ };
31883
+ }
31884
+ if (status !== "needs_decision" && reason !== void 0) {
31885
+ return {
31886
+ content: [
31887
+ {
31888
+ type: "text",
31889
+ text: `A routing reason is only meaningful on a transition to needs_decision, not to ${status}.`
31890
+ }
31891
+ ],
31892
+ isError: true
31893
+ };
31894
+ }
31895
+ const wo = await client.transitionWorkOrderStatus(workOrderId, status, reason);
31896
+ if (!wo) {
31897
+ return { content: [{ type: "text", text: `Work order not found: ${workOrderId}` }] };
31898
+ }
31899
+ return {
31900
+ content: [{ type: "text", text: `Transitioned work order: ${formatOrderLine(wo)}` }]
31901
+ };
31902
+ }
31903
+ );
31904
+ }
31905
+
31906
+ // ../../libs/harmonica-services/src/mcp/tools/work-request-tools.ts
31907
+ var WORK_REQUEST_ORIGIN_ENUM = [
31908
+ "client",
31909
+ "internal"
31910
+ ];
31911
+ function oneLine2(value) {
31912
+ return value.replace(/[\r\n]+/g, " ");
31913
+ }
31914
+ function formatSizeComposition(size) {
31915
+ const parts = size.entries.map((e) => `${e.count}${e.size}`);
31916
+ if (size.unsized > 0) parts.push(`${size.unsized} unsized`);
31917
+ if (parts.length === 0) return "no orders yet";
31918
+ return `${parts.join(" + ")} (${size.totalValueUnits} value units)`;
31919
+ }
31920
+ function registerWorkRequestTools(server, _ctx, client) {
31921
+ server.tool(
31922
+ "list_movement_work_requests",
31923
+ `List the Work Requests filed against a Movement. A Work Request is the customer's ask in their own language, before it is sized or agreed. Recognition cues: "what has the client asked for", "list the requests on this engagement" \u2192 list_movement_work_requests.`,
31924
+ {
31925
+ movementId: external_exports.string().describe("The Movement (engagement) ID")
31926
+ },
31927
+ async ({ movementId }) => {
31928
+ const requests = await client.listMovementWorkRequests(movementId);
31929
+ const text = requests.length === 0 ? "No work requests found." : requests.map((wr) => `[${wr.workRequestId}] ${oneLine2(wr.title)} \u2014 from ${oneLine2(wr.requester.name)} (${wr.origin})`).join("\n");
31930
+ return { content: [{ type: "text", text }] };
31931
+ }
31932
+ );
31933
+ server.tool(
31934
+ "get_work_request",
31935
+ "Get a Work Request with the Work Orders it split into and their combined size composition. Note there is no request-level status: each Order carries its own, because a split whose halves sit in different states cannot be reduced to one word.",
31936
+ {
31937
+ workRequestId: external_exports.string().describe("The Work Request ID (e.g., wr-abc123)")
31938
+ },
31939
+ async ({ workRequestId }) => {
31940
+ const detail = await client.getWorkRequestDetail(workRequestId);
31941
+ if (!detail) {
31942
+ return { content: [{ type: "text", text: `Work request not found: ${workRequestId}` }] };
31943
+ }
31944
+ const { workRequest: wr, orders, size } = detail;
31945
+ const orderLines = orders.length === 0 ? ["Orders: none yet \u2014 this request has not been split"] : [
31946
+ "Orders:",
31947
+ ...orders.map((o) => {
31948
+ const track = o.trackTitle ? `${oneLine2(o.trackTitle)} (${o.trackId})` : o.trackId;
31949
+ const reason = o.needsDecisionReason ? ` [${o.needsDecisionReason}]` : "";
31950
+ const sized = o.size ?? "unsized";
31951
+ return ` [${o.workOrderId}] ${oneLine2(o.title)} \u2014 ${o.status}${reason}, ${sized}, on ${track}`;
31952
+ })
31953
+ ];
31954
+ const lines = [
31955
+ `ID: ${wr.workRequestId}`,
31956
+ `Title: ${oneLine2(wr.title)}`,
31957
+ `Movement: ${wr.movementId}`,
31958
+ `Requester: ${oneLine2(wr.requester.name)} <${wr.requester.email}>`,
31959
+ `Origin: ${wr.origin}`,
31960
+ wr.body ? `Ask: ${oneLine2(wr.body)}` : null,
31961
+ `Size: ${formatSizeComposition(size)}`,
31962
+ ...orderLines,
31963
+ wr.createdBy ? `Created By: ${wr.createdBy}` : null,
31964
+ `Created: ${wr.createdAt}`,
31965
+ `Updated: ${wr.updatedAt}`
31966
+ ].filter(Boolean).join("\n");
31967
+ return { content: [{ type: "text", text: lines }] };
31968
+ }
31969
+ );
31970
+ server.tool(
31971
+ "create_work_request",
31972
+ "File a Work Request against a Movement \u2014 how work arrives, in the requester's own language, before it is sized or agreed. Splitting it into Work Orders is a separate step (create_work_order).",
31973
+ {
31974
+ movementId: external_exports.string().describe("The Movement (engagement) this request is filed against"),
31975
+ title: external_exports.string().min(1).max(300).describe("Short title for the ask"),
31976
+ body: external_exports.string().max(1e4).optional().describe("The ask as written, in the requester's own words"),
31977
+ requesterName: external_exports.string().min(1).describe("Who is asking \u2014 display name"),
31978
+ requesterEmail: external_exports.string().email().describe("Who is asking \u2014 email"),
31979
+ origin: external_exports.enum(WORK_REQUEST_ORIGIN_ENUM).describe('Where the ask came from: "client" or "internal". One entity covers both \u2014 only the entry point differs.'),
31980
+ createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
31981
+ },
31982
+ async ({ movementId, title, body, requesterName, requesterEmail, origin, createdBy }) => {
31983
+ const wr = await client.createWorkRequest({
31984
+ movementId,
31985
+ title,
31986
+ ...body !== void 0 && { body },
31987
+ requester: { name: requesterName, email: requesterEmail },
31988
+ origin,
31989
+ ...createdBy !== void 0 && { createdBy }
31990
+ });
31991
+ return {
31992
+ content: [
31993
+ {
31994
+ type: "text",
31995
+ text: `Filed work request: [${wr.workRequestId}] ${wr.title} \u2014 from ${wr.requester.name} (${wr.origin})`
31996
+ }
31997
+ ]
31998
+ };
31999
+ }
32000
+ );
32001
+ server.tool(
32002
+ "update_work_request",
32003
+ "Reword a Work Request (title, body, requester). A Request cannot be moved to another Movement, and has no status to set \u2014 its Orders carry the state.",
32004
+ {
32005
+ workRequestId: external_exports.string().describe("The Work Request ID"),
32006
+ title: external_exports.string().min(1).max(300).optional().describe("New title"),
32007
+ body: external_exports.string().max(1e4).nullable().optional().describe("The ask as written. Pass null to clear; omit to leave untouched."),
32008
+ requesterName: external_exports.string().min(1).optional().describe("New requester display name (requires requesterEmail too)"),
32009
+ requesterEmail: external_exports.string().email().optional().describe("New requester email (requires requesterName too)")
32010
+ },
32011
+ async ({ workRequestId, title, body, requesterName, requesterEmail }) => {
32012
+ if (requesterName === void 0 !== (requesterEmail === void 0)) {
32013
+ return {
32014
+ content: [
32015
+ {
32016
+ type: "text",
32017
+ text: "Changing the requester requires both requesterName and requesterEmail."
32018
+ }
32019
+ ],
32020
+ isError: true
32021
+ };
32022
+ }
32023
+ if (title === void 0 && body === void 0 && requesterName === void 0) {
32024
+ return {
32025
+ content: [
32026
+ { type: "text", text: "Nothing to update: pass at least one of title, body, or requester." }
32027
+ ],
32028
+ isError: true
32029
+ };
32030
+ }
32031
+ const wr = await client.updateWorkRequest(workRequestId, {
32032
+ ...title !== void 0 && { title },
32033
+ ...body !== void 0 && { body },
32034
+ ...requesterName !== void 0 && requesterEmail !== void 0 && { requester: { name: requesterName, email: requesterEmail } }
32035
+ });
32036
+ if (!wr) {
32037
+ return { content: [{ type: "text", text: `Work request not found: ${workRequestId}` }] };
32038
+ }
32039
+ return {
32040
+ content: [{ type: "text", text: `Updated work request: [${wr.workRequestId}] ${wr.title}` }]
32041
+ };
32042
+ }
32043
+ );
32044
+ }
32045
+
31582
32046
  // ../../libs/harmonica-services/src/mcp/tools/workflow-tools.ts
31583
32047
  var TASK_STATUSES = ["pending", "running", "completed", "failed"];
31584
32048
  function registerWorkflowTools(server, ctx, client) {
@@ -31622,7 +32086,7 @@ ${JSON.stringify(task.result, null, 2)}
31622
32086
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31623
32087
  },
31624
32088
  async ({ systemId, status, limit }) => {
31625
- await assertProjectInOrg(client, systemId, ctx.orgId);
32089
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31626
32090
  const tasks = await client.listSystemTasks(systemId, {
31627
32091
  status,
31628
32092
  limit
@@ -31680,7 +32144,7 @@ ${JSON.stringify(task.result, null, 2)}
31680
32144
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31681
32145
  },
31682
32146
  async ({ systemId, status, limit }) => {
31683
- await assertProjectInOrg(client, systemId, ctx.orgId);
32147
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31684
32148
  const tasks = await client.listSystemTasks(systemId, {
31685
32149
  status,
31686
32150
  limit
@@ -31704,7 +32168,7 @@ ${JSON.stringify(task.result, null, 2)}
31704
32168
  },
31705
32169
  async ({ systemId, beatId }) => {
31706
32170
  try {
31707
- await assertProjectInOrg(client, systemId, ctx.orgId);
32171
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31708
32172
  const task = await client.draftCoda(systemId, beatId);
31709
32173
  return {
31710
32174
  content: [{
@@ -31737,7 +32201,7 @@ ${JSON.stringify(task.result, null, 2)}
31737
32201
  },
31738
32202
  async ({ systemId, noteIds }) => {
31739
32203
  try {
31740
- await assertProjectInOrg(client, systemId, ctx.orgId);
32204
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31741
32205
  const task = await client.validateAssumptions(systemId, noteIds);
31742
32206
  return {
31743
32207
  content: [{
@@ -31772,7 +32236,7 @@ ${JSON.stringify(task.result, null, 2)}
31772
32236
  },
31773
32237
  async ({ systemId, message, revisionId }) => {
31774
32238
  try {
31775
- await assertProjectInOrg(client, systemId, ctx.orgId);
32239
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31776
32240
  const task = await client.startSession(
31777
32241
  systemId,
31778
32242
  message,
@@ -31809,7 +32273,7 @@ ${JSON.stringify(task.result, null, 2)}
31809
32273
  },
31810
32274
  async ({ systemId, revisionIds }) => {
31811
32275
  try {
31812
- await assertProjectInOrg(client, systemId, ctx.orgId);
32276
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31813
32277
  const task = await client.planRevisionBatch(systemId, revisionIds);
31814
32278
  return {
31815
32279
  content: [{
@@ -31841,7 +32305,7 @@ ${JSON.stringify(task.result, null, 2)}
31841
32305
  },
31842
32306
  async ({ systemId }) => {
31843
32307
  try {
31844
- await assertProjectInOrg(client, systemId, ctx.orgId);
32308
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31845
32309
  const task = await client.runTriage(systemId);
31846
32310
  return {
31847
32311
  content: [{
@@ -31887,7 +32351,7 @@ ${JSON.stringify(task.result, null, 2)}
31887
32351
  },
31888
32352
  async ({ systemId, content, filename, encoding }) => {
31889
32353
  try {
31890
- await assertProjectInOrg(client, systemId, ctx.orgId);
32354
+ await assertSystemInOrg(client, systemId, ctx.orgId);
31891
32355
  const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
31892
32356
  return {
31893
32357
  content: [{
@@ -31964,7 +32428,7 @@ var TOOL_PROFILES = Object.freeze({
31964
32428
  registerTeamspaceTools,
31965
32429
  registerMembershipTools,
31966
32430
  registerSystemTools,
31967
- registerProjectLifecycleTools,
32431
+ registerSystemLifecycleTools,
31968
32432
  allow(registerBeatVersionTools, ["relocate_beat_version"])
31969
32433
  ]),
31970
32434
  "onboarding": Object.freeze([
@@ -32002,7 +32466,7 @@ function registerAllTools(server, ctx, client, profile) {
32002
32466
  return;
32003
32467
  }
32004
32468
  registerSystemTools(server, ctx, client);
32005
- registerProjectLifecycleTools(server, ctx, client);
32469
+ registerSystemLifecycleTools(server, ctx, client);
32006
32470
  registerMembershipTools(server, ctx, client);
32007
32471
  registerBeatTools(server, ctx, client);
32008
32472
  registerBeatConvergenceTools(server, ctx, client);
@@ -32043,6 +32507,8 @@ function registerAllTools(server, ctx, client, profile) {
32043
32507
  registerAccountTools(server, ctx, client);
32044
32508
  registerMovementTools(server, ctx, client);
32045
32509
  registerWorkItemTools(server, ctx, client);
32510
+ registerWorkRequestTools(server, ctx, client);
32511
+ registerWorkOrderTools(server, ctx, client);
32046
32512
  registerBarTools(server, ctx, client);
32047
32513
  registerCadenceScheduleTools(server, ctx, client);
32048
32514
  registerPulseReportTools(server, ctx, client);
@@ -32323,7 +32789,7 @@ function createHttpClient(config2) {
32323
32789
  return { error: String(err) };
32324
32790
  }
32325
32791
  },
32326
- transferProject: async (sourceOrgId, projectId, targetOrgId) => {
32792
+ transferSystem: async (sourceOrgId, projectId, targetOrgId) => {
32327
32793
  try {
32328
32794
  const result = await request(
32329
32795
  "POST",
@@ -33451,7 +33917,7 @@ function createHttpClient(config2) {
33451
33917
  return importResult;
33452
33918
  },
33453
33919
  // Embedding similarity
33454
- embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
33920
+ embedSystemEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
33455
33921
  findSimilarNotes: async (noteId, projectId, options) => {
33456
33922
  const qs = buildSimilarityQS(options);
33457
33923
  const res = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes/${encodeURIComponent(noteId)}/similar${qs}`);
@@ -33695,7 +34161,7 @@ function createHttpClient(config2) {
33695
34161
  },
33696
34162
  // WorkItems (WorkItem exposure layer — owned, statused unit of operational work under a Track)
33697
34163
  createWorkItem: async (input) => {
33698
- const { trackId, title, owner, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId } = input;
34164
+ const { trackId, title, owner, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId, workOrderId } = input;
33699
34165
  const body = {
33700
34166
  title,
33701
34167
  ...owner !== void 0 && { owner },
@@ -33704,7 +34170,8 @@ function createHttpClient(config2) {
33704
34170
  ...valueUnits !== void 0 && { valueUnits },
33705
34171
  ...costPerValueUnit !== void 0 && { costPerValueUnit },
33706
34172
  ...createdBy !== void 0 && { createdBy },
33707
- ...beatVersionId !== void 0 && { beatVersionId }
34173
+ ...beatVersionId !== void 0 && { beatVersionId },
34174
+ ...workOrderId !== void 0 && { workOrderId }
33708
34175
  };
33709
34176
  const res = await request(
33710
34177
  "POST",
@@ -33766,6 +34233,134 @@ function createHttpClient(config2) {
33766
34233
  if (!parsed.success) throw new Error(`Unexpected WorkItem shape from API: ${parsed.error.message}`);
33767
34234
  return parsed.data;
33768
34235
  },
34236
+ // Work intake (B-331) — against the endpoints the API layer shipped in v3.
34237
+ createWorkRequest: async (input) => {
34238
+ const { movementId, title, body: askBody, requester, origin, createdBy } = input;
34239
+ const body = {
34240
+ title,
34241
+ ...askBody !== void 0 && { body: askBody },
34242
+ requester,
34243
+ origin,
34244
+ ...createdBy !== void 0 && { createdBy }
34245
+ };
34246
+ const res = await request(
34247
+ "POST",
34248
+ `/api/movements/${encodeURIComponent(movementId)}/work-requests`,
34249
+ body
34250
+ );
34251
+ if (res === void 0) throw new Error(`Movement ${movementId} not found`);
34252
+ const parsed = WorkRequestApiSchema.safeParse(res.workRequest);
34253
+ if (!parsed.success) throw new Error(`Unexpected WorkRequest shape from API: ${parsed.error.message}`);
34254
+ return parsed.data;
34255
+ },
34256
+ getWorkRequest: async (workRequestId) => {
34257
+ const res = await request(
34258
+ "GET",
34259
+ `/api/work-requests/${encodeURIComponent(workRequestId)}`
34260
+ );
34261
+ if (res === void 0) return void 0;
34262
+ const parsed = WorkRequestApiSchema.safeParse(res.workRequest);
34263
+ if (!parsed.success) throw new Error(`Unexpected WorkRequest shape from API: ${parsed.error.message}`);
34264
+ return parsed.data;
34265
+ },
34266
+ getWorkRequestDetail: async (workRequestId) => {
34267
+ const res = await request("GET", `/api/work-requests/${encodeURIComponent(workRequestId)}`);
34268
+ if (res === void 0) return void 0;
34269
+ const parsed = WorkRequestDetailResponseSchema.safeParse(res);
34270
+ if (!parsed.success) throw new Error(`Unexpected WorkRequest detail shape from API: ${parsed.error.message}`);
34271
+ return parsed.data;
34272
+ },
34273
+ updateWorkRequest: async (workRequestId, updates) => {
34274
+ const res = await request(
34275
+ "PATCH",
34276
+ `/api/work-requests/${encodeURIComponent(workRequestId)}`,
34277
+ updates
34278
+ );
34279
+ if (res === void 0) return void 0;
34280
+ const parsed = WorkRequestApiSchema.safeParse(res.workRequest);
34281
+ if (!parsed.success) throw new Error(`Unexpected WorkRequest shape from API: ${parsed.error.message}`);
34282
+ return parsed.data;
34283
+ },
34284
+ listMovementWorkRequests: async (movementId) => {
34285
+ const res = await request("GET", `/api/movements/${encodeURIComponent(movementId)}/work-requests`);
34286
+ if (res === void 0) throw new Error(`Movement ${movementId} not found`);
34287
+ const parsed = WorkRequestsResponseSchema.safeParse(res);
34288
+ if (!parsed.success) throw new Error(`Unexpected work requests list shape from API: ${parsed.error.message}`);
34289
+ return parsed.data.workRequests;
34290
+ },
34291
+ createWorkOrder: async (input) => {
34292
+ const { trackId, workRequestId, title, description, size, createdBy } = input;
34293
+ const body = {
34294
+ workRequestId,
34295
+ title,
34296
+ ...description !== void 0 && { description },
34297
+ ...size !== void 0 && { size },
34298
+ ...createdBy !== void 0 && { createdBy }
34299
+ };
34300
+ const res = await request(
34301
+ "POST",
34302
+ `/api/deliverable-groups/${encodeURIComponent(trackId)}/work-orders`,
34303
+ body
34304
+ );
34305
+ if (res === void 0) throw new Error(`Track ${trackId} not found`);
34306
+ const parsed = WorkOrderApiSchema.safeParse(res.workOrder);
34307
+ if (!parsed.success) throw new Error(`Unexpected WorkOrder shape from API: ${parsed.error.message}`);
34308
+ return parsed.data;
34309
+ },
34310
+ getWorkOrder: async (workOrderId) => {
34311
+ const res = await request("GET", `/api/work-orders/${encodeURIComponent(workOrderId)}`);
34312
+ if (res === void 0) return void 0;
34313
+ const parsed = WorkOrderApiSchema.safeParse(res.workOrder);
34314
+ if (!parsed.success) throw new Error(`Unexpected WorkOrder shape from API: ${parsed.error.message}`);
34315
+ return parsed.data;
34316
+ },
34317
+ updateWorkOrder: async (workOrderId, updates) => {
34318
+ const res = await request(
34319
+ "PATCH",
34320
+ `/api/work-orders/${encodeURIComponent(workOrderId)}`,
34321
+ updates
34322
+ );
34323
+ if (res === void 0) return void 0;
34324
+ const parsed = WorkOrderApiSchema.safeParse(res.workOrder);
34325
+ if (!parsed.success) throw new Error(`Unexpected WorkOrder shape from API: ${parsed.error.message}`);
34326
+ return parsed.data;
34327
+ },
34328
+ listTrackWorkOrders: async (trackId) => {
34329
+ const res = await request("GET", `/api/deliverable-groups/${encodeURIComponent(trackId)}/work-orders`);
34330
+ if (res === void 0) throw new Error(`Track ${trackId} not found`);
34331
+ const parsed = WorkOrdersResponseSchema.safeParse(res);
34332
+ if (!parsed.success) throw new Error(`Unexpected work orders list shape from API: ${parsed.error.message}`);
34333
+ return parsed.data.workOrders;
34334
+ },
34335
+ listWorkRequestWorkOrders: async (workRequestId) => {
34336
+ const res = await request("GET", `/api/work-requests/${encodeURIComponent(workRequestId)}/work-orders`);
34337
+ if (res === void 0) throw new Error(`Work request ${workRequestId} not found`);
34338
+ const parsed = WorkOrdersResponseSchema.safeParse(res);
34339
+ if (!parsed.success) throw new Error(`Unexpected work orders list shape from API: ${parsed.error.message}`);
34340
+ return parsed.data.workOrders;
34341
+ },
34342
+ moveWorkOrder: async (workOrderId, newTrackId) => {
34343
+ const res = await request(
34344
+ "POST",
34345
+ `/api/work-orders/${encodeURIComponent(workOrderId)}/move`,
34346
+ { newTrackId }
34347
+ );
34348
+ if (res === void 0) return void 0;
34349
+ const parsed = WorkOrderApiSchema.safeParse(res.workOrder);
34350
+ if (!parsed.success) throw new Error(`Unexpected WorkOrder shape from API: ${parsed.error.message}`);
34351
+ return parsed.data;
34352
+ },
34353
+ transitionWorkOrderStatus: async (workOrderId, toStatus, reason) => {
34354
+ const res = await request(
34355
+ "POST",
34356
+ `/api/work-orders/${encodeURIComponent(workOrderId)}/status`,
34357
+ { status: toStatus, ...reason !== void 0 && { reason } }
34358
+ );
34359
+ if (res === void 0) return void 0;
34360
+ const parsed = WorkOrderApiSchema.safeParse(res.workOrder);
34361
+ if (!parsed.success) throw new Error(`Unexpected WorkOrder shape from API: ${parsed.error.message}`);
34362
+ return parsed.data;
34363
+ },
33769
34364
  // Accounts (B-242 / org-scoped paying client entity that owns Movements)
33770
34365
  createAccount: async (input) => {
33771
34366
  const { orgId, name, description, createdBy, parentAccountId, accountType, accountCode } = input;
@@ -34317,7 +34912,7 @@ function loadConfig() {
34317
34912
  };
34318
34913
  }
34319
34914
  async function main() {
34320
- console.error(`[harmonica-mcp] v${"3.8.6"} starting\u2026`);
34915
+ console.error(`[harmonica-mcp] v${"3.9.1"} starting\u2026`);
34321
34916
  const config2 = loadConfig();
34322
34917
  const client = createHttpClient({
34323
34918
  apiBaseUrl: config2.apiBaseUrl,