@codazen/harmonica-mcp 3.6.0 → 3.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +344 -340
  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, projectId, orgId) {
21546
- const project = await client.getSystem(projectId);
21545
+ async function assertProjectInOrg(client, systemId, orgId) {
21546
+ const project = await client.getSystem(systemId);
21547
21547
  if (!project) {
21548
- throw new Error(`Project not found: "${projectId}"`);
21548
+ throw new Error(`Project not found: "${systemId}"`);
21549
21549
  }
21550
21550
  if (project.orgId !== orgId) {
21551
21551
  throw new Error(
21552
- `Project "${projectId}" does not belong to the configured organization. Access denied.`
21552
+ `Project "${systemId}" does not belong to the configured organization. Access denied.`
21553
21553
  );
21554
21554
  }
21555
21555
  }
21556
- async function assertRevisionInProject(client, revisionId, projectId) {
21556
+ async function assertRevisionInProject(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
- if (revision.projectId !== projectId) {
21561
+ if (revision.systemId !== systemId) {
21562
21562
  throw new Error(
21563
- `Revision "${revisionId}" does not belong to project "${projectId}". Access denied.`
21563
+ `Revision "${revisionId}" does not belong to project "${systemId}". Access denied.`
21564
21564
  );
21565
21565
  }
21566
21566
  }
@@ -21575,14 +21575,14 @@ async function assertBeatInOrg(client, beatId, orgId) {
21575
21575
  );
21576
21576
  }
21577
21577
  }
21578
- async function fetchProjectInOrg(client, projectId, orgId) {
21579
- const project = await client.getSystem(projectId);
21578
+ async function fetchProjectInOrg(client, systemId, orgId) {
21579
+ const project = await client.getSystem(systemId);
21580
21580
  if (!project) {
21581
- throw new Error(`Project not found: "${projectId}"`);
21581
+ throw new Error(`Project not found: "${systemId}"`);
21582
21582
  }
21583
21583
  if (project.orgId !== orgId) {
21584
21584
  throw new Error(
21585
- `Project "${projectId}" does not belong to the configured organization. Access denied.`
21585
+ `Project "${systemId}" does not belong to the configured organization. Access denied.`
21586
21586
  );
21587
21587
  }
21588
21588
  return project;
@@ -21974,13 +21974,13 @@ function registerActivityTools(server, ctx, client) {
21974
21974
  "list_activities",
21975
21975
  "List project activity audit trail \u2014 shows beat changes, revision transitions, note operations, merges, splits, and reassignments. Sorted newest-first.",
21976
21976
  {
21977
- projectId: external_exports.string().describe("The project ID"),
21977
+ systemId: external_exports.string().describe("The project ID"),
21978
21978
  limit: external_exports.coerce.number().min(1).max(100).optional().describe("Max activities to return (default 30)"),
21979
21979
  importance: external_exports.enum(IMPORTANCE_VALUES).optional().describe("Filter by minimum importance level")
21980
21980
  },
21981
- async ({ projectId, limit, importance }) => {
21982
- await assertProjectInOrg(client, projectId, ctx.orgId);
21983
- const { entries, hasMore } = await client.listSystemActivities(projectId, {
21981
+ async ({ systemId, limit, importance }) => {
21982
+ await assertProjectInOrg(client, systemId, ctx.orgId);
21983
+ const { entries, hasMore } = await client.listSystemActivities(systemId, {
21984
21984
  limit: limit ?? 30,
21985
21985
  minImportance: importance
21986
21986
  });
@@ -22005,14 +22005,14 @@ function registerAnalysisTools(server, ctx, client) {
22005
22005
  "cluster_entities",
22006
22006
  "Cluster any embeddable entity type (notes, beats, revisions) by vector similarity using DBSCAN. Returns groups of related entities \u2014 useful for understanding subsystem boundaries, finding duplicates, and planning batched work.",
22007
22007
  {
22008
- projectId: external_exports.string().describe("The project ID"),
22008
+ systemId: external_exports.string().describe("The project ID"),
22009
22009
  entityType: ENTITY_TYPE_SCHEMA.describe("Entity type to cluster. Default: revision"),
22010
22010
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.75).describe("DBSCAN similarity threshold (0-1). Higher = tighter clusters. Default 0.75"),
22011
22011
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum cluster size. Default 3")
22012
22012
  },
22013
- async ({ projectId, entityType, threshold, minClusterSize }) => {
22014
- await assertProjectInOrg(client, projectId, ctx.orgId);
22015
- const result = await client.clusterEntities(projectId, entityType, { threshold, minClusterSize });
22013
+ async ({ systemId, entityType, threshold, minClusterSize }) => {
22014
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22015
+ const result = await client.clusterEntities(systemId, entityType, { threshold, minClusterSize });
22016
22016
  if (result.clusters.length === 0) {
22017
22017
  return {
22018
22018
  content: [{ type: "text", text: `No clusters found (${result.totalAnalyzed} ${entityType}s analyzed, ${result.noise.length} unclustered).` }]
@@ -22036,15 +22036,15 @@ Analyzed: ${result.totalAnalyzed} | Clusters: ${result.clusters.length} | Noise:
22036
22036
  "run_clustered_analysis",
22037
22037
  "Run batched analysis on clustered project entities. Groups entities by vector similarity, then runs a specified analysis type (docs, completeness, tech_debt, security) per cluster. Returns aggregated results.",
22038
22038
  {
22039
- projectId: external_exports.string().describe("The project ID"),
22039
+ systemId: external_exports.string().describe("The project ID"),
22040
22040
  analysisType: ANALYSIS_TYPE_SCHEMA.describe("Type of analysis to run"),
22041
22041
  entityType: ENTITY_TYPE_SCHEMA.describe("Entity type to cluster. Default: revision"),
22042
22042
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("DBSCAN similarity threshold (0-1). Default 0.7"),
22043
22043
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum cluster size. Default 2")
22044
22044
  },
22045
- async ({ projectId, analysisType, entityType, threshold, minClusterSize }) => {
22046
- await assertProjectInOrg(client, projectId, ctx.orgId);
22047
- const result = await client.runClusteredAnalysis(projectId, analysisType, {
22045
+ async ({ systemId, analysisType, entityType, threshold, minClusterSize }) => {
22046
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22047
+ const result = await client.runClusteredAnalysis(systemId, analysisType, {
22048
22048
  entityType,
22049
22049
  threshold,
22050
22050
  minClusterSize
@@ -22073,13 +22073,13 @@ function registerAuditIntegrityTools(server, ctx, client) {
22073
22073
  "verify_audit_integrity",
22074
22074
  "Verify the cryptographic integrity of audit trail records for a project. Checks SHA-256 hashes on Activity and PromptLog records to detect any post-creation tampering.",
22075
22075
  {
22076
- projectId: external_exports.string().describe("The project ID to verify"),
22076
+ systemId: external_exports.string().describe("The project ID to verify"),
22077
22077
  limit: external_exports.number().optional().describe("Max records to verify (default 100)")
22078
22078
  },
22079
- async ({ projectId, limit }) => {
22079
+ async ({ systemId, limit }) => {
22080
22080
  try {
22081
- await assertProjectInOrg(client, projectId, ctx.orgId);
22082
- const report = await client.verifyAuditIntegrity(projectId, { limit });
22081
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22082
+ const report = await client.verifyAuditIntegrity(systemId, { limit });
22083
22083
  const lines = [
22084
22084
  `## Audit Integrity Report`,
22085
22085
  ``,
@@ -22287,16 +22287,16 @@ Rationale: ${rationale}`;
22287
22287
  "bulk_reassign_revisions",
22288
22288
  "Reassign multiple revisions to new home Beats in a single call. Each item names a revision and its target Beat (different items can target different Beats \u2014 e.g., redistributing one Beat's revisions across several after decomposition). Each reassignment archives the original revision and creates a copy on the target Beat, identical to reassign_revision. Returns per-item success/failure so partial failures don't abort the batch. Use when migrating many revisions at once after a Beat split.",
22289
22289
  {
22290
- projectId: external_exports.string().describe("The project ID (for access control \u2014 all revisions and target beats must belong to this project)"),
22290
+ systemId: external_exports.string().describe("The project ID (for access control \u2014 all revisions and target beats must belong to this project)"),
22291
22291
  items: external_exports.array(external_exports.object({
22292
22292
  revisionId: external_exports.string().describe("The revision ID to reassign"),
22293
22293
  toBeatId: external_exports.string().describe("The target beat ID for this specific revision")
22294
22294
  })).min(1).max(100).describe("Reassignment items (max 100 per call)"),
22295
22295
  reason: external_exports.string().describe("Why these revisions are being reassigned (applied to all items)")
22296
22296
  },
22297
- async ({ projectId, items, reason }) => {
22297
+ async ({ systemId, items, reason }) => {
22298
22298
  try {
22299
- await assertProjectInOrg(client, projectId, ctx.orgId);
22299
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22300
22300
  const result = await client.bulkReassignRevisions(items, reason, ctx.user.userId);
22301
22301
  const lines = [
22302
22302
  `# Bulk reassignment complete: ${result.reassigned.length} succeeded, ${result.failed.length} failed`,
@@ -22328,14 +22328,14 @@ Rationale: ${rationale}`;
22328
22328
  "reassign_revision",
22329
22329
  "Reassign a misattributed revision from one Beat to another. Creates a copy on the target Beat and archives the original. Logs a revision_reassigned activity for audit trail.",
22330
22330
  {
22331
- projectId: external_exports.string().describe("The project ID (for access control)"),
22331
+ systemId: external_exports.string().describe("The project ID (for access control)"),
22332
22332
  revisionId: external_exports.string().describe("The revision ID to reassign"),
22333
22333
  toBeatId: external_exports.string().describe("The target beat ID"),
22334
22334
  reason: external_exports.string().describe("Why this revision is being reassigned")
22335
22335
  },
22336
- async ({ projectId, revisionId, toBeatId, reason }) => {
22336
+ async ({ systemId, revisionId, toBeatId, reason }) => {
22337
22337
  try {
22338
- await assertProjectInOrg(client, projectId, ctx.orgId);
22338
+ await assertProjectInOrg(client, systemId, ctx.orgId);
22339
22339
  const result = await client.reassignRevision(revisionId, toBeatId, reason, ctx.user.userId);
22340
22340
  const text = [
22341
22341
  `Revision reassigned successfully.`,
@@ -22374,7 +22374,7 @@ function registerBeatConvergenceTools(server, ctx, client) {
22374
22374
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
22375
22375
  async ({ beatId }) => {
22376
22376
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
22377
- const report = await client.getBeatConvergence(beat.projectId, beatId);
22377
+ const report = await client.getBeatConvergence(beat.systemId, beatId);
22378
22378
  if (!report) {
22379
22379
  return { content: [{ type: "text", text: `No convergence data for beat "${beatId}".` }], isError: true };
22380
22380
  }
@@ -22434,7 +22434,7 @@ function registerBeatPlanningTools(server, ctx, client) {
22434
22434
  "Plan the implementation of a Beat Revision via a conversational turn. Uses the revision description and Beat notes as context. Use this during the planning or building stage to refine scope, explore implementation details, or assess readiness.",
22435
22435
  {
22436
22436
  revisionId: external_exports.string().describe("The revision ID to plan"),
22437
- projectId: external_exports.string().describe("The project ID"),
22437
+ systemId: external_exports.string().describe("The project ID"),
22438
22438
  message: external_exports.string().describe(
22439
22439
  'Your planning message \u2014 e.g. "Review the revision scope", "What should we consider for the auth flow?", or "Does this cover the acceptance criteria?"'
22440
22440
  ),
@@ -22443,7 +22443,7 @@ function registerBeatPlanningTools(server, ctx, client) {
22443
22443
  content: external_exports.string()
22444
22444
  })).optional().describe("Previous conversation messages for context continuity")
22445
22445
  },
22446
- async ({ revisionId, projectId, message, conversationHistory }) => {
22446
+ async ({ revisionId, systemId, message, conversationHistory }) => {
22447
22447
  try {
22448
22448
  const revision = await client.getRevision(revisionId);
22449
22449
  if (!revision) {
@@ -22454,7 +22454,7 @@ function registerBeatPlanningTools(server, ctx, client) {
22454
22454
  ...conversationHistory ?? [],
22455
22455
  { role: "user", content: message }
22456
22456
  ];
22457
- const result = await client.planRevision(revisionId, projectId, messages);
22457
+ const result = await client.planRevision(revisionId, systemId, messages);
22458
22458
  return {
22459
22459
  content: [{
22460
22460
  type: "text",
@@ -22536,13 +22536,13 @@ ${d.rationale}${d.suggestions?.length ? `
22536
22536
  function registerBeatQualityTools(server, ctx, client) {
22537
22537
  const schema = {
22538
22538
  beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)"),
22539
- projectId: external_exports.string().describe("The project ID"),
22539
+ systemId: external_exports.string().describe("The project ID"),
22540
22540
  wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
22541
22541
  };
22542
- const handler = async ({ beatId, projectId, wait }) => {
22542
+ const handler = async ({ beatId, systemId, wait }) => {
22543
22543
  try {
22544
22544
  await assertBeatInOrg(client, beatId, ctx.orgId);
22545
- const { taskId } = await client.checkBeatQuality(projectId, beatId);
22545
+ const { taskId } = await client.checkBeatQuality(systemId, beatId);
22546
22546
  if (!wait) {
22547
22547
  return {
22548
22548
  content: [{
@@ -22585,12 +22585,12 @@ function registerBeatReframeTools(server, ctx, client) {
22585
22585
  "Reframe a Beat's title and description to meet business-outcome quality rules \u2014 outcome-first, plain English, non-technical audience. Returns a proposed rewrite for review.",
22586
22586
  {
22587
22587
  beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)"),
22588
- projectId: external_exports.string().describe("The project ID")
22588
+ systemId: external_exports.string().describe("The project ID")
22589
22589
  },
22590
- async ({ beatId, projectId }) => {
22590
+ async ({ beatId, systemId }) => {
22591
22591
  try {
22592
22592
  await assertBeatInOrg(client, beatId, ctx.orgId);
22593
- const result = await client.reframeBeat(projectId, beatId);
22593
+ const result = await client.reframeBeat(systemId, beatId);
22594
22594
  const text = formatReframeResult(beatId, result);
22595
22595
  return { content: [{ type: "text", text }] };
22596
22596
  } catch (err) {
@@ -22673,7 +22673,7 @@ function formatProjectSummaryTable(projects) {
22673
22673
  const header = "| Project ID | Title | Status |\n|------------|-------|--------|";
22674
22674
  const rows = projects.map((p) => {
22675
22675
  const status = p.status.replace("_", " ");
22676
- return `| ${p.projectId} | ${p.title} | ${status} |`;
22676
+ return `| ${p.systemId} | ${p.title} | ${status} |`;
22677
22677
  });
22678
22678
  return [header, ...rows].join("\n");
22679
22679
  }
@@ -22702,7 +22702,7 @@ function formatDeliveryPolicy(policy, unresolvedReason) {
22702
22702
  function formatProjectContext(project, notes, orgCoda) {
22703
22703
  const sections = [];
22704
22704
  sections.push(`# System: ${project.title}`);
22705
- sections.push(`**ID:** ${project.projectId}`);
22705
+ sections.push(`**ID:** ${project.systemId}`);
22706
22706
  sections.push(`**Status:** ${project.status.replace("_", " ")}`);
22707
22707
  if (project.repoOwner && project.repoName) {
22708
22708
  const branch = project.repoDefaultBranch ?? "main";
@@ -22989,7 +22989,7 @@ function formatSessionDetail(session) {
22989
22989
  "",
22990
22990
  `**Type:** ${type}`,
22991
22991
  `**Status:** ${status}`,
22992
- `**Project:** ${session.projectId}`,
22992
+ `**Project:** ${session.systemId}`,
22993
22993
  `**Actor:** ${session.actor.agentPersona}${session.actor.agentRole ? ` (${session.actor.agentRole})` : ""}`
22994
22994
  ];
22995
22995
  if (session.actor.initiator) {
@@ -23097,14 +23097,14 @@ function registerBeatTools(server, ctx, client) {
23097
23097
  "list_beats",
23098
23098
  "List beats in a project. Use offset/limit to paginate large result sets.",
23099
23099
  {
23100
- projectId: external_exports.string().describe("The project ID"),
23100
+ systemId: external_exports.string().describe("The project ID"),
23101
23101
  offset: external_exports.coerce.number().int().min(0).default(0).describe("Number of beats to skip (for pagination)"),
23102
23102
  limit: external_exports.coerce.number().int().min(1).max(500).default(100).describe("Max beats to return (default 100, max 500)"),
23103
23103
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived and deprecated beats (excluded by default)")
23104
23104
  },
23105
- async ({ projectId, offset = 0, limit = 100, includeArchived }) => {
23106
- await assertProjectInOrg(client, projectId, ctx.orgId);
23107
- let beats = await client.listSystemBeats(projectId);
23105
+ async ({ systemId, offset = 0, limit = 100, includeArchived }) => {
23106
+ await assertProjectInOrg(client, systemId, ctx.orgId);
23107
+ let beats = await client.listSystemBeats(systemId);
23108
23108
  if (!includeArchived) {
23109
23109
  beats = beats.filter((b) => b.beatStatus !== "archived");
23110
23110
  }
@@ -23139,11 +23139,11 @@ function registerBeatTools(server, ctx, client) {
23139
23139
  "Get the full decision lineage timeline for a beat (revisions, status changes, edits, proposals)",
23140
23140
  {
23141
23141
  beatId: external_exports.string().describe("The beat ID"),
23142
- projectId: external_exports.string().optional().describe("Project ID (auto-resolved from beat if omitted)")
23142
+ systemId: external_exports.string().optional().describe("Project ID (auto-resolved from beat if omitted)")
23143
23143
  },
23144
- async ({ beatId, projectId }) => {
23144
+ async ({ beatId, systemId }) => {
23145
23145
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
23146
- const resolvedProjectId = projectId ?? beat.projectId;
23146
+ const resolvedProjectId = systemId ?? beat.systemId;
23147
23147
  const timeline = await client.getUnifiedBeatTimeline(resolvedProjectId, beatId);
23148
23148
  if (timeline.length === 0) {
23149
23149
  return { content: [{ type: "text", text: `No history for beat "${beatId}".` }] };
@@ -23156,7 +23156,7 @@ function registerBeatTools(server, ctx, client) {
23156
23156
  "create_beat",
23157
23157
  "Create a new beat in a project. Title must be outcome-first (what changes for the user, not what we build). Description is the Beat Coda \u2014 second-person resolved expression of the capability (1-3 sentences). Avoid jargon and user-story format. Read harmonica://guidelines/beat-quality for full rules. Auto-generates beat ID if not provided.",
23158
23158
  {
23159
- projectId: external_exports.string().describe("The project ID"),
23159
+ systemId: external_exports.string().describe("The project ID"),
23160
23160
  title: external_exports.string().describe('Beat title \u2014 outcome-first, e.g. "At-risk shipments screen used daily by Ops"'),
23161
23161
  description: external_exports.string().optional().describe("Beat Coda \u2014 second-person resolved expression of what the capability looks like when complete (1-3 sentences)"),
23162
23162
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
@@ -23167,13 +23167,13 @@ function registerBeatTools(server, ctx, client) {
23167
23167
  agentAssignee: agentAssigneeSchema.optional().describe('Agent assignee (e.g. { agentId: "maya", name: "Maya" })'),
23168
23168
  branch: external_exports.string().optional().describe('Git branch for code assessment (falls back to Project.repoDefaultBranch \u2192 "main")')
23169
23169
  },
23170
- async ({ projectId, title, description, tags, priority, estimatedEffort, beatId, humanAssignee, agentAssignee, branch }) => {
23170
+ async ({ systemId, title, description, tags, priority, estimatedEffort, beatId, humanAssignee, agentAssignee, branch }) => {
23171
23171
  try {
23172
- await fetchProjectInOrg(client, projectId, ctx.orgId);
23173
- const resolvedBeatId = beatId ?? await client.getNextBeatId(projectId);
23172
+ await fetchProjectInOrg(client, systemId, ctx.orgId);
23173
+ const resolvedBeatId = beatId ?? await client.getNextBeatId(systemId);
23174
23174
  const beat = await client.createBeat({
23175
23175
  beatId: resolvedBeatId,
23176
- projectId,
23176
+ systemId,
23177
23177
  orgId: ctx.orgId,
23178
23178
  title,
23179
23179
  description,
@@ -23190,7 +23190,7 @@ function registerBeatTools(server, ctx, client) {
23190
23190
  });
23191
23191
  if (!beat) return { content: [{ type: "text", text: "Failed to create beat: store returned undefined" }], isError: true };
23192
23192
  await logBeatActivity(client, {
23193
- projectId,
23193
+ systemId,
23194
23194
  action: "beat_created",
23195
23195
  beatIds: [resolvedBeatId],
23196
23196
  beatTitles: [title],
@@ -23199,7 +23199,7 @@ function registerBeatTools(server, ctx, client) {
23199
23199
  });
23200
23200
  let similarityWarning = "";
23201
23201
  try {
23202
- const similar = await client.findSimilarBeats(beat.beatId, projectId, { threshold: 0.7, limit: 5 });
23202
+ const similar = await client.findSimilarBeats(beat.beatId, systemId, { threshold: 0.7, limit: 5 });
23203
23203
  if (similar.length > 0) {
23204
23204
  const rows = similar.map((r) => `- **${r.entityId}** (score: ${r.score.toFixed(2)}): ${r.snippet.replace(/[\n\r]/g, " ")}`).join("\n");
23205
23205
  similarityWarning = `
@@ -23257,7 +23257,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23257
23257
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
23258
23258
  }
23259
23259
  await logBeatActivity(client, {
23260
- projectId: beat.projectId,
23260
+ systemId: beat.systemId,
23261
23261
  action: "beat_updated",
23262
23262
  beatIds: [beatId],
23263
23263
  beatTitles: [updated.title],
@@ -23279,15 +23279,15 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23279
23279
  "list_revisions",
23280
23280
  "List revisions for a project, optionally filtered by binary status (`active` or `archived`). Archived revisions are excluded by default. Use beatId to scope results to a single beat.",
23281
23281
  {
23282
- projectId: external_exports.string().describe("The project ID"),
23282
+ systemId: external_exports.string().describe("The project ID"),
23283
23283
  beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project revisions)"),
23284
23284
  status: external_exports.enum(["active", "archived"]).optional().describe("Filter by binary Revision status (default: all non-archived)"),
23285
23285
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived revisions (excluded by default)")
23286
23286
  },
23287
- async ({ projectId, beatId, status, includeArchived }) => {
23287
+ async ({ systemId, beatId, status, includeArchived }) => {
23288
23288
  try {
23289
- await assertProjectInOrg(client, projectId, ctx.orgId);
23290
- const revisions = beatId ? await client.listBeatRevisions(projectId, beatId, { status, includeArchived }) : await client.listSystemRevisions(projectId, { status, includeArchived });
23289
+ await assertProjectInOrg(client, systemId, ctx.orgId);
23290
+ const revisions = beatId ? await client.listBeatRevisions(systemId, beatId, { status, includeArchived }) : await client.listSystemRevisions(systemId, { status, includeArchived });
23291
23291
  return { content: [{ type: "text", text: formatRevisionSummaryTable(revisions) }] };
23292
23292
  } catch (err) {
23293
23293
  const message = err instanceof Error ? err.message : String(err);
@@ -23327,7 +23327,7 @@ async function logBeatActivity(client, params) {
23327
23327
  try {
23328
23328
  await client.createActivity({
23329
23329
  activityId: (0, import_crypto2.randomUUID)(),
23330
- projectId: params.projectId,
23330
+ systemId: params.systemId,
23331
23331
  action: params.action,
23332
23332
  importance: params.importance ?? "minor",
23333
23333
  affectedBeatIds: params.beatIds,
@@ -23399,13 +23399,13 @@ function registerBeatVersionTools(server, ctx, client) {
23399
23399
  "list_beat_versions",
23400
23400
  "List Beat Versions for a Beat or Project. Beat Versions are the shippable planning increments that advance a Beat toward its goal. Use beatId to scope to a specific Beat, or omit for all Beat Versions in the project. Status is binary (active | archived); by default excludes archived.",
23401
23401
  {
23402
- projectId: external_exports.string().describe("The project ID"),
23402
+ systemId: external_exports.string().describe("The project ID"),
23403
23403
  beatId: external_exports.string().optional().describe("Filter to a specific Beat (omit for all Beat Versions in project)"),
23404
23404
  status: external_exports.enum(BEAT_VERSION_STATUSES).optional().describe("Filter to a specific status (active | archived)"),
23405
23405
  includeTerminal: external_exports.boolean().optional().describe("Include archived Beat Versions. Default: false.")
23406
23406
  },
23407
- async ({ projectId, beatId, status, includeTerminal }) => {
23408
- const beatVersions = await client.listBeatVersions(projectId, {
23407
+ async ({ systemId, beatId, status, includeTerminal }) => {
23408
+ const beatVersions = await client.listBeatVersions(systemId, {
23409
23409
  beatId,
23410
23410
  status,
23411
23411
  includeTerminal
@@ -23476,7 +23476,7 @@ function registerBeatVersionTools(server, ctx, client) {
23476
23476
  return { content: [{ type: "text", text: `Beat not found: "${beatId}"` }], isError: true };
23477
23477
  }
23478
23478
  await assertBeatInOrg(client, beatId, ctx.orgId);
23479
- const beatVersion = await client.createBeatVersion(beat.projectId, beatId, {
23479
+ const beatVersion = await client.createBeatVersion(beat.systemId, beatId, {
23480
23480
  title,
23481
23481
  description,
23482
23482
  scope,
@@ -23831,13 +23831,13 @@ function registerCheckTools(server, ctx, client) {
23831
23831
  "list_checks",
23832
23832
  "List persisted checks for a project, beat, beat version, or revision. Shows score trends over time. Filter by check type.",
23833
23833
  {
23834
- projectId: external_exports.string().describe("The project ID"),
23834
+ systemId: external_exports.string().describe("The project ID"),
23835
23835
  beatId: external_exports.string().optional().describe("List checks for this specific beat"),
23836
23836
  beatVersionId: external_exports.string().optional().describe("List checks for this specific beat version"),
23837
23837
  revisionId: external_exports.string().optional().describe("List checks for this specific revision"),
23838
23838
  checkType: external_exports.enum(["beat_coda_quality", "beat_quality", "beat_version_quality", "plan_quality", "build_quality", "pii_scan", "portfolio_coherence"]).optional().describe("Filter by check type. beat_quality is the DEPRECATED alias of beat_coda_quality. plan_quality is the DEPRECATED alias of beat_version_quality. pii_scan is RETIRED from the taxonomy (PII detection moved to the Scan family, N-4E09-6809) \u2014 it is still accepted here so historical records stay readable, but no new pii_scan checks are written.")
23839
23839
  },
23840
- async ({ projectId, beatId, beatVersionId, revisionId, checkType }) => {
23840
+ async ({ systemId, beatId, beatVersionId, revisionId, checkType }) => {
23841
23841
  try {
23842
23842
  const filters = [
23843
23843
  revisionId ? "revisionId" : null,
@@ -23916,18 +23916,18 @@ function registerCheckTools(server, ctx, client) {
23916
23916
  let checks;
23917
23917
  let scope;
23918
23918
  if (revisionId) {
23919
- checks = await client.listRevisionChecks(revisionId, projectId, checkType);
23919
+ checks = await client.listRevisionChecks(revisionId, systemId, checkType);
23920
23920
  scope = `revision ${revisionId}`;
23921
23921
  } else if (beatVersionId) {
23922
- checks = await client.listBeatVersionChecks(beatVersionId, projectId, checkType);
23922
+ checks = await client.listBeatVersionChecks(beatVersionId, systemId, checkType);
23923
23923
  scope = `beat_version ${beatVersionId}`;
23924
23924
  } else if (beatId) {
23925
- checks = await client.listBeatChecks(beatId, projectId, checkType);
23925
+ checks = await client.listBeatChecks(beatId, systemId, checkType);
23926
23926
  scope = `beat ${beatId}`;
23927
23927
  } else {
23928
- const result = await client.listSystemChecks(projectId, checkType, { limit: 200 });
23928
+ const result = await client.listSystemChecks(systemId, checkType, { limit: 200 });
23929
23929
  checks = result.checks;
23930
- scope = `project ${projectId}`;
23930
+ scope = `project ${systemId}`;
23931
23931
  }
23932
23932
  if (checks.length === 0) {
23933
23933
  return { content: [{ type: "text", text: `No checks found for ${scope}.` }] };
@@ -23944,15 +23944,15 @@ function registerCheckTools(server, ctx, client) {
23944
23944
  "list_latest_checks",
23945
23945
  "List the latest check per target (Beat, Beat Version, Revision) across a whole project in one call \u2014 the most recent score of each type for each entity. Use this for an at-a-glance quality snapshot of every capability; use list_checks when you need the full history of one target.",
23946
23946
  {
23947
- projectId: external_exports.string().describe("The project ID")
23947
+ systemId: external_exports.string().describe("The project ID")
23948
23948
  },
23949
- async ({ projectId }) => {
23949
+ async ({ systemId }) => {
23950
23950
  try {
23951
- const checks = await client.listLatestSystemChecks(projectId);
23951
+ const checks = await client.listLatestSystemChecks(systemId);
23952
23952
  if (checks.length === 0) {
23953
- return { content: [{ type: "text", text: `No checks found for project ${projectId}.` }] };
23953
+ return { content: [{ type: "text", text: `No checks found for project ${systemId}.` }] };
23954
23954
  }
23955
- const text = formatCheckList(checks, `latest per target \xB7 project ${projectId}`);
23955
+ const text = formatCheckList(checks, `latest per target \xB7 project ${systemId}`);
23956
23956
  return { content: [{ type: "text", text }] };
23957
23957
  } catch (err) {
23958
23958
  const message = err instanceof Error ? err.message : String(err);
@@ -23966,19 +23966,19 @@ function registerCheckTools(server, ctx, client) {
23966
23966
  {
23967
23967
  checkType: external_exports.enum(["beat_coda_quality", "beat_quality", "beat_version_quality", "plan_quality", "build_quality"]).describe("Type of check to run. beat_quality is the deprecated alias of beat_coda_quality. plan_quality is the deprecated alias of beat_version_quality."),
23968
23968
  targetId: external_exports.string().describe("The target entity ID \u2014 a beatId for beat_coda_quality, or a Beat Version ID (bv-*) for beat_version_quality and build_quality"),
23969
- projectId: external_exports.string().describe("The project ID"),
23969
+ systemId: external_exports.string().describe("The project ID"),
23970
23970
  branch: external_exports.string().optional().describe("Override the git branch for code checks (default: project default branch)"),
23971
23971
  localPath: external_exports.string().optional().describe("Absolute path to an already-cloned local copy of the repo \u2014 skips git clone entirely (useful for large repos or offline use)")
23972
23972
  },
23973
- async ({ checkType, targetId, projectId, branch, localPath }) => {
23973
+ async ({ checkType, targetId, systemId, branch, localPath }) => {
23974
23974
  try {
23975
- await assertProjectInOrg(client, projectId, ctx.orgId);
23975
+ await assertProjectInOrg(client, systemId, ctx.orgId);
23976
23976
  const targetError = checkType === "build_quality" ? buildQualityTargetError(targetId) : checkType === "beat_version_quality" || checkType === "plan_quality" ? beatVersionQualityTargetError(targetId) : null;
23977
23977
  if (targetError) {
23978
23978
  return { content: [{ type: "text", text: targetError }], isError: true };
23979
23979
  }
23980
23980
  const opts = branch || localPath ? { branch, localPath } : void 0;
23981
- const task = await client.runCheck(projectId, checkType, targetId, opts);
23981
+ const task = await client.runCheck(systemId, checkType, targetId, opts);
23982
23982
  const text = [
23983
23983
  `Check enqueued.`,
23984
23984
  `**Task ID:** ${task.taskId}`,
@@ -24070,7 +24070,7 @@ function registerClaimTools(server, ctx, client) {
24070
24070
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
24071
24071
  }
24072
24072
  await assertBeatInOrg(client, revision.beatId, ctx.orgId);
24073
- const result = await client.claimRevision(revision.projectId, revisionId);
24073
+ const result = await client.claimRevision(revision.systemId, revisionId);
24074
24074
  if (!result.success) {
24075
24075
  return { content: [{ type: "text", text: `Failed to claim revision (${result.error.code}): ${result.error.message}` }], isError: true };
24076
24076
  }
@@ -24104,7 +24104,7 @@ function registerClaimTools(server, ctx, client) {
24104
24104
  return { content: [{ type: "text", text: `Revision not found: "${revisionId}"` }], isError: true };
24105
24105
  }
24106
24106
  await assertBeatInOrg(client, revision.beatId, ctx.orgId);
24107
- const result = await client.releaseRevisionClaim(revision.projectId, revisionId, reason);
24107
+ const result = await client.releaseRevisionClaim(revision.systemId, revisionId, reason);
24108
24108
  if (!result.success) {
24109
24109
  return { content: [{ type: "text", text: `Failed to release claim (${result.error.code}): ${result.error.message}` }], isError: true };
24110
24110
  }
@@ -24204,11 +24204,11 @@ ${g.acceptanceCriteria}` : null,
24204
24204
  deliverables: external_exports.string().max(2e4).optional().describe("The Track's deliverables as Markdown bullet points (parallel to acceptanceCriteria)"),
24205
24205
  acceptanceCriteria: external_exports.string().max(2e4).optional().describe("The Track's acceptance criteria (Markdown), parallel to its deliverables"),
24206
24206
  vuRate: external_exports.number().nonnegative().describe("Value-Unit rate (billing rate for this engagement)"),
24207
- projectId: external_exports.string().optional().describe("Optional project to scope this Track to"),
24207
+ systemId: external_exports.string().optional().describe("Optional project to scope this Track to"),
24208
24208
  movementId: external_exports.string().optional().describe("Optional Movement (client SOW) to scope this Track to"),
24209
24209
  parentGroupId: external_exports.string().optional().describe("Optional parent Track ID for nested grouping (Phase 0 scaffolding)")
24210
24210
  };
24211
- const createTrackHandler = async ({ teamspaceId, title, description, deliverables, acceptanceCriteria, vuRate, projectId, movementId, parentGroupId }) => {
24211
+ const createTrackHandler = async ({ teamspaceId, title, description, deliverables, acceptanceCriteria, vuRate, systemId, movementId, parentGroupId }) => {
24212
24212
  await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
24213
24213
  if (movementId !== void 0) {
24214
24214
  await assertMovementInOrg(client, movementId, ctx.orgId);
@@ -24224,7 +24224,7 @@ ${g.acceptanceCriteria}` : null,
24224
24224
  ...deliverables !== void 0 && { deliverables },
24225
24225
  ...acceptanceCriteria !== void 0 && { acceptanceCriteria },
24226
24226
  vuRate,
24227
- ...projectId !== void 0 && { projectId },
24227
+ ...systemId !== void 0 && { systemId },
24228
24228
  ...movementId !== void 0 && { movementId },
24229
24229
  ...parentGroupId !== void 0 && { parentGroupId },
24230
24230
  createdBy: ctx.user.userId
@@ -24447,7 +24447,7 @@ function registerDownbeatTools(server, ctx, client) {
24447
24447
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24448
24448
  async ({ beatId }) => {
24449
24449
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24450
- const downbeat = await client.setDownbeat(beat.projectId, beatId);
24450
+ const downbeat = await client.setDownbeat(beat.systemId, beatId);
24451
24451
  if (!downbeat) {
24452
24452
  return { content: [{ type: "text", text: `Could not set a Downbeat for beat "${beatId}".` }], isError: true };
24453
24453
  }
@@ -24460,7 +24460,7 @@ function registerDownbeatTools(server, ctx, client) {
24460
24460
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24461
24461
  async ({ beatId }) => {
24462
24462
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24463
- const result = await client.listIncrementsSinceDownbeat(beat.projectId, beatId);
24463
+ const result = await client.listIncrementsSinceDownbeat(beat.systemId, beatId);
24464
24464
  if (!result) {
24465
24465
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
24466
24466
  }
@@ -24473,7 +24473,7 @@ function registerDownbeatTools(server, ctx, client) {
24473
24473
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24474
24474
  async ({ beatId }) => {
24475
24475
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24476
- const report = await client.getCodaDrift(beat.projectId, beatId);
24476
+ const report = await client.getCodaDrift(beat.systemId, beatId);
24477
24477
  if (!report) {
24478
24478
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
24479
24479
  }
@@ -24486,7 +24486,7 @@ function registerDownbeatTools(server, ctx, client) {
24486
24486
  { beatId: external_exports.string().describe("The beat ID (e.g., TF-B-001)") },
24487
24487
  async ({ beatId }) => {
24488
24488
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
24489
- const report = await client.getRelationalDrift(beat.projectId, beatId);
24489
+ const report = await client.getRelationalDrift(beat.systemId, beatId);
24490
24490
  if (!report) {
24491
24491
  return { content: [{ type: "text", text: `No such beat "${beatId}".` }], isError: true };
24492
24492
  }
@@ -24555,17 +24555,17 @@ function registerDropQualityTools(server, ctx, client) {
24555
24555
  "Run a five-dimension coherence check on a Drop (Substantial, Harmonious, Clear, Focused, Strategically Aligned), graded LLM-only on the Drop's Beat Versions and their parent Beats/Projects/Codas. By default returns a taskId immediately \u2014 set wait=true to block and receive the full scorecard inline.",
24556
24556
  {
24557
24557
  dropId: external_exports.string().min(1).describe("The Drop ID (e.g. drop-abc123) to grade"),
24558
- projectId: external_exports.string().min(1).describe("A project within this Drop \u2014 the check record is attributed to it (Drops span projects, so the result is recorded under one member project)"),
24558
+ systemId: external_exports.string().min(1).describe("A project within this Drop \u2014 the check record is attributed to it (Drops span projects, so the result is recorded under one member project)"),
24559
24559
  wait: external_exports.boolean().optional().describe("Block until the check completes and return the scorecard inline. Default: false.")
24560
24560
  },
24561
- async ({ dropId, projectId, wait }) => {
24561
+ async ({ dropId, systemId, wait }) => {
24562
24562
  try {
24563
- await assertProjectInOrg(client, projectId, ctx.orgId);
24563
+ await assertProjectInOrg(client, systemId, ctx.orgId);
24564
24564
  const drop = await client.getDrop(dropId);
24565
24565
  if (!drop) {
24566
24566
  return { content: [{ type: "text", text: `Drop "${dropId}" not found.` }], isError: true };
24567
24567
  }
24568
- const { taskId } = await client.runCheck(projectId, "drop_quality", dropId);
24568
+ const { taskId } = await client.runCheck(systemId, "drop_quality", dropId);
24569
24569
  if (!wait) {
24570
24570
  return {
24571
24571
  content: [{
@@ -24966,9 +24966,9 @@ function registerEmbeddingTools(server, ctx, client) {
24966
24966
  );
24967
24967
  server.tool(
24968
24968
  "search",
24969
- "Free-text semantic search across project, teamspace, or org scope. Provide exactly one of projectId / teamspaceId / orgLevel. Teamspace and org searches fan out across child projects and merge by score. Cross-project results carry the source project name.",
24969
+ "Free-text semantic search across project, teamspace, or org scope. Provide exactly one of systemId / teamspaceId / orgLevel. Teamspace and org searches fan out across child projects and merge by score. Cross-project results carry the source project name.",
24970
24970
  {
24971
- projectId: external_exports.string().optional().describe("Search within a single project"),
24971
+ systemId: external_exports.string().optional().describe("Search within a single project"),
24972
24972
  teamspaceId: external_exports.string().optional().describe("Search across all projects in a teamspace"),
24973
24973
  orgLevel: external_exports.boolean().optional().describe("Search across every project in the configured org"),
24974
24974
  query: external_exports.string().describe("Natural language search query"),
@@ -24979,15 +24979,15 @@ function registerEmbeddingTools(server, ctx, client) {
24979
24979
  noteStatus: external_exports.enum(["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"]).optional().describe("Restrict note results to a specific status (e.g. unvalidated). DynamoDB post-filter \u2014 works without re-embedding."),
24980
24980
  beatId: external_exports.string().optional().describe("Restrict note results to those belonging to a specific beat (DynamoDB post-filter, no re-embedding needed)")
24981
24981
  },
24982
- async ({ projectId, teamspaceId, orgLevel, query, limit, includeArchived, entityType, noteType, noteStatus, beatId }) => {
24983
- const provided = [projectId, teamspaceId, orgLevel].filter(Boolean).length;
24982
+ async ({ systemId, teamspaceId, orgLevel, query, limit, includeArchived, entityType, noteType, noteStatus, beatId }) => {
24983
+ const provided = [systemId, teamspaceId, orgLevel].filter(Boolean).length;
24984
24984
  if (provided !== 1) {
24985
- throw new Error("Specify exactly one of projectId, teamspaceId, or orgLevel.");
24985
+ throw new Error("Specify exactly one of systemId, teamspaceId, or orgLevel.");
24986
24986
  }
24987
24987
  let scope;
24988
- if (projectId) {
24989
- await assertProjectInOrg(client, projectId, ctx.orgId);
24990
- scope = { projectId };
24988
+ if (systemId) {
24989
+ await assertProjectInOrg(client, systemId, ctx.orgId);
24990
+ scope = { systemId };
24991
24991
  } else if (teamspaceId) {
24992
24992
  await assertTeamspaceInOrg(client, teamspaceId, ctx.orgId);
24993
24993
  scope = { teamspaceId };
@@ -25004,22 +25004,22 @@ function registerEmbeddingTools(server, ctx, client) {
25004
25004
  "Find Notes semantically similar to one or more Notes. Accepts a single Note ID or an array of Note IDs for batch duplicate detection. Results are grouped by source Note.",
25005
25005
  {
25006
25006
  noteId: external_exports.union([external_exports.string(), external_exports.array(external_exports.string())]).describe("Note ID or array of Note IDs to find similar notes for"),
25007
- projectId: external_exports.string().describe("The project ID the notes belong to"),
25007
+ systemId: external_exports.string().describe("The project ID the notes belong to"),
25008
25008
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
25009
25009
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results per source note. Default 10"),
25010
25010
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated notes in results (excluded by default)"),
25011
25011
  noteStatus: external_exports.enum(["active", "unvalidated", "validated", "invalidated", "inProgress", "resolved", "superseded", "dismissed"]).optional().describe("Restrict results to notes with a specific status (e.g. unvalidated). DynamoDB post-filter."),
25012
25012
  beatId: external_exports.string().optional().describe("Restrict results to notes belonging to a specific beat (DynamoDB post-filter)")
25013
25013
  },
25014
- async ({ noteId, projectId, threshold, limit, includeArchived = false, noteStatus, beatId }) => {
25015
- await assertProjectInOrg(client, projectId, ctx.orgId);
25014
+ async ({ noteId, systemId, threshold, limit, includeArchived = false, noteStatus, beatId }) => {
25015
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25016
25016
  const ids = Array.isArray(noteId) ? noteId : [noteId];
25017
25017
  if (ids.length === 1) {
25018
- const results = await client.findSimilarNotes(ids[0], projectId, { threshold, limit, includeArchived, noteStatus, beatId });
25018
+ const results = await client.findSimilarNotes(ids[0], systemId, { threshold, limit, includeArchived, noteStatus, beatId });
25019
25019
  const text = formatSimilarityResults(results, ids[0], "Similar Notes");
25020
25020
  return { content: [{ type: "text", text }] };
25021
25021
  }
25022
- const batchResults = await client.findSimilarNotesMulti(ids, projectId, { threshold, limit, includeArchived, noteStatus, beatId });
25022
+ const batchResults = await client.findSimilarNotesMulti(ids, systemId, { threshold, limit, includeArchived, noteStatus, beatId });
25023
25023
  const sections = batchResults.map(
25024
25024
  (br) => formatSimilarityResults(br.results, br.sourceNoteId, "Similar Notes")
25025
25025
  );
@@ -25031,14 +25031,14 @@ function registerEmbeddingTools(server, ctx, client) {
25031
25031
  "Find Beats semantically similar to a given Beat. Useful for detecting duplicates, dependencies, or related capabilities.",
25032
25032
  {
25033
25033
  beatId: external_exports.string().describe("The beat ID to find similar beats for"),
25034
- projectId: external_exports.string().describe("The project ID the beat belongs to"),
25034
+ systemId: external_exports.string().describe("The project ID the beat belongs to"),
25035
25035
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
25036
25036
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10"),
25037
25037
  includeArchived: external_exports.boolean().optional().default(false).describe("Include archived/deprecated beats in results (excluded by default)")
25038
25038
  },
25039
- async ({ beatId, projectId, threshold, limit, includeArchived = false }) => {
25039
+ async ({ beatId, systemId, threshold, limit, includeArchived = false }) => {
25040
25040
  await assertBeatInOrg(client, beatId, ctx.orgId);
25041
- const results = await client.findSimilarBeats(beatId, projectId, { threshold, limit, includeArchived });
25041
+ const results = await client.findSimilarBeats(beatId, systemId, { threshold, limit, includeArchived });
25042
25042
  const text = formatSimilarityResults(results, beatId, "Similar Beats");
25043
25043
  return { content: [{ type: "text", text }] };
25044
25044
  }
@@ -25048,13 +25048,13 @@ function registerEmbeddingTools(server, ctx, client) {
25048
25048
  "Find Beats semantically related to a given Note. Surfaces missing affectsBeats links and helps classify unassigned Notes.",
25049
25049
  {
25050
25050
  noteId: external_exports.string().describe("The note ID to find related beats for"),
25051
- projectId: external_exports.string().describe("The project ID the note belongs to"),
25051
+ systemId: external_exports.string().describe("The project ID the note belongs to"),
25052
25052
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.7).describe("Minimum similarity score (0-1). Default 0.7"),
25053
25053
  limit: external_exports.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results to return. Default 10")
25054
25054
  },
25055
- async ({ noteId, projectId, threshold, limit }) => {
25056
- await assertProjectInOrg(client, projectId, ctx.orgId);
25057
- const results = await client.findRelatedBeats(noteId, projectId, { threshold, limit });
25055
+ async ({ noteId, systemId, threshold, limit }) => {
25056
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25057
+ const results = await client.findRelatedBeats(noteId, systemId, { threshold, limit });
25058
25058
  const text = formatSimilarityResults(results, noteId, "Related Beats");
25059
25059
  return { content: [{ type: "text", text }] };
25060
25060
  }
@@ -25063,14 +25063,14 @@ function registerEmbeddingTools(server, ctx, client) {
25063
25063
  "cluster_notes",
25064
25064
  "Cluster semantically similar Notes using DBSCAN on vector embeddings. Returns clusters for review with member previews. Use this to discover natural groupings among notes and consolidate them under parent summary notes.",
25065
25065
  {
25066
- projectId: external_exports.string().describe("The project ID"),
25066
+ systemId: external_exports.string().describe("The project ID"),
25067
25067
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.75).describe("Cosine similarity threshold for clustering (0-1). Higher = tighter clusters. Default 0.75"),
25068
25068
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(3).describe("Minimum notes to form a cluster. Default 3"),
25069
25069
  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
25070
  },
25071
- async ({ projectId, threshold, minClusterSize, includeLinked }) => {
25072
- await assertProjectInOrg(client, projectId, ctx.orgId);
25073
- const result = await client.clusterNotes(projectId, { threshold, minClusterSize, excludeAlreadyLinked: !includeLinked });
25071
+ async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25072
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25073
+ const result = await client.clusterNotes(systemId, { threshold, minClusterSize, excludeAlreadyLinked: !includeLinked });
25074
25074
  if (result.clusters.length === 0) {
25075
25075
  return { content: [{ type: "text", text: `No clusters found among ${result.totalAnalyzed} analyzed notes. Try lowering the threshold.` }] };
25076
25076
  }
@@ -25094,14 +25094,14 @@ ${members}`;
25094
25094
  "consolidate_notes",
25095
25095
  "Two-pass note consolidation: clusters notes via DBSCAN, then sends each cluster to an LLM for contextual analysis. Returns consolidation proposals (should these be grouped under a parent summary?) without modifying data. Use cluster_notes for a quick vector-only view; use this when ready to act on consolidation.",
25096
25096
  {
25097
- projectId: external_exports.string().describe("The project ID"),
25097
+ systemId: external_exports.string().describe("The project ID"),
25098
25098
  threshold: external_exports.coerce.number().min(0).max(1).optional().default(0.65).describe("Cosine similarity threshold for clustering (0-1). Default 0.65"),
25099
25099
  minClusterSize: external_exports.coerce.number().int().min(2).optional().default(2).describe("Minimum notes to form a cluster. Default 2"),
25100
25100
  includeLinked: external_exports.coerce.boolean().optional().default(false).describe("Include notes that already have dependsOnNotes links. Default false.")
25101
25101
  },
25102
- async ({ projectId, threshold, minClusterSize, includeLinked }) => {
25103
- await assertProjectInOrg(client, projectId, ctx.orgId);
25104
- const result = await client.consolidateNotes(projectId, { threshold, minClusterSize, includeLinked });
25102
+ async ({ systemId, threshold, minClusterSize, includeLinked }) => {
25103
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25104
+ const result = await client.consolidateNotes(systemId, { threshold, minClusterSize, includeLinked });
25105
25105
  if (result.proposals.length === 0) {
25106
25106
  return { content: [{ type: "text", text: `No consolidation candidates found among ${result.totalAnalyzed} analyzed notes.` }] };
25107
25107
  }
@@ -25337,18 +25337,18 @@ function registerLayerTools(server, ctx, client) {
25337
25337
  "list_system_layers",
25338
25338
  "List the architectural Layers declared on a System (e.g. frontend, backend, data). Layers are an evaluation lens for plan/build quality checks, not a work container.",
25339
25339
  {
25340
- projectId: external_exports.string().describe("The System ID")
25340
+ systemId: external_exports.string().describe("The System ID")
25341
25341
  },
25342
- async ({ projectId }) => {
25342
+ async ({ systemId }) => {
25343
25343
  try {
25344
- await assertProjectInOrg(client, projectId, ctx.orgId);
25344
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25345
25345
  } catch (err) {
25346
25346
  const message = err instanceof Error ? err.message : String(err);
25347
25347
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
25348
25348
  }
25349
25349
  let layers;
25350
25350
  try {
25351
- layers = await client.listSystemLayers(projectId);
25351
+ layers = await client.listSystemLayers(systemId);
25352
25352
  } catch (err) {
25353
25353
  const message = err instanceof Error ? err.message : String(err);
25354
25354
  return { content: [{ type: "text", text: `Failed to list Layers: ${message}` }], isError: true };
@@ -25361,12 +25361,12 @@ function registerLayerTools(server, ctx, client) {
25361
25361
  "get_layer",
25362
25362
  "Get full details of a single Layer by ID, including its repo mapping and parent Layer (if it was split from one).",
25363
25363
  {
25364
- projectId: external_exports.string().describe("The System ID (for access control)"),
25364
+ systemId: external_exports.string().describe("The System ID (for access control)"),
25365
25365
  layerId: external_exports.string().describe("The Layer ID")
25366
25366
  },
25367
- async ({ projectId, layerId }) => {
25367
+ async ({ systemId, layerId }) => {
25368
25368
  try {
25369
- await assertProjectInOrg(client, projectId, ctx.orgId);
25369
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25370
25370
  } catch (err) {
25371
25371
  const message = err instanceof Error ? err.message : String(err);
25372
25372
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25381,8 +25381,8 @@ function registerLayerTools(server, ctx, client) {
25381
25381
  if (!layer) {
25382
25382
  return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
25383
25383
  }
25384
- if (layer.projectId !== projectId) {
25385
- return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${projectId}".` }], isError: true };
25384
+ if (layer.systemId !== systemId) {
25385
+ return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
25386
25386
  }
25387
25387
  const text = formatLayerDetail(layer);
25388
25388
  return { content: [{ type: "text", text }] };
@@ -25392,16 +25392,16 @@ function registerLayerTools(server, ctx, client) {
25392
25392
  "create_layer",
25393
25393
  "Declare a new architectural Layer on a System (e.g. frontend, backend, data). Layers are an evaluation lens for plan/build quality checks. layerKey must be a lowercase slug and unique per System.",
25394
25394
  {
25395
- projectId: external_exports.string().describe("The System ID"),
25395
+ systemId: external_exports.string().describe("The System ID"),
25396
25396
  layerKey: external_exports.string().regex(/^[a-z]([a-z0-9-]*[a-z0-9])?$/, "layerKey must be a lowercase slug (letters, digits, hyphens; start with a letter, no trailing hyphen)").describe('Stable lowercase slug for the Layer role, e.g. "frontend" or "agent-workers"'),
25397
25397
  name: external_exports.string().describe('Display name, e.g. "Frontend"'),
25398
25398
  description: external_exports.string().optional().describe("Optional description of the Layer"),
25399
25399
  parentLayerId: external_exports.string().optional().describe("Optional parent Layer to nest under (must be a Layer on the same System)"),
25400
25400
  repoPatterns: external_exports.array(external_exports.string()).optional().describe("Optional repo path patterns owned by this Layer")
25401
25401
  },
25402
- async ({ projectId, layerKey, name, description, parentLayerId, repoPatterns }) => {
25402
+ async ({ systemId, layerKey, name, description, parentLayerId, repoPatterns }) => {
25403
25403
  try {
25404
- await assertProjectInOrg(client, projectId, ctx.orgId);
25404
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25405
25405
  } catch (err) {
25406
25406
  const message = err instanceof Error ? err.message : String(err);
25407
25407
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25409,7 +25409,7 @@ function registerLayerTools(server, ctx, client) {
25409
25409
  let layer;
25410
25410
  try {
25411
25411
  layer = await client.createSystemLayer({
25412
- projectId,
25412
+ systemId,
25413
25413
  layerKey,
25414
25414
  name,
25415
25415
  ...description !== void 0 && { description },
@@ -25421,7 +25421,7 @@ function registerLayerTools(server, ctx, client) {
25421
25421
  return { content: [{ type: "text", text: `Failed to create Layer: ${message}` }], isError: true };
25422
25422
  }
25423
25423
  if (!layer) {
25424
- return { content: [{ type: "text", text: `System "${projectId}" not found or not writable.` }], isError: true };
25424
+ return { content: [{ type: "text", text: `System "${systemId}" not found or not writable.` }], isError: true };
25425
25425
  }
25426
25426
  return { content: [{ type: "text", text: `Created Layer.
25427
25427
 
@@ -25432,15 +25432,15 @@ ${formatLayerDetail(layer)}` }] };
25432
25432
  "update_layer",
25433
25433
  "Update a Layer's editable fields (name, description, repo mapping). Omitted fields are left unchanged. Archiving/reviving a Layer goes through transition_layer, not this tool. Requires write access to the System.",
25434
25434
  {
25435
- projectId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
25435
+ systemId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
25436
25436
  layerId: external_exports.string().describe("The Layer ID to update"),
25437
25437
  name: external_exports.string().optional().describe("New display name"),
25438
25438
  description: external_exports.string().optional().describe("New description"),
25439
25439
  repoPatterns: external_exports.array(external_exports.string()).optional().describe("Replacement repo mapping (replaces the existing list)")
25440
25440
  },
25441
- async ({ projectId, layerId, name, description, repoPatterns }) => {
25441
+ async ({ systemId, layerId, name, description, repoPatterns }) => {
25442
25442
  try {
25443
- await assertProjectInOrg(client, projectId, ctx.orgId);
25443
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25444
25444
  } catch (err) {
25445
25445
  const message = err instanceof Error ? err.message : String(err);
25446
25446
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25453,8 +25453,8 @@ ${formatLayerDetail(layer)}` }] };
25453
25453
  return { content: [{ type: "text", text: `Failed to load Layer: ${message}` }], isError: true };
25454
25454
  }
25455
25455
  if (!existing) return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
25456
- if (existing.projectId !== projectId) {
25457
- return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${projectId}".` }], isError: true };
25456
+ if (existing.systemId !== systemId) {
25457
+ return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
25458
25458
  }
25459
25459
  let layer;
25460
25460
  try {
@@ -25477,14 +25477,14 @@ ${formatLayerDetail(layer)}` }] };
25477
25477
  "transition_layer",
25478
25478
  "Archive or revive a Layer. Archiving requires a reason (audit trail); reviving sets it back to active. Requires write access to the System.",
25479
25479
  {
25480
- projectId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
25480
+ systemId: external_exports.string().describe("The System ID the Layer belongs to (for access control)"),
25481
25481
  layerId: external_exports.string().describe("The Layer ID to transition"),
25482
25482
  status: external_exports.enum(["active", "archived"]).describe('Target status: "archived" to retire, "active" to revive'),
25483
25483
  reason: external_exports.string().optional().describe('Why the Layer is being archived (required when status is "archived")')
25484
25484
  },
25485
- async ({ projectId, layerId, status, reason }) => {
25485
+ async ({ systemId, layerId, status, reason }) => {
25486
25486
  try {
25487
- await assertProjectInOrg(client, projectId, ctx.orgId);
25487
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25488
25488
  } catch (err) {
25489
25489
  const message = err instanceof Error ? err.message : String(err);
25490
25490
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -25497,8 +25497,8 @@ ${formatLayerDetail(layer)}` }] };
25497
25497
  return { content: [{ type: "text", text: `Failed to load Layer: ${message}` }], isError: true };
25498
25498
  }
25499
25499
  if (!existing) return { content: [{ type: "text", text: `Layer not found: "${layerId}"` }], isError: true };
25500
- if (existing.projectId !== projectId) {
25501
- return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${projectId}".` }], isError: true };
25500
+ if (existing.systemId !== systemId) {
25501
+ return { content: [{ type: "text", text: `Layer "${layerId}" does not belong to System "${systemId}".` }], isError: true };
25502
25502
  }
25503
25503
  let layer;
25504
25504
  try {
@@ -25823,13 +25823,13 @@ function registerNextActionsTools(server, ctx, client) {
25823
25823
  "get_next_actions",
25824
25824
  "Get prioritized list of recommended next actions for a project. Analyzes Beats, Revisions, and Notes to surface the highest-priority work: P0 (urgent bugs) \u2192 P1 (deploy) \u2192 P2 (build) \u2192 P3 (plan) \u2192 P4 (compose) \u2192 P5 (maintenance).",
25825
25825
  {
25826
- projectId: external_exports.string().describe("The project ID"),
25826
+ systemId: external_exports.string().describe("The project ID"),
25827
25827
  limit: external_exports.coerce.number().optional().default(10).describe("Max actions to return")
25828
25828
  },
25829
- async ({ projectId, limit }) => {
25829
+ async ({ systemId, limit }) => {
25830
25830
  try {
25831
- await assertProjectInOrg(client, projectId, ctx.orgId);
25832
- const actions = await client.getNextActions(projectId, { limit });
25831
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25832
+ const actions = await client.getNextActions(systemId, { limit });
25833
25833
  return { content: [{ type: "text", text: formatNextActions(actions) }] };
25834
25834
  } catch (err) {
25835
25835
  const message = err instanceof Error ? err.message : String(err);
@@ -25841,12 +25841,12 @@ function registerNextActionsTools(server, ctx, client) {
25841
25841
  "list_triage_actions",
25842
25842
  "List active triage actions from the ledger (open + acted). This is the stateful view of what needs attention \u2014 includes status tracking, task progress, and escalation state. Use this instead of get_next_actions when triage has been run.",
25843
25843
  {
25844
- projectId: external_exports.string().describe("The project ID")
25844
+ systemId: external_exports.string().describe("The project ID")
25845
25845
  },
25846
- async ({ projectId }) => {
25846
+ async ({ systemId }) => {
25847
25847
  try {
25848
- await assertProjectInOrg(client, projectId, ctx.orgId);
25849
- const actions = await client.listTriageActions(projectId);
25848
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25849
+ const actions = await client.listTriageActions(systemId);
25850
25850
  return { content: [{ type: "text", text: formatTriageActions(actions) }] };
25851
25851
  } catch (err) {
25852
25852
  const message = err instanceof Error ? err.message : String(err);
@@ -25858,13 +25858,13 @@ function registerNextActionsTools(server, ctx, client) {
25858
25858
  "dismiss_triage_action",
25859
25859
  'Dismiss a triage action (human decides "not now"). The action will not be re-escalated until the underlying state changes.',
25860
25860
  {
25861
- projectId: external_exports.string().describe("The project ID"),
25861
+ systemId: external_exports.string().describe("The project ID"),
25862
25862
  actionKey: external_exports.string().describe("The action key to dismiss (from list_triage_actions)")
25863
25863
  },
25864
- async ({ projectId, actionKey }) => {
25864
+ async ({ systemId, actionKey }) => {
25865
25865
  try {
25866
- await assertProjectInOrg(client, projectId, ctx.orgId);
25867
- await client.dismissTriageAction(projectId, actionKey, ctx.user?.userId ?? "mcp");
25866
+ await assertProjectInOrg(client, systemId, ctx.orgId);
25867
+ await client.dismissTriageAction(systemId, actionKey, ctx.user?.userId ?? "mcp");
25868
25868
  return { content: [{ type: "text", text: `Action ${actionKey} dismissed.` }] };
25869
25869
  } catch (err) {
25870
25870
  const message = err instanceof Error ? err.message : String(err);
@@ -27255,7 +27255,7 @@ function registerNoteTools(server, ctx, client) {
27255
27255
  "list_notes",
27256
27256
  "List Notes for a project. Without beatId: returns all notes (project-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.",
27257
27257
  {
27258
- projectId: external_exports.string().describe("The project ID"),
27258
+ systemId: external_exports.string().describe("The project ID"),
27259
27259
  beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project notes)"),
27260
27260
  noteType: external_exports.union([external_exports.enum(NOTE_TYPE_VALUES), external_exports.array(external_exports.enum(NOTE_TYPE_VALUES)).min(1)]).optional().describe('Filter by note type \u2014 single value or array for multi-select (e.g. ["constraint", "assumption"])'),
27261
27261
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
@@ -27268,7 +27268,7 @@ function registerNoteTools(server, ctx, client) {
27268
27268
  cursor: external_exports.string().optional().describe("Opaque pagination cursor from a previous response \u2014 use for project-wide note pagination (no beatId)"),
27269
27269
  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.")
27270
27270
  },
27271
- async ({ projectId, beatId, noteType, status, revisionId, beatVersionId, sourceDocumentNoteId, significance, offset = 0, limit = 100, cursor, orderBy }) => {
27271
+ async ({ systemId, beatId, noteType, status, revisionId, beatVersionId, sourceDocumentNoteId, significance, offset = 0, limit = 100, cursor, orderBy }) => {
27272
27272
  if (Array.isArray(noteType) && noteType.length === 0) {
27273
27273
  return { content: [{ type: "text", text: "noteType must not be an empty array \u2014 omit it to return all types." }], isError: true };
27274
27274
  }
@@ -27280,7 +27280,7 @@ function registerNoteTools(server, ctx, client) {
27280
27280
  return { content: [{ type: "text", text: "orderBy requires beatId \u2014 it is only supported for beat-scoped queries." }], isError: true };
27281
27281
  }
27282
27282
  try {
27283
- await assertProjectInOrg(client, projectId, ctx.orgId);
27283
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27284
27284
  } catch (err) {
27285
27285
  const message = err instanceof Error ? err.message : String(err);
27286
27286
  return { content: [{ type: "text", text: `Access denied: ${message}` }], isError: true };
@@ -27320,7 +27320,7 @@ function registerNoteTools(server, ctx, client) {
27320
27320
  const SOURCE_DOC_FETCH_LIMIT = 5e3;
27321
27321
  let allNotesResult;
27322
27322
  try {
27323
- allNotesResult = await client.listAllSystemNotes(projectId, filters, SOURCE_DOC_FETCH_LIMIT);
27323
+ allNotesResult = await client.listAllSystemNotes(systemId, filters, SOURCE_DOC_FETCH_LIMIT);
27324
27324
  } catch (err) {
27325
27325
  const message = err instanceof Error ? err.message : String(err);
27326
27326
  return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
@@ -27341,7 +27341,7 @@ function registerNoteTools(server, ctx, client) {
27341
27341
  } else {
27342
27342
  let projectNotesResult;
27343
27343
  try {
27344
- projectNotesResult = await client.listAllSystemNotes(projectId, filters, limit, cursor);
27344
+ projectNotesResult = await client.listAllSystemNotes(systemId, filters, limit, cursor);
27345
27345
  } catch (err) {
27346
27346
  const message = err instanceof Error ? err.message : String(err);
27347
27347
  return { content: [{ type: "text", text: `Failed to list project notes: ${message}` }], isError: true };
@@ -27423,12 +27423,12 @@ function registerNoteTools(server, ctx, client) {
27423
27423
  "list_documents",
27424
27424
  "List uploaded documents for a project with processing status, filename, child note count, and AI summary. Use get_note with a document note ID for full details.",
27425
27425
  {
27426
- projectId: external_exports.string().describe("The project ID")
27426
+ systemId: external_exports.string().describe("The project ID")
27427
27427
  },
27428
- async ({ projectId }) => {
27429
- await assertProjectInOrg(client, projectId, ctx.orgId);
27428
+ async ({ systemId }) => {
27429
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27430
27430
  const DOCUMENT_FETCH_LIMIT = 5e3;
27431
- const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(projectId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
27431
+ const { notes: docs, hasMore: truncated } = await client.listAllSystemNotes(systemId, { noteType: "document" }, DOCUMENT_FETCH_LIMIT);
27432
27432
  const visibleDocs = docs.filter((d) => d.status !== DISMISSED_NOTE_STATUS);
27433
27433
  const truncationNotice = truncated && visibleDocs.length > 0 ? "\n\n> \u26A0\uFE0F This project has more than 5000 documents. Some documents may not be shown.\n" : "";
27434
27434
  const text = formatDocumentList(visibleDocs) + truncationNotice;
@@ -27439,16 +27439,16 @@ function registerNoteTools(server, ctx, client) {
27439
27439
  "get_note",
27440
27440
  "Get full details of a single Note by ID",
27441
27441
  {
27442
- projectId: external_exports.string().optional().describe("The project ID (for access control). Provide projectId or orgId, not both."),
27443
- orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or projectId, not both."),
27442
+ systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
27443
+ orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
27444
27444
  noteId: external_exports.string().describe("The note ID")
27445
27445
  },
27446
- async ({ projectId, orgId, noteId }) => {
27447
- if (projectId && orgId) {
27448
- return { content: [{ type: "text", text: "Provide projectId or orgId, not both." }], isError: true };
27446
+ async ({ systemId, orgId, noteId }) => {
27447
+ if (systemId && orgId) {
27448
+ return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
27449
27449
  }
27450
- if (!projectId && !orgId) {
27451
- return { content: [{ type: "text", text: "Provide projectId or orgId (for access control)." }], isError: true };
27450
+ if (!systemId && !orgId) {
27451
+ return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
27452
27452
  }
27453
27453
  try {
27454
27454
  if (orgId) {
@@ -27461,7 +27461,7 @@ function registerNoteTools(server, ctx, client) {
27461
27461
  }
27462
27462
  return { content: [{ type: "text", text: formatNoteDetail(note2) }] };
27463
27463
  }
27464
- await assertProjectInOrg(client, projectId, ctx.orgId);
27464
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27465
27465
  const note = await client.getNote(noteId);
27466
27466
  if (!note) {
27467
27467
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27476,15 +27476,15 @@ function registerNoteTools(server, ctx, client) {
27476
27476
  );
27477
27477
  server.tool(
27478
27478
  "create_note",
27479
- "Create a new Note. Provide exactly one of projectId, teamspaceId, movementId, or none (org-level): projectId scopes the note to a project (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child projects), 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 projects.",
27479
+ "Create a new Note. Provide exactly one of systemId, teamspaceId, movementId, or none (org-level): systemId scopes the note to a project (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child projects), 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 projects.",
27480
27480
  {
27481
- projectId: external_exports.string().optional().describe("The project ID (omit for teamspace-, movement-, or org-level note)"),
27481
+ systemId: external_exports.string().optional().describe("The project ID (omit for teamspace-, movement-, or org-level note)"),
27482
27482
  teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for project-, movement-, or org-level note)"),
27483
27483
  movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for project-, teamspace-, or org-level note)"),
27484
27484
  noteType: external_exports.enum(NOTE_TYPE_VALUES).describe("The type of note"),
27485
27485
  content: external_exports.string().describe("The note content"),
27486
- beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires projectId)"),
27487
- revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires projectId)"),
27486
+ beatId: external_exports.string().optional().describe("Beat ID if this note is beat-scoped (requires systemId)"),
27487
+ revisionId: external_exports.string().optional().describe("Revision ID if this note is revision-scoped (requires systemId)"),
27488
27488
  beatVersionId: external_exports.string().optional().describe("Beat Version ID if this note governs a specific planning increment (requires beatId)"),
27489
27489
  rationale: external_exports.string().optional().describe("Why this note exists"),
27490
27490
  confidence: external_exports.coerce.number().min(0).max(1).optional().describe("Confidence level for assumptions (0-1)"),
@@ -27492,20 +27492,20 @@ function registerNoteTools(server, ctx, client) {
27492
27492
  dependsOnNotes: external_exports.array(external_exports.string()).optional().describe("Note IDs this note depends on (child \u2192 parent links for consolidation)"),
27493
27493
  significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Defaults to implementation when unset. Only meaningful on noteType=decision.")
27494
27494
  },
27495
- async ({ projectId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, confidence, affectsBeats, dependsOnNotes, significance }) => {
27495
+ async ({ systemId, teamspaceId, movementId, noteType, content, beatId, revisionId, beatVersionId, rationale, confidence, affectsBeats, dependsOnNotes, significance }) => {
27496
27496
  try {
27497
- if ([projectId, teamspaceId, movementId].filter(Boolean).length > 1) {
27498
- return { content: [{ type: "text", text: "Specify at most one of projectId, teamspaceId, or movementId." }], isError: true };
27497
+ if ([systemId, teamspaceId, movementId].filter(Boolean).length > 1) {
27498
+ return { content: [{ type: "text", text: "Specify at most one of systemId, teamspaceId, or movementId." }], isError: true };
27499
27499
  }
27500
27500
  const assumptionMeta = noteType === "assumption" && confidence !== void 0 ? { confidence } : void 0;
27501
27501
  let note;
27502
- if (projectId) {
27503
- await assertProjectInOrg(client, projectId, ctx.orgId);
27504
- const projectCode = projectId.substring(0, 4).toUpperCase();
27505
- const noteId = await client.getNextNoteId(projectCode, projectId);
27502
+ if (systemId) {
27503
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27504
+ const projectCode = systemId.substring(0, 4).toUpperCase();
27505
+ const noteId = await client.getNextNoteId(projectCode, systemId);
27506
27506
  note = await client.createNote({
27507
27507
  noteId,
27508
- projectId,
27508
+ projectId: systemId,
27509
27509
  beatId,
27510
27510
  revisionId,
27511
27511
  beatVersionId,
@@ -27577,8 +27577,8 @@ ${text}` }] };
27577
27577
  "update_note",
27578
27578
  "Update a Note (status, content, response, etc.). humanAssignee and agentAssignee are independent fields \u2014 both can be set simultaneously (a note can have a human owner and an agent collaborator). Set either to null to clear it.",
27579
27579
  {
27580
- projectId: external_exports.string().optional().describe("The project ID (for access control). Provide projectId or orgId, not both."),
27581
- orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or projectId, not both."),
27580
+ systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
27581
+ orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
27582
27582
  noteId: external_exports.string().describe("The note ID to update"),
27583
27583
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. assumption \u2192 guidance)"),
27584
27584
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status"),
@@ -27592,13 +27592,13 @@ ${text}` }] };
27592
27592
  agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or reassign this note to an agent (null to clear)"),
27593
27593
  significance: external_exports.enum(DECISION_SIGNIFICANCE_VALUES2).optional().describe("Significance tier for decision Notes (strategic | structural | implementation). Only meaningful on noteType=decision.")
27594
27594
  },
27595
- async ({ projectId, orgId, noteId, noteType, status, content, rationale, response, beatId, revisionId, dependsOnNotes, humanAssignee, agentAssignee, significance }) => {
27595
+ async ({ systemId, orgId, noteId, noteType, status, content, rationale, response, beatId, revisionId, dependsOnNotes, humanAssignee, agentAssignee, significance }) => {
27596
27596
  try {
27597
- if (projectId && orgId) {
27598
- return { content: [{ type: "text", text: "Provide projectId or orgId, not both." }], isError: true };
27597
+ if (systemId && orgId) {
27598
+ return { content: [{ type: "text", text: "Provide systemId or orgId, not both." }], isError: true };
27599
27599
  }
27600
- if (!projectId && !orgId) {
27601
- return { content: [{ type: "text", text: "Provide projectId or orgId (for access control)." }], isError: true };
27600
+ if (!systemId && !orgId) {
27601
+ return { content: [{ type: "text", text: "Provide systemId or orgId (for access control)." }], isError: true };
27602
27602
  }
27603
27603
  if (orgId) {
27604
27604
  if (orgId !== ctx.orgId) {
@@ -27628,7 +27628,7 @@ ${text}` }] };
27628
27628
 
27629
27629
  ${formatNoteDetail(updated2)}` }] };
27630
27630
  }
27631
- await assertProjectInOrg(client, projectId, ctx.orgId);
27631
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27632
27632
  const updated = await client.updateNote(noteId, {
27633
27633
  ...noteType !== void 0 && { noteType },
27634
27634
  status,
@@ -27659,7 +27659,7 @@ ${text}` }] };
27659
27659
  "bulk_update_notes",
27660
27660
  "Update multiple Notes in one call, applying the same fields to all. More efficient than calling update_note N times. Returns updated notes and any per-note failures.",
27661
27661
  {
27662
- projectId: external_exports.string().describe("The project ID (for access control)"),
27662
+ systemId: external_exports.string().describe("The project ID (for access control)"),
27663
27663
  noteIds: external_exports.array(external_exports.string()).min(1).max(100).describe("Note IDs to update (max 100 per call)"),
27664
27664
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify all notes to this type"),
27665
27665
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status for all notes"),
@@ -27668,9 +27668,9 @@ ${text}` }] };
27668
27668
  humanAssignee: humanAssigneeSchema.nullable().optional().describe("Assign or clear human assignee on all notes (null to clear)"),
27669
27669
  agentAssignee: agentAssigneeSchema.nullable().optional().describe("Assign or clear agent assignee on all notes (null to clear)")
27670
27670
  },
27671
- async ({ projectId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
27671
+ async ({ systemId, noteIds, noteType, status, content, response, humanAssignee, agentAssignee }) => {
27672
27672
  try {
27673
- await assertProjectInOrg(client, projectId, ctx.orgId);
27673
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27674
27674
  const result = await client.bulkUpdateNotes(noteIds, {
27675
27675
  ...noteType !== void 0 && { noteType },
27676
27676
  status,
@@ -27701,15 +27701,15 @@ ${text}` }] };
27701
27701
  "reassign_note",
27702
27702
  "Reassign a misattributed note to a different beat (with audit trail)",
27703
27703
  {
27704
- projectId: external_exports.string().describe("The project ID (for access control)"),
27704
+ systemId: external_exports.string().describe("The project ID (for access control)"),
27705
27705
  noteId: external_exports.string().describe("The note ID to reassign"),
27706
27706
  targetBeatId: external_exports.string().describe("The beat ID to move the note to"),
27707
27707
  targetRevisionId: external_exports.string().optional().describe("Optional revision ID to scope the note to"),
27708
27708
  reason: external_exports.string().describe("Why this note is being reassigned")
27709
27709
  },
27710
- async ({ projectId, noteId, targetBeatId, targetRevisionId, reason }) => {
27710
+ async ({ systemId, noteId, targetBeatId, targetRevisionId, reason }) => {
27711
27711
  try {
27712
- await assertProjectInOrg(client, projectId, ctx.orgId);
27712
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27713
27713
  const updated = await client.reassignNote(noteId, targetBeatId, reason, ctx.user.userId, targetRevisionId);
27714
27714
  if (!updated) {
27715
27715
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
@@ -27725,15 +27725,15 @@ ${text}` }] };
27725
27725
  "remove_note",
27726
27726
  "Soft-remove a misattributed note by dismissing it (with audit trail)",
27727
27727
  {
27728
- projectId: external_exports.string().min(1).describe("The project ID (for access control)"),
27728
+ systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
27729
27729
  noteId: external_exports.string().min(1).describe("The note ID to remove"),
27730
27730
  reason: external_exports.string().min(1).max(500).optional().describe('Why this note is being removed (defaults to "Removed via remove_note")')
27731
27731
  },
27732
- async ({ projectId, noteId, reason }) => {
27732
+ async ({ systemId, noteId, reason }) => {
27733
27733
  try {
27734
- await assertProjectInOrg(client, projectId, ctx.orgId);
27734
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27735
27735
  const note = await client.getNote(noteId);
27736
- if (!note || note.projectId !== projectId) {
27736
+ if (!note || (note.systemId ?? note.projectId) !== systemId) {
27737
27737
  return { content: [{ type: "text", text: `Note not found: "${noteId}"` }], isError: true };
27738
27738
  }
27739
27739
  const removeReason = reason ?? "Removed via remove_note";
@@ -27752,15 +27752,15 @@ ${text}` }] };
27752
27752
  "dismiss_document",
27753
27753
  "Soft-remove an uploaded document by dismissing it \u2014 it will no longer appear in list_documents output. Only works on document-type notes; use remove_note for non-document notes.",
27754
27754
  {
27755
- projectId: external_exports.string().min(1).describe("The project ID (for access control)"),
27755
+ systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
27756
27756
  noteId: external_exports.string().min(1).describe("The document note ID to dismiss"),
27757
27757
  reason: external_exports.string().min(1).max(500).optional().describe('Why this document is being dismissed (defaults to "Dismissed via dismiss_document")')
27758
27758
  },
27759
- async ({ projectId, noteId, reason }) => {
27759
+ async ({ systemId, noteId, reason }) => {
27760
27760
  try {
27761
- await assertProjectInOrg(client, projectId, ctx.orgId);
27761
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27762
27762
  const note = await client.getNote(noteId);
27763
- if (!note || note.projectId !== projectId) {
27763
+ if (!note || (note.systemId ?? note.projectId) !== systemId) {
27764
27764
  return { content: [{ type: "text", text: `Document not found: "${noteId}"` }], isError: true };
27765
27765
  }
27766
27766
  if (note.noteType !== "document") {
@@ -27785,11 +27785,11 @@ ${text}` }] };
27785
27785
  "list_key_assumptions",
27786
27786
  "Get key assumptions sorted by criticality (highest risk first)",
27787
27787
  {
27788
- projectId: external_exports.string().describe("The project ID")
27788
+ systemId: external_exports.string().describe("The project ID")
27789
27789
  },
27790
- async ({ projectId }) => {
27791
- await assertProjectInOrg(client, projectId, ctx.orgId);
27792
- const assumptions = await client.getKeyAssumptions(projectId);
27790
+ async ({ systemId }) => {
27791
+ await assertProjectInOrg(client, systemId, ctx.orgId);
27792
+ const assumptions = await client.getKeyAssumptions(systemId);
27793
27793
  if (assumptions.length === 0) {
27794
27794
  return { content: [{ type: "text", text: "No unvalidated assumptions found." }] };
27795
27795
  }
@@ -28051,12 +28051,12 @@ ${formatTree(tree.root)}` }] };
28051
28051
  heading: external_exports.string().min(1).max(200).describe('Section label, e.g. "Decision Log"'),
28052
28052
  parentNoteId: external_exports.string().optional().describe("Parent page's Note ID; defaults to the notebook root"),
28053
28053
  noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("Note type to collect (default: decision)"),
28054
- projectId: external_exports.string().optional().describe("Project scope; if omitted, the Notebook's first bound project scope is used"),
28054
+ systemId: external_exports.string().optional().describe("Project scope; if omitted, the Notebook's first bound project scope is used"),
28055
28055
  limit: external_exports.number().int().min(1).max(500).optional().describe("Max members to show (default: 25)"),
28056
28056
  order: external_exports.enum(["newest", "oldest"]).optional().describe("Sort order (default: newest)"),
28057
28057
  significance: external_exports.enum(["strategic", "structural", "implementation"]).optional().describe("Only for decision notes \u2014 filter to a significance tier (e.g. strategic). Omit to include all.")
28058
28058
  },
28059
- async ({ notebookId, heading, parentNoteId, noteType, projectId, limit, order, significance }) => {
28059
+ async ({ notebookId, heading, parentNoteId, noteType, systemId, limit, order, significance }) => {
28060
28060
  let notebook;
28061
28061
  try {
28062
28062
  notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
@@ -28069,7 +28069,7 @@ ${formatTree(tree.root)}` }] };
28069
28069
  isError: true
28070
28070
  };
28071
28071
  }
28072
- let scopeProjectId = projectId;
28072
+ let scopeProjectId = systemId;
28073
28073
  if (!scopeProjectId) {
28074
28074
  try {
28075
28075
  const bindings = await client.getNotebookScopeBindings(notebookId);
@@ -28078,7 +28078,7 @@ ${formatTree(tree.root)}` }] };
28078
28078
  }
28079
28079
  }
28080
28080
  if (!scopeProjectId) {
28081
- return { content: [{ type: "text", text: "No project scope: pass projectId or bind the notebook to a project first." }], isError: true };
28081
+ return { content: [{ type: "text", text: "No project scope: pass systemId or bind the notebook to a project first." }], isError: true };
28082
28082
  }
28083
28083
  let tree;
28084
28084
  try {
@@ -28139,12 +28139,12 @@ ${formatTree(tree.root)}` }] };
28139
28139
  parentNoteId: external_exports.string().optional().describe("The page Note ID the section sits under; defaults to the notebook root"),
28140
28140
  heading: external_exports.string().min(1).max(200).optional().describe("New section label"),
28141
28141
  noteType: external_exports.enum(["context", "constraint", "guidance", "decision", "document"]).optional().describe("New note type to collect"),
28142
- projectId: external_exports.string().optional().describe("New project scope"),
28142
+ systemId: external_exports.string().optional().describe("New project scope"),
28143
28143
  limit: external_exports.number().int().min(1).max(500).optional().describe("New max members to show"),
28144
28144
  order: external_exports.enum(["newest", "oldest"]).optional().describe("New sort order"),
28145
28145
  significance: external_exports.enum(["strategic", "structural", "implementation"]).optional().describe("New significance-tier filter (decision notes only)")
28146
28146
  },
28147
- async ({ notebookId, elementId, parentNoteId, heading, noteType, projectId, limit, order, significance }) => {
28147
+ async ({ notebookId, elementId, parentNoteId, heading, noteType, systemId, limit, order, significance }) => {
28148
28148
  let notebook;
28149
28149
  try {
28150
28150
  notebook = await fetchNotebookInOrg(client, notebookId, ctx.orgId);
@@ -28183,7 +28183,7 @@ ${formatTree(tree.root)}` }] };
28183
28183
  const base = existing.query;
28184
28184
  const mergedQuery = {
28185
28185
  source: "notes",
28186
- scope: projectId !== void 0 ? `project:${projectId}` : base.scope,
28186
+ scope: systemId !== void 0 ? `project:${systemId}` : base.scope,
28187
28187
  noteType: noteType ?? base.noteType,
28188
28188
  limit: limit ?? base.limit,
28189
28189
  sortBy: order !== void 0 ? order === "oldest" ? "createdAt_asc" : "createdAt_desc" : base.sortBy,
@@ -28485,14 +28485,14 @@ function registerOnboardingTools(server, ctx, client) {
28485
28485
  "The PM reviews and confirms \u2014 use create_beat for each approved Beat, create_note for any Note conversions."
28486
28486
  ].join(" "),
28487
28487
  {
28488
- projectId: external_exports.string().describe("The newly created project ID"),
28488
+ systemId: external_exports.string().describe("The newly created project ID"),
28489
28489
  projectTitle: external_exports.string().describe("The project title"),
28490
28490
  projectDescription: external_exports.string().optional().describe("Discovery context: pain points, goals, and constraints summary to inform the Beat structure"),
28491
28491
  analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
28492
28492
  },
28493
- async ({ projectId, projectTitle, projectDescription, analysisJson }) => {
28494
- await assertProjectInOrg(client, projectId, ctx.orgId);
28495
- const result = await client.draftOnboardingBeats(projectId, { projectTitle, projectDescription, analysisJson });
28493
+ async ({ systemId, projectTitle, projectDescription, analysisJson }) => {
28494
+ await assertProjectInOrg(client, systemId, ctx.orgId);
28495
+ const result = await client.draftOnboardingBeats(systemId, { projectTitle, projectDescription, analysisJson });
28496
28496
  if (!result.suggestions || result.suggestions.length === 0) {
28497
28497
  return {
28498
28498
  content: [{ type: "text", text: "Failed to generate Beat suggestions. Try providing more project context." }]
@@ -28558,14 +28558,14 @@ Error: ${msg}`);
28558
28558
  for (const p of projects) {
28559
28559
  try {
28560
28560
  const project = await client.createSystem({
28561
- projectId: (0, import_node_crypto.randomUUID)(),
28561
+ systemId: (0, import_node_crypto.randomUUID)(),
28562
28562
  orgId: ctx.orgId,
28563
28563
  teamspaceId: teamspace.teamspaceId,
28564
28564
  ownerUserId: ctx.user?.userId ?? "system",
28565
28565
  title: p.title,
28566
28566
  description: p.description
28567
28567
  });
28568
- createdProjects.push({ projectId: project.projectId, title: project.title });
28568
+ createdProjects.push({ systemId: project.systemId, title: project.title });
28569
28569
  } catch (err) {
28570
28570
  const msg = err instanceof Error ? err.message : String(err);
28571
28571
  lines.push(`
@@ -28578,7 +28578,7 @@ Error: ${msg}`);
28578
28578
  if (createdProjects.length > 0) {
28579
28579
  lines.push(`
28580
28580
  Projects (${createdProjects.length}):`);
28581
- for (const p of createdProjects) lines.push(` [${p.projectId}] ${p.title}`);
28581
+ for (const p of createdProjects) lines.push(` [${p.systemId}] ${p.title}`);
28582
28582
  }
28583
28583
  const createdNotes = [];
28584
28584
  const teamspaceSlug = teamspace.slug ?? teamspaceName.toLowerCase().replace(/\s+/g, "-");
@@ -28604,7 +28604,7 @@ Projects (${createdProjects.length}):`);
28604
28604
  const n = await client.createNote({
28605
28605
  noteId: `N-TEMP-${(0, import_node_crypto.randomUUID)()}`,
28606
28606
  // overridden server-side
28607
- projectId: project.projectId,
28607
+ projectId: project.systemId,
28608
28608
  noteType: note.noteType,
28609
28609
  content: note.content,
28610
28610
  rationale: note.rationale,
@@ -28802,7 +28802,7 @@ function registerOrganizationTools(server, ctx, client) {
28802
28802
  }
28803
28803
  const text = `System transferred.
28804
28804
 
28805
- **System ID:** ${transferred.projectId}
28805
+ **System ID:** ${transferred.systemId}
28806
28806
  **Title:** ${transferred.title}
28807
28807
  **New Org:** ${transferred.orgId}`;
28808
28808
  return { content: [{ type: "text", text }] };
@@ -28926,7 +28926,7 @@ ${d.rationale}${d.suggestions?.length ? `
28926
28926
  function registerPlanQualityTools(server, ctx, client) {
28927
28927
  const schema = {
28928
28928
  beatVersionId: external_exports.string().describe("The Beat Version ID (e.g., bv-abc123). beat_version_quality targets Beat Versions only."),
28929
- projectId: external_exports.string().describe("The project ID"),
28929
+ systemId: external_exports.string().describe("The project ID"),
28930
28930
  // The completeness dimension probes the codebase, so this tool accepts the
28931
28931
  // same repo overrides as run_check — without them the probe could only ever
28932
28932
  // read the default branch from this surface.
@@ -28934,7 +28934,7 @@ function registerPlanQualityTools(server, ctx, client) {
28934
28934
  localPath: external_exports.string().optional().describe("Absolute path to an already-cloned local copy of the repo \u2014 the completeness probe reads it instead of cloning"),
28935
28935
  wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
28936
28936
  };
28937
- const handler = async ({ beatVersionId, projectId, branch, localPath, wait }) => {
28937
+ const handler = async ({ beatVersionId, systemId, branch, localPath, wait }) => {
28938
28938
  try {
28939
28939
  const targetError = beatVersionQualityTargetError(beatVersionId ?? "");
28940
28940
  if (targetError) {
@@ -28943,9 +28943,9 @@ function registerPlanQualityTools(server, ctx, client) {
28943
28943
  isError: true
28944
28944
  };
28945
28945
  }
28946
- await assertProjectInOrg(client, projectId, ctx.orgId);
28946
+ await assertProjectInOrg(client, systemId, ctx.orgId);
28947
28947
  const opts = branch || localPath ? { branch, localPath } : void 0;
28948
- const task = await client.runCheck(projectId, "beat_version_quality", beatVersionId, opts);
28948
+ const task = await client.runCheck(systemId, "beat_version_quality", beatVersionId, opts);
28949
28949
  if (!wait) {
28950
28950
  return {
28951
28951
  content: [{
@@ -29085,22 +29085,22 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29085
29085
  "check_portfolio_coherence",
29086
29086
  "Run a portfolio coherence check on a project. Clusters Beats by semantic similarity to surface fragmentation (merge candidates) and checks what fraction of Beats name an outcome their authoriser would recognise. By default returns a taskId immediately \u2014 set wait=true to block and receive the full report inline.",
29087
29087
  {
29088
- projectId: external_exports.string().min(1).describe("The project ID"),
29088
+ systemId: external_exports.string().min(1).describe("The project ID"),
29089
29089
  wait: external_exports.boolean().optional().describe("Block until the check completes and return the report inline. Default: false.")
29090
29090
  },
29091
- async ({ projectId, wait }) => {
29091
+ async ({ systemId, wait }) => {
29092
29092
  try {
29093
- await assertProjectInOrg(client, projectId, ctx.orgId);
29094
- const { taskId } = await client.runCheck(projectId, "portfolio_coherence", projectId);
29093
+ await assertProjectInOrg(client, systemId, ctx.orgId);
29094
+ const { taskId } = await client.runCheck(systemId, "portfolio_coherence", systemId);
29095
29095
  if (!wait) {
29096
29096
  return {
29097
29097
  content: [{
29098
29098
  type: "text",
29099
29099
  text: [
29100
- `Portfolio coherence check enqueued for ${projectId}.`,
29100
+ `Portfolio coherence check enqueued for ${systemId}.`,
29101
29101
  `Task ID: ${taskId}`,
29102
29102
  "",
29103
- `Use get_job_status with jobId="${taskId}" to poll, or list_checks with projectId="${projectId}" and checkType="portfolio_coherence" to view results.`
29103
+ `Use get_job_status with jobId="${taskId}" to poll, or list_checks with systemId="${systemId}" and checkType="portfolio_coherence" to view results.`
29104
29104
  ].join("\n")
29105
29105
  }]
29106
29106
  };
@@ -29117,17 +29117,17 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29117
29117
  "consolidate_beats",
29118
29118
  "Consolidate a fragmented Beat cluster \u2014 identified by check_portfolio_coherence \u2014 into a single VP-level capability. By default (confirm omitted or false) returns a dry-run preview: surviving Beat, Beats to archive, Notes to reassign, and any in-flight Beat Versions. Set confirm=true to execute the merge.",
29119
29119
  {
29120
- projectId: external_exports.string().min(1).describe("The project ID \u2014 all beatIds must belong to this project"),
29120
+ systemId: external_exports.string().min(1).describe("The project ID \u2014 all beatIds must belong to this project"),
29121
29121
  beatIds: external_exports.array(external_exports.string().min(1)).min(2).max(50).describe("All Beat IDs in the cluster (including the one that will survive)"),
29122
29122
  primaryBeatId: external_exports.string().min(1).optional().describe("Beat ID that survives \u2014 defaults to beatIds[0] if omitted"),
29123
29123
  rationale: external_exports.string().max(2e3).optional().describe("Why these Beats are being merged \u2014 auto-generated if omitted"),
29124
29124
  confirm: external_exports.boolean().optional().describe("false/omitted = preview; true = execute the merge")
29125
29125
  },
29126
- async ({ projectId, beatIds, primaryBeatId, rationale, confirm }) => {
29126
+ async ({ systemId, beatIds, primaryBeatId, rationale, confirm }) => {
29127
29127
  const archivedBeatIds = [];
29128
29128
  let totalNotesReassigned = 0;
29129
29129
  try {
29130
- await assertProjectInOrg(client, projectId, ctx.orgId);
29130
+ await assertProjectInOrg(client, systemId, ctx.orgId);
29131
29131
  const uniqueBeatIds = [...new Set(beatIds)];
29132
29132
  const resolvedPrimaryId = primaryBeatId ?? uniqueBeatIds[0];
29133
29133
  if (!uniqueBeatIds.includes(resolvedPrimaryId)) {
@@ -29143,19 +29143,19 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29143
29143
  isError: true
29144
29144
  };
29145
29145
  }
29146
- const allProjectBeats = await client.listSystemBeats(projectId);
29146
+ const allProjectBeats = await client.listSystemBeats(systemId);
29147
29147
  const beatMap = new Map(allProjectBeats.map((b) => [b.beatId, b]));
29148
29148
  const primaryBeat = beatMap.get(resolvedPrimaryId);
29149
29149
  if (!primaryBeat) {
29150
- return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${projectId}".` }], isError: true };
29150
+ return { content: [{ type: "text", text: `Primary Beat "${resolvedPrimaryId}" not found in project "${systemId}".` }], isError: true };
29151
29151
  }
29152
29152
  const missingIds = duplicateBeatIds.filter((id) => !beatMap.has(id));
29153
29153
  if (missingIds.length > 0) {
29154
- return { content: [{ type: "text", text: `Beat(s) not found in project "${projectId}": ${missingIds.join(", ")}` }], isError: true };
29154
+ return { content: [{ type: "text", text: `Beat(s) not found in project "${systemId}": ${missingIds.join(", ")}` }], isError: true };
29155
29155
  }
29156
29156
  const dupBvResults = await Promise.allSettled(
29157
29157
  duplicateBeatIds.map(
29158
- (id) => client.listBeatVersions(projectId, { beatId: id, includeTerminal: false })
29158
+ (id) => client.listBeatVersions(systemId, { beatId: id, includeTerminal: false })
29159
29159
  )
29160
29160
  );
29161
29161
  const bvRejected = dupBvResults.filter((r) => r.status === "rejected");
@@ -29328,11 +29328,11 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29328
29328
  if (!parentBv) {
29329
29329
  return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
29330
29330
  }
29331
- if (parentBv.beatId !== beatId || parentBv.projectId !== beat.projectId) {
29331
+ if (parentBv.beatId !== beatId || parentBv.systemId !== beat.systemId) {
29332
29332
  return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
29333
29333
  }
29334
29334
  }
29335
- const { revision, warnings } = await client.createRevision(beat.projectId, beatId, {
29335
+ const { revision, warnings } = await client.createRevision(beat.systemId, beatId, {
29336
29336
  title,
29337
29337
  description,
29338
29338
  changeSummary,
@@ -29364,7 +29364,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29364
29364
  }
29365
29365
  if (parentBv && !parentBv.targetDropId) {
29366
29366
  try {
29367
- const project = await client.getSystem(parentBv.projectId);
29367
+ const project = await client.getSystem(parentBv.systemId);
29368
29368
  const accountId = project?.accountId;
29369
29369
  if (accountId) {
29370
29370
  const draftDrops = await client.listAccountDrops(accountId, { state: "draft" });
@@ -29420,7 +29420,7 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29420
29420
  if (!bv) {
29421
29421
  return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
29422
29422
  }
29423
- if (bv.beatId !== revision.beatId || bv.projectId !== revision.projectId) {
29423
+ if (bv.beatId !== revision.beatId || bv.systemId !== revision.systemId) {
29424
29424
  return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
29425
29425
  }
29426
29426
  }
@@ -29589,7 +29589,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29589
29589
  if (revision.scm) {
29590
29590
  lines2.push("", `\u26A0 This revision already has a linked PR (#${revision.scm.number} \u2014 ${revision.scm.url}). create_pr_for_revision would be rejected.`);
29591
29591
  } else {
29592
- const project = await client.getSystem(revision.projectId);
29592
+ const project = await client.getSystem(revision.systemId);
29593
29593
  if (!project?.repoOwner || !project?.repoName || !project?.repoDefaultBranch) {
29594
29594
  lines2.push("", "\u26A0 The project has no repo configured (repoOwner/repoName/repoDefaultBranch). create_pr_for_revision would be rejected.");
29595
29595
  } else {
@@ -29852,10 +29852,10 @@ function registerRevisionQualityTools(server, ctx, client) {
29852
29852
  "Run a five-dimension PR-readiness check on a Revision (Scoped, Traceable, Testable, Review-ready, Non-duplicating). Advisory only \u2014 no lifecycle transition or creation path gates on this score. By default returns a jobId immediately (fire-and-forget) \u2014 use get_job_status or list_checks to retrieve results. Set wait=true to block until the check completes and receive the scorecard inline. WARNING: wait=true holds the MCP session open for 30\u201360s \u2014 for bulk operations, omit wait and poll get_job_status separately.",
29853
29853
  {
29854
29854
  revisionId: external_exports.string().describe("The Revision ID (e.g., rev-abc123). revision_quality targets Revisions only."),
29855
- projectId: external_exports.string().describe("The project ID"),
29855
+ systemId: external_exports.string().describe("The project ID"),
29856
29856
  wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
29857
29857
  },
29858
- async ({ revisionId, projectId, wait }) => {
29858
+ async ({ revisionId, systemId, wait }) => {
29859
29859
  try {
29860
29860
  const targetError = revisionQualityTargetError(revisionId);
29861
29861
  if (targetError) {
@@ -29864,9 +29864,9 @@ function registerRevisionQualityTools(server, ctx, client) {
29864
29864
  isError: true
29865
29865
  };
29866
29866
  }
29867
- await assertProjectInOrg(client, projectId, ctx.orgId);
29868
- await assertRevisionInProject(client, revisionId, projectId);
29869
- const task = await client.runCheck(projectId, "revision_quality", revisionId);
29867
+ await assertProjectInOrg(client, systemId, ctx.orgId);
29868
+ await assertRevisionInProject(client, revisionId, systemId);
29869
+ const task = await client.runCheck(systemId, "revision_quality", revisionId);
29870
29870
  if (!wait) {
29871
29871
  return {
29872
29872
  content: [{
@@ -30330,10 +30330,10 @@ async function resolveSnapshotInput(input) {
30330
30330
  }
30331
30331
  function formatImportSummary(result, targetTeamspaceId) {
30332
30332
  if (!result.counts || result.counts.beats == null) {
30333
- throw new Error(`Import of system ${result.projectId} completed but returned no counts \u2014 check worker logs for a TransactionCanceledException`);
30333
+ throw new Error(`Import of system ${result.systemId} completed but returned no counts \u2014 check worker logs for a TransactionCanceledException`);
30334
30334
  }
30335
30335
  const lines = [
30336
- `Imported system: ${result.projectId}`,
30336
+ `Imported system: ${result.systemId}`,
30337
30337
  ` Beats: ${result.counts.beats}`,
30338
30338
  ` Proposals: ${result.counts.proposals}`,
30339
30339
  ` Revisions: ${result.counts.revisions}`,
@@ -30400,8 +30400,8 @@ function assertSnapshotShape(val, sourceHint) {
30400
30400
  throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
30401
30401
  }
30402
30402
  const systemOrProject = obj["system"] ?? obj["project"];
30403
- if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["projectId"] !== "string") {
30404
- throw new Error(`Snapshot${sourceHint} is missing 'system.projectId' \u2014 it may not be a valid project snapshot.`);
30403
+ if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["systemId"] !== "string") {
30404
+ throw new Error(`Snapshot${sourceHint} is missing 'system.systemId' \u2014 it may not be a valid project snapshot.`);
30405
30405
  }
30406
30406
  }
30407
30407
 
@@ -30414,12 +30414,12 @@ function registerSubscriptionTools(server, ctx, client) {
30414
30414
  {
30415
30415
  entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
30416
30416
  entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
30417
- projectId: external_exports.string().describe("The project ID (for access control)")
30417
+ systemId: external_exports.string().describe("The project ID (for access control)")
30418
30418
  },
30419
- async ({ entityType, entityId, projectId }) => {
30419
+ async ({ entityType, entityId, systemId }) => {
30420
30420
  try {
30421
- await assertProjectInOrg(client, projectId, ctx.orgId);
30422
- await client.subscribe(ctx.user.userId, entityType, entityId, projectId, "manual");
30421
+ await assertProjectInOrg(client, systemId, ctx.orgId);
30422
+ await client.subscribe(ctx.user.userId, entityType, entityId, systemId, "manual");
30423
30423
  return {
30424
30424
  content: [{
30425
30425
  type: "text",
@@ -30653,7 +30653,7 @@ function registerProjectTools(server, ctx, client) {
30653
30653
  const lines = [
30654
30654
  `System updated successfully.`,
30655
30655
  "",
30656
- `**ID:** ${updated.projectId}`,
30656
+ `**ID:** ${updated.systemId}`,
30657
30657
  `**Title:** ${updated.title}`,
30658
30658
  accountLine(nonEmpty, updated.accountId),
30659
30659
  updated.strategy ? `**Strategy:** (updated)` : "",
@@ -30690,7 +30690,7 @@ function registerProjectTools(server, ctx, client) {
30690
30690
  text: [
30691
30691
  "System archived successfully.",
30692
30692
  "",
30693
- `**ID:** ${updated.projectId}`,
30693
+ `**ID:** ${updated.systemId}`,
30694
30694
  `**Title:** ${updated.title}`,
30695
30695
  `**Status:** ${updated.status}`
30696
30696
  ].join("\n")
@@ -30725,7 +30725,7 @@ function registerProjectTools(server, ctx, client) {
30725
30725
  const account = normalizeCreateAccountId(accountId);
30726
30726
  try {
30727
30727
  const project = await client.createSystem({
30728
- projectId: (0, import_crypto5.randomUUID)(),
30728
+ systemId: (0, import_crypto5.randomUUID)(),
30729
30729
  orgId: ctx.orgId,
30730
30730
  ownerUserId: ctx.user.userId,
30731
30731
  title,
@@ -30741,7 +30741,7 @@ function registerProjectTools(server, ctx, client) {
30741
30741
  const text = [
30742
30742
  `System created successfully.`,
30743
30743
  "",
30744
- `**ID:** ${project.projectId}`,
30744
+ `**ID:** ${project.systemId}`,
30745
30745
  `**Title:** ${project.title}`,
30746
30746
  `**Status:** ${project.status}`,
30747
30747
  project.accountId ? `**Account:** ${project.accountId}` : "",
@@ -30811,7 +30811,7 @@ function registerTeamspaceTools(server, ctx, client) {
30811
30811
  ];
30812
30812
  if (tsProjects.length > 0) {
30813
30813
  lines.push("", `Projects (${tsProjects.length}):`);
30814
- for (const p of tsProjects) lines.push(` [${p.projectId}] ${p.title} \u2014 ${p.status ?? "unknown"}`);
30814
+ for (const p of tsProjects) lines.push(` [${p.systemId}] ${p.title} \u2014 ${p.status ?? "unknown"}`);
30815
30815
  } else {
30816
30816
  lines.push("Projects: (none)");
30817
30817
  }
@@ -31073,23 +31073,23 @@ function registerValueVelocityTools(server, ctx, client) {
31073
31073
  "propose_enablement_edges",
31074
31074
  'Propose directed enablement edges for a project \u2014 "Beat A accelerates Beat B". The LLM analyzes project Beats and proposes which ones unlock or accelerate others. Only proposed edges are created; a human must confirm before they affect Value Velocity scores. Pass beatId to scope the pass to edges FROM one Beat \u2014 strongly recommended on a large portfolio, where an unscoped pass can exceed the request timeout. If you already know the edge you want, skip proposing and use create_enablement_edge instead.',
31075
31075
  {
31076
- projectId: external_exports.string().describe("The project ID"),
31076
+ systemId: external_exports.string().describe("The project ID"),
31077
31077
  beatId: external_exports.string().min(1).optional().describe("Scope the pass to edges FROM this Beat. Omit for a whole-project pass (slow on large portfolios).")
31078
31078
  },
31079
- async ({ projectId, beatId }) => {
31079
+ async ({ systemId, beatId }) => {
31080
31080
  try {
31081
- await assertProjectInOrg(client, projectId, ctx.orgId);
31081
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31082
31082
  const actorId = resolveActorId(ctx);
31083
31083
  if (!actorId) {
31084
31084
  return { content: [{ type: "text", text: "Actor identity unavailable: user has no email or userId." }], isError: true };
31085
31085
  }
31086
31086
  if (beatId !== void 0) await assertBeatInOrg(client, beatId, ctx.orgId);
31087
- const proposed = await client.proposeEnablementEdges(projectId, actorId, beatId);
31087
+ const proposed = await client.proposeEnablementEdges(systemId, actorId, beatId);
31088
31088
  if (proposed.length === 0) {
31089
31089
  return { content: [{ type: "text", text: "No enablement edges proposed (fewer than 2 Beats or no relationships found)." }] };
31090
31090
  }
31091
31091
  const lines = [
31092
- `Proposed ${proposed.length} enablement edge(s) for project ${projectId}${beatId ? ` scoped to ${beatId}` : ""}:`,
31092
+ `Proposed ${proposed.length} enablement edge(s) for project ${systemId}${beatId ? ` scoped to ${beatId}` : ""}:`,
31093
31093
  "",
31094
31094
  "| Source Beat | Target Beat | Rationale |",
31095
31095
  "|-------------|------------|-----------|"
@@ -31461,13 +31461,13 @@ ${JSON.stringify(task.result, null, 2)}
31461
31461
  "list_tasks",
31462
31462
  "DEPRECATED: use `list_jobs`. List background jobs for a project with optional status filter.",
31463
31463
  {
31464
- projectId: external_exports.string().describe("The project ID"),
31464
+ systemId: external_exports.string().describe("The project ID"),
31465
31465
  status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
31466
31466
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31467
31467
  },
31468
- async ({ projectId, status, limit }) => {
31469
- await assertProjectInOrg(client, projectId, ctx.orgId);
31470
- const tasks = await client.listSystemTasks(projectId, {
31468
+ async ({ systemId, status, limit }) => {
31469
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31470
+ const tasks = await client.listSystemTasks(systemId, {
31471
31471
  status,
31472
31472
  limit
31473
31473
  });
@@ -31519,13 +31519,13 @@ ${JSON.stringify(task.result, null, 2)}
31519
31519
  "list_jobs",
31520
31520
  "List background jobs for a project with optional status filter",
31521
31521
  {
31522
- projectId: external_exports.string().describe("The project ID"),
31522
+ systemId: external_exports.string().describe("The project ID"),
31523
31523
  status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
31524
31524
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31525
31525
  },
31526
- async ({ projectId, status, limit }) => {
31527
- await assertProjectInOrg(client, projectId, ctx.orgId);
31528
- const tasks = await client.listSystemTasks(projectId, {
31526
+ async ({ systemId, status, limit }) => {
31527
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31528
+ const tasks = await client.listSystemTasks(systemId, {
31529
31529
  status,
31530
31530
  limit
31531
31531
  });
@@ -31543,13 +31543,13 @@ ${JSON.stringify(task.result, null, 2)}
31543
31543
  "draft_coda",
31544
31544
  "Generate and apply a Coda (description) for a Beat that has a title but no description. Uses project context, sibling Beats, related Notes, and codebase grep to write a resolved expression of the capability. The Coda is applied directly to the Beat, making it eligible for plan_beat_versions once a beat_quality check passes. An activity is logged so the Beat owner can review and edit. Returns a job ID \u2014 use get_job_status to track progress.",
31545
31545
  {
31546
- projectId: external_exports.string().describe("The project ID"),
31546
+ systemId: external_exports.string().describe("The project ID"),
31547
31547
  beatId: external_exports.string().describe("The beat ID (must have a title but no description)")
31548
31548
  },
31549
- async ({ projectId, beatId }) => {
31549
+ async ({ systemId, beatId }) => {
31550
31550
  try {
31551
- await assertProjectInOrg(client, projectId, ctx.orgId);
31552
- const task = await client.draftCoda(projectId, beatId);
31551
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31552
+ const task = await client.draftCoda(systemId, beatId);
31553
31553
  return {
31554
31554
  content: [{
31555
31555
  type: "text",
@@ -31557,7 +31557,7 @@ ${JSON.stringify(task.result, null, 2)}
31557
31557
  "Coda draft session queued.",
31558
31558
  "",
31559
31559
  `**Beat:** ${beatId}`,
31560
- `**Project:** ${projectId}`,
31560
+ `**Project:** ${systemId}`,
31561
31561
  `**Task ID:** ${task.taskId}`,
31562
31562
  `**Status:** ${task.status}`,
31563
31563
  "",
@@ -31576,20 +31576,20 @@ ${JSON.stringify(task.result, null, 2)}
31576
31576
  "validate_assumptions",
31577
31577
  "Validate unvalidated assumption Notes against the codebase. Checks if assumptions are confirmed (validated), contradicted (invalidated), or have insufficient evidence (unvalidated). Low-criticality assumptions are auto-resolved; high-criticality are flagged for human review. Returns a job ID \u2014 use get_job_status to track progress.",
31578
31578
  {
31579
- projectId: external_exports.string().describe("The project ID"),
31579
+ systemId: external_exports.string().describe("The project ID"),
31580
31580
  noteIds: external_exports.array(external_exports.string()).optional().describe("Specific assumption note IDs to validate. If omitted, validates all unvalidated assumptions.")
31581
31581
  },
31582
- async ({ projectId, noteIds }) => {
31582
+ async ({ systemId, noteIds }) => {
31583
31583
  try {
31584
- await assertProjectInOrg(client, projectId, ctx.orgId);
31585
- const task = await client.validateAssumptions(projectId, noteIds);
31584
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31585
+ const task = await client.validateAssumptions(systemId, noteIds);
31586
31586
  return {
31587
31587
  content: [{
31588
31588
  type: "text",
31589
31589
  text: [
31590
31590
  "Assumption validation queued.",
31591
31591
  "",
31592
- `**Project:** ${projectId}`,
31592
+ `**Project:** ${systemId}`,
31593
31593
  `**Scope:** ${noteIds ? `${noteIds.length} specific assumptions` : "All unvalidated assumptions"}`,
31594
31594
  `**Task ID:** ${task.taskId}`,
31595
31595
  `**Status:** ${task.status}`,
@@ -31610,15 +31610,15 @@ ${JSON.stringify(task.result, null, 2)}
31610
31610
  "start_session",
31611
31611
  'Start an agent session from a free-form message \u2014 like opening a Claude Code conversation pointed at your project. The message is the only instruction: describe what you want ("investigate why login fails", "implement revision rev-abc123", "what does the auth flow do?") and the agent resolves any entity references itself and does whatever the work needs \u2014 investigate and report findings, or change code and open a PR. A findings-only result with no PR is a valid outcome. Returns a job ID \u2014 use get_job_status to track progress. Requires agent sessions enabled on the project.',
31612
31612
  {
31613
- projectId: external_exports.string().min(1).describe("The project ID \u2014 the trusted scope the session is rooted at"),
31613
+ systemId: external_exports.string().min(1).describe("The project ID \u2014 the trusted scope the session is rooted at"),
31614
31614
  message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
31615
31615
  revisionId: external_exports.string().min(1).optional().describe("Optional Revision ID (rev-<uuid>). When provided, the Worker routes to the typed implementation session path (prepareRevisionImplementationSession), enabling file hint generation and richer session context. Omit for investigation, planning, or freeform sessions.")
31616
31616
  },
31617
- async ({ projectId, message, revisionId }) => {
31617
+ async ({ systemId, message, revisionId }) => {
31618
31618
  try {
31619
- await assertProjectInOrg(client, projectId, ctx.orgId);
31619
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31620
31620
  const task = await client.startSession(
31621
- projectId,
31621
+ systemId,
31622
31622
  message,
31623
31623
  { name: ctx.user.name, email: ctx.user.email },
31624
31624
  revisionId?.trim() ? { revisionId: revisionId.trim() } : void 0
@@ -31629,7 +31629,7 @@ ${JSON.stringify(task.result, null, 2)}
31629
31629
  text: [
31630
31630
  "Agent session queued.",
31631
31631
  "",
31632
- `**Project:** ${projectId}`,
31632
+ `**Project:** ${systemId}`,
31633
31633
  `**Task ID:** ${task.taskId}`,
31634
31634
  `**Status:** ${task.status}`,
31635
31635
  "",
@@ -31648,20 +31648,20 @@ ${JSON.stringify(task.result, null, 2)}
31648
31648
  "plan_revision_batch",
31649
31649
  "Autonomously generate an implementation plan for planning Revisions. If revisionIds are omitted, discovers all unplanned planning Revisions and plans them all. Uses codebase-aware agent sessions when available, LLM-only fallback otherwise. Returns a job ID \u2014 use get_job_status to track progress.",
31650
31650
  {
31651
- projectId: external_exports.string().describe("The project ID"),
31651
+ systemId: external_exports.string().describe("The project ID"),
31652
31652
  revisionIds: external_exports.array(external_exports.string()).optional().describe("Specific revision IDs to plan. If omitted, auto-discovers all unplanned planning revisions.")
31653
31653
  },
31654
- async ({ projectId, revisionIds }) => {
31654
+ async ({ systemId, revisionIds }) => {
31655
31655
  try {
31656
- await assertProjectInOrg(client, projectId, ctx.orgId);
31657
- const task = await client.planRevisionBatch(projectId, revisionIds);
31656
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31657
+ const task = await client.planRevisionBatch(systemId, revisionIds);
31658
31658
  return {
31659
31659
  content: [{
31660
31660
  type: "text",
31661
31661
  text: [
31662
31662
  "Revision batch planning queued.",
31663
31663
  "",
31664
- `**Project:** ${projectId}`,
31664
+ `**Project:** ${systemId}`,
31665
31665
  `**Scope:** ${revisionIds ? `${revisionIds.length} specific revision(s)` : "All unplanned planning revisions"}`,
31666
31666
  `**Task ID:** ${task.taskId}`,
31667
31667
  `**Status:** ${task.status}`,
@@ -31681,19 +31681,19 @@ ${JSON.stringify(task.result, null, 2)}
31681
31681
  "run_triage",
31682
31682
  "Run the autonomous triage loop for a project. Analyzes next actions, auto-executes safe actions (draft_coda, plan_revision_batch), and escalates items that need human judgment. Requires agentCapabilities.triage enabled on the project. Returns a job ID \u2014 use get_job_status to track progress.",
31683
31683
  {
31684
- projectId: external_exports.string().describe("The project ID")
31684
+ systemId: external_exports.string().describe("The project ID")
31685
31685
  },
31686
- async ({ projectId }) => {
31686
+ async ({ systemId }) => {
31687
31687
  try {
31688
- await assertProjectInOrg(client, projectId, ctx.orgId);
31689
- const task = await client.runTriage(projectId);
31688
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31689
+ const task = await client.runTriage(systemId);
31690
31690
  return {
31691
31691
  content: [{
31692
31692
  type: "text",
31693
31693
  text: [
31694
31694
  "Triage loop queued.",
31695
31695
  "",
31696
- `**Project:** ${projectId}`,
31696
+ `**Project:** ${systemId}`,
31697
31697
  `**Task ID:** ${task.taskId}`,
31698
31698
  `**Status:** ${task.status}`,
31699
31699
  "",
@@ -31724,15 +31724,15 @@ ${JSON.stringify(task.result, null, 2)}
31724
31724
  "Returns a job ID \u2014 use `get_job_status` to track progress."
31725
31725
  ].join("\n"),
31726
31726
  {
31727
- projectId: external_exports.string().describe("The project ID"),
31727
+ systemId: external_exports.string().describe("The project ID"),
31728
31728
  content: external_exports.string().describe('Document content \u2014 plain text (default) or base64-encoded binary (when encoding is "base64")'),
31729
31729
  filename: external_exports.string().optional().default("document.txt").describe('Original filename with extension (e.g., "sow.pdf", "spec.docx"). Used for format detection in binary mode.'),
31730
31730
  encoding: external_exports.enum(["base64"]).optional().describe('Set to "base64" when submitting binary files (DOCX, XLSX, PPTX, PDF). Omit for plain text.')
31731
31731
  },
31732
- async ({ projectId, content, filename, encoding }) => {
31732
+ async ({ systemId, content, filename, encoding }) => {
31733
31733
  try {
31734
- await assertProjectInOrg(client, projectId, ctx.orgId);
31735
- const result = encoding === "base64" ? await client.analyzeBinaryDocument(projectId, content, filename) : await client.analyzeDocument(projectId, content, filename);
31734
+ await assertProjectInOrg(client, systemId, ctx.orgId);
31735
+ const result = encoding === "base64" ? await client.analyzeBinaryDocument(systemId, content, filename) : await client.analyzeDocument(systemId, content, filename);
31736
31736
  return {
31737
31737
  content: [{
31738
31738
  type: "text",
@@ -32176,7 +32176,7 @@ function createHttpClient(config2) {
32176
32176
  );
32177
32177
  const project = result?.project ? {
32178
32178
  ...result.project,
32179
- projectId: result.project.id ?? result.project.projectId,
32179
+ systemId: result.project.id ?? result.project.systemId ?? result.project.projectId,
32180
32180
  title: result.project.name ?? result.project.title,
32181
32181
  orgId: result.project.organizationId ?? result.project.orgId
32182
32182
  } : void 0;
@@ -32196,7 +32196,7 @@ function createHttpClient(config2) {
32196
32196
  if (!raw) return void 0;
32197
32197
  return {
32198
32198
  ...raw,
32199
- projectId: raw.id ?? raw.projectId,
32199
+ systemId: raw.id ?? raw.systemId ?? raw.projectId,
32200
32200
  title: raw.name ?? raw.title,
32201
32201
  orgId: raw.organizationId ?? raw.orgId
32202
32202
  };
@@ -32212,17 +32212,17 @@ function createHttpClient(config2) {
32212
32212
  // is created unlinked — invisible in the Account's Systems list.
32213
32213
  ...input.accountId !== void 0 && { accountId: input.accountId }
32214
32214
  });
32215
- return { ...raw, projectId: raw?.id ?? raw?.projectId, title: raw?.name ?? raw?.title, orgId: raw?.organizationId ?? raw?.orgId };
32215
+ return { ...raw, systemId: raw?.id ?? raw?.systemId ?? raw?.projectId, title: raw?.name ?? raw?.title, orgId: raw?.organizationId ?? raw?.orgId };
32216
32216
  },
32217
32217
  updateSystem: async (projectId, updates) => {
32218
32218
  const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}`, updates);
32219
32219
  if (!raw) return void 0;
32220
- return { ...raw, projectId: raw.id ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
32220
+ return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
32221
32221
  },
32222
32222
  archiveSystem: async (projectId) => {
32223
32223
  const raw = await request("PATCH", `/api/systems/${encodeURIComponent(projectId)}/archive`);
32224
32224
  if (!raw) return void 0;
32225
- return { ...raw, projectId: raw.id ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
32225
+ return { ...raw, systemId: raw.id ?? raw.systemId ?? raw.projectId, title: raw.name ?? raw.title, orgId: raw.organizationId ?? raw.orgId };
32226
32226
  },
32227
32227
  getSystemFact: async (projectId) => {
32228
32228
  const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/facts`);
@@ -32253,7 +32253,7 @@ function createHttpClient(config2) {
32253
32253
  getBeat: async (beatId) => {
32254
32254
  const raw = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
32255
32255
  if (!raw) return void 0;
32256
- return { ...raw, beatId: raw.beatId ?? raw.id, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
32256
+ return { ...raw, beatId: raw.beatId ?? raw.id, projectId: raw.projectId ?? raw.systemId, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
32257
32257
  },
32258
32258
  getBeatConvergence: async (projectId, beatId) => {
32259
32259
  const raw = await request(
@@ -32291,7 +32291,7 @@ function createHttpClient(config2) {
32291
32291
  return raw?.report;
32292
32292
  },
32293
32293
  createBeat: async (input) => {
32294
- const raw = await request("POST", `/api/systems/${encodeURIComponent(input.projectId)}/beats`, input);
32294
+ const raw = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/beats`, input);
32295
32295
  return { ...raw, beatId: raw.beatId ?? raw.id, status: raw.status ?? "open", beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
32296
32296
  },
32297
32297
  updateBeat: async (beatId, updates) => {
@@ -32300,7 +32300,9 @@ function createHttpClient(config2) {
32300
32300
  }
32301
32301
  const beat = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
32302
32302
  if (!beat) return void 0;
32303
- const result = await request("PUT", `/api/systems/${encodeURIComponent(beat.projectId)}/beats/${encodeURIComponent(beatId)}`, updates);
32303
+ const systemId = beat.systemId ?? beat.projectId;
32304
+ if (!systemId) return void 0;
32305
+ const result = await request("PUT", `/api/systems/${encodeURIComponent(systemId)}/beats/${encodeURIComponent(beatId)}`, updates);
32304
32306
  const raw = result?.beat ?? result;
32305
32307
  return { ...raw, beatStatus: coerceBeatStatusLocal(raw.beatStatus) };
32306
32308
  },
@@ -32373,7 +32375,7 @@ function createHttpClient(config2) {
32373
32375
  return result?.layer;
32374
32376
  },
32375
32377
  createSystemLayer: async (input) => {
32376
- const { projectId, layerKey, name, description, parentLayerId, repoPatterns } = input;
32378
+ const { systemId, layerKey, name, description, parentLayerId, repoPatterns } = input;
32377
32379
  const body = {
32378
32380
  layerKey,
32379
32381
  name,
@@ -32383,7 +32385,7 @@ function createHttpClient(config2) {
32383
32385
  };
32384
32386
  const result = await request(
32385
32387
  "POST",
32386
- `/api/systems/${encodeURIComponent(projectId)}/layers`,
32388
+ `/api/systems/${encodeURIComponent(systemId)}/layers`,
32387
32389
  body
32388
32390
  );
32389
32391
  if (result === void 0) return void 0;
@@ -32434,6 +32436,8 @@ function createHttpClient(config2) {
32434
32436
  listBeatNotes: async (beatId, filters) => {
32435
32437
  const beat = await request("GET", `/api/beats/${encodeURIComponent(beatId)}`);
32436
32438
  if (!beat) return [];
32439
+ const systemId = beat.systemId ?? beat.projectId;
32440
+ if (!systemId) return [];
32437
32441
  const params = new URLSearchParams();
32438
32442
  if (filters?.noteType) {
32439
32443
  const types = Array.isArray(filters.noteType) ? filters.noteType : [filters.noteType];
@@ -32445,7 +32449,7 @@ function createHttpClient(config2) {
32445
32449
  if (filters?.excludeChildren) params.set("excludeChildren", "true");
32446
32450
  if (filters?.significance) params.set("significance", filters.significance);
32447
32451
  const qs = params.toString() ? `?${params.toString()}` : "";
32448
- const result = await request("GET", `/api/systems/${encodeURIComponent(beat.projectId)}/beats/${encodeURIComponent(beatId)}/notes${qs}`);
32452
+ const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/beats/${encodeURIComponent(beatId)}/notes${qs}`);
32449
32453
  return result?.notes ?? [];
32450
32454
  },
32451
32455
  getNote: async (noteId) => {
@@ -32768,9 +32772,9 @@ function createHttpClient(config2) {
32768
32772
  // partitions, but the REST surface is unified per the project-level
32769
32773
  // "avoid nested/duplicate routes" guidance.
32770
32774
  getSession: (sessionId) => request("GET", `/api/sessions/${encodeURIComponent(sessionId)}`),
32771
- listSystemSessions: async (projectId, options) => {
32775
+ listSystemSessions: async (systemId, options) => {
32772
32776
  const params = new URLSearchParams();
32773
- params.set("projectId", projectId);
32777
+ params.set("systemId", systemId);
32774
32778
  if (options?.status) params.set("status", options.status);
32775
32779
  if (options?.includeTerminal) params.set("includeTerminal", "true");
32776
32780
  const result = await request("GET", `/api/sessions?${params.toString()}`);
@@ -32996,7 +33000,7 @@ function createHttpClient(config2) {
32996
33000
  return { entries: result.activities ?? [], hasMore: false };
32997
33001
  },
32998
33002
  createActivity: async (input) => {
32999
- const result = await request("POST", `/api/systems/${encodeURIComponent(input.projectId)}/activities`, {
33003
+ const result = await request("POST", `/api/systems/${encodeURIComponent(input.systemId)}/activities`, {
33000
33004
  action: input.action,
33001
33005
  importance: input.importance,
33002
33006
  reason: input.reason,
@@ -33181,7 +33185,7 @@ function createHttpClient(config2) {
33181
33185
  { projectId, message, ...triggeredBy && { triggeredBy }, ...trimmedRevisionId && { revisionId: trimmedRevisionId } }
33182
33186
  );
33183
33187
  if (!result?.taskId) throw new Error("Start session enqueue failed: no taskId returned");
33184
- return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33188
+ return { taskId: result.taskId, taskType: "generic_session", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33185
33189
  },
33186
33190
  planRevisionBatch: async (projectId, revisionIds) => {
33187
33191
  const result = await request(
@@ -33190,7 +33194,7 @@ function createHttpClient(config2) {
33190
33194
  revisionIds ? { revisionIds } : {}
33191
33195
  );
33192
33196
  if (!result?.taskId) throw new Error("Plan revision batch enqueue failed: no taskId returned");
33193
- return { taskId: result.taskId, taskType: "plan_revision_batch", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33197
+ return { taskId: result.taskId, taskType: "plan_revision_batch", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33194
33198
  },
33195
33199
  draftCoda: async (projectId, beatId) => {
33196
33200
  const result = await request(
@@ -33199,7 +33203,7 @@ function createHttpClient(config2) {
33199
33203
  {}
33200
33204
  );
33201
33205
  if (!result?.taskId) throw new Error("Draft coda enqueue failed: no taskId returned");
33202
- return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33206
+ return { taskId: result.taskId, taskType: "draft_coda", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33203
33207
  },
33204
33208
  composeRootCoda: async (orgId) => {
33205
33209
  const result = await request(
@@ -33208,7 +33212,7 @@ function createHttpClient(config2) {
33208
33212
  {}
33209
33213
  );
33210
33214
  if (!result?.taskId) throw new Error("Compose root Coda enqueue failed: no taskId returned");
33211
- return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", projectId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33215
+ return { taskId: result.taskId, taskType: "compose_root_coda", status: result.status ?? "pending", systemId: orgId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33212
33216
  },
33213
33217
  validateAssumptions: async (projectId, noteIds) => {
33214
33218
  const result = await request(
@@ -33217,7 +33221,7 @@ function createHttpClient(config2) {
33217
33221
  noteIds ? { noteIds } : {}
33218
33222
  );
33219
33223
  if (!result?.taskId) throw new Error("Assumption validation enqueue failed: no taskId returned");
33220
- return { taskId: result.taskId, taskType: "validate_assumptions", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33224
+ return { taskId: result.taskId, taskType: "validate_assumptions", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33221
33225
  },
33222
33226
  runTriage: async (projectId) => {
33223
33227
  const result = await request(
@@ -33226,7 +33230,7 @@ function createHttpClient(config2) {
33226
33230
  {}
33227
33231
  );
33228
33232
  if (!result?.taskId) throw new Error("Triage enqueue failed: no taskId returned");
33229
- return { taskId: result.taskId, taskType: "triage_loop", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33233
+ return { taskId: result.taskId, taskType: "triage_loop", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33230
33234
  },
33231
33235
  baselineProject: async (projectId, options) => {
33232
33236
  const result = await request(
@@ -33235,7 +33239,7 @@ function createHttpClient(config2) {
33235
33239
  { beatIds: options?.beatIds, runChecks: options?.runChecks ?? true, localPath: options?.localPath }
33236
33240
  );
33237
33241
  if (!result?.taskId) throw new Error("Baseline enqueue failed: no taskId returned");
33238
- return { taskId: result.taskId, taskType: "baseline_project", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33242
+ return { taskId: result.taskId, taskType: "baseline_project", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33239
33243
  },
33240
33244
  runCheck: async (projectId, checkType, targetId, options) => {
33241
33245
  const result = await request(
@@ -33244,7 +33248,7 @@ function createHttpClient(config2) {
33244
33248
  { checkType, targetId, branch: options?.branch, localPath: options?.localPath }
33245
33249
  );
33246
33250
  if (!result?.taskId) throw new Error("Check enqueue failed: no taskId returned");
33247
- return { taskId: result.taskId, taskType: "run_check", status: result.status ?? "pending", projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33251
+ return { taskId: result.taskId, taskType: "run_check", status: result.status ?? "pending", systemId: projectId, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
33248
33252
  },
33249
33253
  // Subscriptions
33250
33254
  subscribe: async (userId, entityType, entityId, projectId, source) => {
@@ -33321,7 +33325,7 @@ function createHttpClient(config2) {
33321
33325
  },
33322
33326
  searchByText: async (scope, query, options) => {
33323
33327
  const params = new URLSearchParams({ q: query });
33324
- if ("projectId" in scope) params.set("projectId", scope.projectId);
33328
+ if ("systemId" in scope) params.set("systemId", scope.systemId);
33325
33329
  else if ("teamspaceId" in scope) params.set("teamspaceId", scope.teamspaceId);
33326
33330
  else params.set("orgId", scope.orgId);
33327
33331
  if (options?.limit) params.set("limit", String(options.limit));
@@ -34157,7 +34161,7 @@ function loadConfig() {
34157
34161
  };
34158
34162
  }
34159
34163
  async function main() {
34160
- console.error(`[harmonica-mcp] v${"3.6.0"} starting\u2026`);
34164
+ console.error(`[harmonica-mcp] v${"3.7.0"} starting\u2026`);
34161
34165
  const config2 = loadConfig();
34162
34166
  const client = createHttpClient({
34163
34167
  apiBaseUrl: config2.apiBaseUrl,