@codazen/harmonica-mcp 3.7.1 → 3.8.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 +228 -105
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21972,9 +21972,9 @@ function formatActivity(a) {
21972
21972
  function registerActivityTools(server, ctx, client) {
21973
21973
  server.tool(
21974
21974
  "list_activities",
21975
- "List project activity audit trail \u2014 shows beat changes, revision transitions, note operations, merges, splits, and reassignments. Sorted newest-first.",
21975
+ "List system activity audit trail \u2014 shows beat changes, revision transitions, note operations, merges, splits, and reassignments. Sorted newest-first.",
21976
21976
  {
21977
- systemId: external_exports.string().describe("The project ID"),
21977
+ systemId: external_exports.string().describe("The system 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
  },
@@ -22005,7 +22005,7 @@ 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
- systemId: external_exports.string().describe("The project ID"),
22008
+ systemId: external_exports.string().describe("The system 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")
@@ -22034,9 +22034,9 @@ Analyzed: ${result.totalAnalyzed} | Clusters: ${result.clusters.length} | Noise:
22034
22034
  );
22035
22035
  server.tool(
22036
22036
  "run_clustered_analysis",
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.",
22037
+ "Run batched analysis on clustered system 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
- systemId: external_exports.string().describe("The project ID"),
22039
+ systemId: external_exports.string().describe("The system 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"),
@@ -22071,9 +22071,9 @@ ${result.noise.join(", ")}` : "");
22071
22071
  function registerAuditIntegrityTools(server, ctx, client) {
22072
22072
  server.tool(
22073
22073
  "verify_audit_integrity",
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.",
22074
+ "Verify the cryptographic integrity of audit trail records for a system. Checks SHA-256 hashes on Activity and PromptLog records to detect any post-creation tampering.",
22075
22075
  {
22076
- systemId: external_exports.string().describe("The project ID to verify"),
22076
+ systemId: external_exports.string().describe("The system ID to verify"),
22077
22077
  limit: external_exports.number().optional().describe("Max records to verify (default 100)")
22078
22078
  },
22079
22079
  async ({ systemId, limit }) => {
@@ -22287,7 +22287,7 @@ 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
- systemId: 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 system ID (for access control \u2014 all revisions and target beats must belong to this system)"),
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")
@@ -22328,7 +22328,7 @@ 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
- systemId: external_exports.string().describe("The project ID (for access control)"),
22331
+ systemId: external_exports.string().describe("The system 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")
@@ -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
- systemId: external_exports.string().describe("The project ID"),
22437
+ systemId: external_exports.string().describe("The system 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
  ),
@@ -22536,7 +22536,7 @@ ${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
- systemId: external_exports.string().describe("The project ID"),
22539
+ systemId: external_exports.string().describe("The system 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
22542
  const handler = async ({ beatId, systemId, wait }) => {
@@ -22585,7 +22585,7 @@ 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
- systemId: external_exports.string().describe("The project ID")
22588
+ systemId: external_exports.string().describe("The system ID")
22589
22589
  },
22590
22590
  async ({ beatId, systemId }) => {
22591
22591
  try {
@@ -23095,9 +23095,9 @@ function formatLayerDetail(layer) {
23095
23095
  function registerBeatTools(server, ctx, client) {
23096
23096
  server.tool(
23097
23097
  "list_beats",
23098
- "List beats in a project. Use offset/limit to paginate large result sets.",
23098
+ "List beats in a system. Use offset/limit to paginate large result sets.",
23099
23099
  {
23100
- systemId: external_exports.string().describe("The project ID"),
23100
+ systemId: external_exports.string().describe("The system 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)")
@@ -23139,7 +23139,7 @@ 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
- systemId: external_exports.string().optional().describe("Project ID (auto-resolved from beat if omitted)")
23142
+ systemId: external_exports.string().optional().describe("System ID (auto-resolved from beat if omitted)")
23143
23143
  },
23144
23144
  async ({ beatId, systemId }) => {
23145
23145
  const beat = await fetchBeatInOrg(client, beatId, ctx.orgId);
@@ -23154,9 +23154,9 @@ function registerBeatTools(server, ctx, client) {
23154
23154
  );
23155
23155
  server.tool(
23156
23156
  "create_beat",
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.",
23157
+ "Create a new beat in a system. 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
- systemId: external_exports.string().describe("The project ID"),
23159
+ systemId: external_exports.string().describe("The system 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"),
@@ -23238,7 +23238,7 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23238
23238
  maxConcurrentRevisions: external_exports.coerce.number().min(0).describe("Recommended max active revisions (0 = no cap)"),
23239
23239
  allowOverlap: external_exports.boolean().describe("Whether overlapping work on this Beat is acceptable"),
23240
23240
  scopeOverlapStrategy: external_exports.enum(["block", "warn", "allow"]).describe("Advisory only \u2014 how the UI/MCP should react when limits are exceeded")
23241
- }).nullable().optional().describe("Per-Beat concurrency guidance (advisory, not a gate). Omit to leave unchanged; pass null to clear and use the project default.")
23241
+ }).nullable().optional().describe("Per-Beat concurrency guidance (advisory, not a gate). Omit to leave unchanged; pass null to clear and use the system default.")
23242
23242
  },
23243
23243
  async ({ beatId, changeSummary, ...changes }) => {
23244
23244
  try {
@@ -23277,10 +23277,10 @@ ${formatBeatDetail(beat)}${similarityWarning}` }] };
23277
23277
  );
23278
23278
  server.tool(
23279
23279
  "list_revisions",
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.",
23280
+ "List revisions for a system, 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
- systemId: external_exports.string().describe("The project ID"),
23283
- beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project revisions)"),
23282
+ systemId: external_exports.string().describe("The system ID"),
23283
+ beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all system 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
  },
@@ -23397,10 +23397,10 @@ var PlanVersionsIdempotencyError = class extends Error {
23397
23397
  function registerBeatVersionTools(server, ctx, client) {
23398
23398
  server.tool(
23399
23399
  "list_beat_versions",
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.",
23400
+ "List Beat Versions for a Beat or System. 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 system. Status is binary (active | archived); by default excludes archived.",
23401
23401
  {
23402
- systemId: external_exports.string().describe("The project ID"),
23403
- beatId: external_exports.string().optional().describe("Filter to a specific Beat (omit for all Beat Versions in project)"),
23402
+ systemId: external_exports.string().describe("The system ID"),
23403
+ beatId: external_exports.string().optional().describe("Filter to a specific Beat (omit for all Beat Versions in system)"),
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
  },
@@ -23672,10 +23672,10 @@ function registerBeatVersionTools(server, ctx, client) {
23672
23672
  );
23673
23673
  server.tool(
23674
23674
  "relocate_beat_version",
23675
- "Move a Beat Version from one parent Beat to another within the same project, re-parenting all child Revisions and keeping every DynamoDB index consistent. Use when a Beat Version was planned under the wrong Beat or the scope of a Beat has been reorganized. Dry-run by default (confirm=false): shows what would change without writing. Pass confirm=true to execute. Blocked on released Drops by default \u2014 pass overrideReleasedDropLock=true to proceed (preserves Drop membership for audit trail).",
23675
+ "Move a Beat Version from one parent Beat to another within the same system, re-parenting all child Revisions and keeping every DynamoDB index consistent. Use when a Beat Version was planned under the wrong Beat or the scope of a Beat has been reorganized. Dry-run by default (confirm=false): shows what would change without writing. Pass confirm=true to execute. Blocked on released Drops by default \u2014 pass overrideReleasedDropLock=true to proceed (preserves Drop membership for audit trail).",
23676
23676
  {
23677
23677
  beatVersionId: external_exports.string().describe("The Beat Version ID to relocate (e.g., bv-abc123)"),
23678
- targetBeatId: external_exports.string().describe("The Beat ID to relocate the Beat Version to (must be in the same project)"),
23678
+ targetBeatId: external_exports.string().describe("The Beat ID to relocate the Beat Version to (must be in the same system)"),
23679
23679
  reason: external_exports.string().min(1).describe("Why this relocation is being made (recorded in audit trail)"),
23680
23680
  confirm: external_exports.boolean().optional().describe("Set true to execute the relocation. Default false (dry-run describes the move)."),
23681
23681
  overrideReleasedDropLock: external_exports.boolean().optional().describe("Allow relocation even when the Beat Version belongs to a released Drop (preserves Drop membership). Default false.")
@@ -23829,9 +23829,9 @@ function registerCheckTools(server, ctx, client) {
23829
23829
  );
23830
23830
  server.tool(
23831
23831
  "list_checks",
23832
- "List persisted checks for a project, beat, beat version, or revision. Shows score trends over time. Filter by check type.",
23832
+ "List persisted checks for a system, beat, beat version, or revision. Shows score trends over time. Filter by check type.",
23833
23833
  {
23834
- systemId: external_exports.string().describe("The project ID"),
23834
+ systemId: external_exports.string().describe("The system 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"),
@@ -23942,9 +23942,9 @@ function registerCheckTools(server, ctx, client) {
23942
23942
  );
23943
23943
  server.tool(
23944
23944
  "list_latest_checks",
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.",
23945
+ "List the latest check per target (Beat, Beat Version, Revision) across a whole system 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
- systemId: external_exports.string().describe("The project ID")
23947
+ systemId: external_exports.string().describe("The system ID")
23948
23948
  },
23949
23949
  async ({ systemId }) => {
23950
23950
  try {
@@ -23966,8 +23966,8 @@ 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
- systemId: external_exports.string().describe("The project ID"),
23970
- branch: external_exports.string().optional().describe("Override the git branch for code checks (default: project default branch)"),
23969
+ systemId: external_exports.string().describe("The system ID"),
23970
+ branch: external_exports.string().optional().describe("Override the git branch for code checks (default: system 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
23973
  async ({ checkType, targetId, systemId, branch, localPath }) => {
@@ -24555,7 +24555,7 @@ 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
- 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)"),
24558
+ systemId: external_exports.string().min(1).describe("A system within this Drop \u2014 the check record is attributed to it (Drops span systems, so the result is recorded under one member system)"),
24559
24559
  wait: external_exports.boolean().optional().describe("Block until the check completes and return the scorecard inline. Default: false.")
24560
24560
  },
24561
24561
  async ({ dropId, systemId, wait }) => {
@@ -25004,7 +25004,7 @@ 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
- systemId: external_exports.string().describe("The project ID the notes belong to"),
25007
+ systemId: external_exports.string().describe("The system 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)"),
@@ -25031,7 +25031,7 @@ 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
- systemId: external_exports.string().describe("The project ID the beat belongs to"),
25034
+ systemId: external_exports.string().describe("The system 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)")
@@ -25048,7 +25048,7 @@ 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
- systemId: external_exports.string().describe("The project ID the note belongs to"),
25051
+ systemId: external_exports.string().describe("The system 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
  },
@@ -25063,7 +25063,7 @@ 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
- systemId: external_exports.string().describe("The project ID"),
25066
+ systemId: external_exports.string().describe("The system 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.")
@@ -25094,7 +25094,7 @@ ${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
- systemId: external_exports.string().describe("The project ID"),
25097
+ systemId: external_exports.string().describe("The system 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.")
@@ -25821,9 +25821,9 @@ function formatNextActions(actions) {
25821
25821
  function registerNextActionsTools(server, ctx, client) {
25822
25822
  server.tool(
25823
25823
  "get_next_actions",
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).",
25824
+ "Get prioritized list of recommended next actions for a system. 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
- systemId: external_exports.string().describe("The project ID"),
25826
+ systemId: external_exports.string().describe("The system ID"),
25827
25827
  limit: external_exports.coerce.number().optional().default(10).describe("Max actions to return")
25828
25828
  },
25829
25829
  async ({ systemId, limit }) => {
@@ -25841,7 +25841,7 @@ 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
- systemId: external_exports.string().describe("The project ID")
25844
+ systemId: external_exports.string().describe("The system ID")
25845
25845
  },
25846
25846
  async ({ systemId }) => {
25847
25847
  try {
@@ -25858,7 +25858,7 @@ 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
- systemId: external_exports.string().describe("The project ID"),
25861
+ systemId: external_exports.string().describe("The system ID"),
25862
25862
  actionKey: external_exports.string().describe("The action key to dismiss (from list_triage_actions)")
25863
25863
  },
25864
25864
  async ({ systemId, actionKey }) => {
@@ -25905,6 +25905,126 @@ function formatTriageActions(actions) {
25905
25905
  // ../../libs/harmonica-services/src/mcp/tools/note-element-tools.ts
25906
25906
  var import_crypto3 = require("crypto");
25907
25907
 
25908
+ // ../../libs/harmonica-schemas/src/azure-marketplace.ts
25909
+ var AzureMarketplaceSubscriptionSchema = external_exports.object({
25910
+ id: external_exports.string(),
25911
+ publisherId: external_exports.string().optional(),
25912
+ offerId: external_exports.string().optional(),
25913
+ name: external_exports.string().optional(),
25914
+ saasSubscriptionStatus: external_exports.string().optional(),
25915
+ planId: external_exports.string().optional(),
25916
+ quantity: external_exports.number().optional(),
25917
+ beneficiary: external_exports.object({
25918
+ emailId: external_exports.string().optional(),
25919
+ objectId: external_exports.string().optional(),
25920
+ tenantId: external_exports.string().optional()
25921
+ }).optional(),
25922
+ purchaser: external_exports.object({
25923
+ emailId: external_exports.string().optional(),
25924
+ objectId: external_exports.string().optional(),
25925
+ tenantId: external_exports.string().optional()
25926
+ }).optional()
25927
+ });
25928
+ var AzureSubscribedSchema = external_exports.object({
25929
+ action: external_exports.literal("Subscribe"),
25930
+ // Non-blank, not merely non-empty. A bare z.string() accepts "", which would
25931
+ // derive the slug "az-" — 3 chars, trailing dash, invalid under the repository
25932
+ // slug invariant. And .min(1) alone still admits " ", which normalizes to
25933
+ // nothing and falls through to the hash fallback, quietly provisioning an org
25934
+ // under a hashed slug for input that is plainly garbage. Reject it at the
25935
+ // boundary instead.
25936
+ subscriptionId: external_exports.string().min(1).refine((v) => v.trim().length > 0, {
25937
+ message: "subscriptionId must not be blank"
25938
+ }),
25939
+ offerId: external_exports.string(),
25940
+ planId: external_exports.string(),
25941
+ quantity: external_exports.number().optional(),
25942
+ timeStamp: external_exports.string(),
25943
+ operationId: external_exports.string(),
25944
+ subscription: AzureMarketplaceSubscriptionSchema
25945
+ });
25946
+ var AzureUnsubscribedSchema = external_exports.object({
25947
+ action: external_exports.literal("Unsubscribe"),
25948
+ // Non-blank, not merely non-empty. A bare z.string() accepts "", which would
25949
+ // derive the slug "az-" — 3 chars, trailing dash, invalid under the repository
25950
+ // slug invariant. And .min(1) alone still admits " ", which normalizes to
25951
+ // nothing and falls through to the hash fallback, quietly provisioning an org
25952
+ // under a hashed slug for input that is plainly garbage. Reject it at the
25953
+ // boundary instead.
25954
+ subscriptionId: external_exports.string().min(1).refine((v) => v.trim().length > 0, {
25955
+ message: "subscriptionId must not be blank"
25956
+ }),
25957
+ offerId: external_exports.string(),
25958
+ planId: external_exports.string(),
25959
+ timeStamp: external_exports.string(),
25960
+ operationId: external_exports.string(),
25961
+ subscription: AzureMarketplaceSubscriptionSchema
25962
+ });
25963
+ var AzureChangePlanSchema = external_exports.object({
25964
+ action: external_exports.literal("ChangePlan"),
25965
+ // Non-blank, not merely non-empty. A bare z.string() accepts "", which would
25966
+ // derive the slug "az-" — 3 chars, trailing dash, invalid under the repository
25967
+ // slug invariant. And .min(1) alone still admits " ", which normalizes to
25968
+ // nothing and falls through to the hash fallback, quietly provisioning an org
25969
+ // under a hashed slug for input that is plainly garbage. Reject it at the
25970
+ // boundary instead.
25971
+ subscriptionId: external_exports.string().min(1).refine((v) => v.trim().length > 0, {
25972
+ message: "subscriptionId must not be blank"
25973
+ }),
25974
+ offerId: external_exports.string(),
25975
+ planId: external_exports.string(),
25976
+ timeStamp: external_exports.string(),
25977
+ operationId: external_exports.string(),
25978
+ subscription: AzureMarketplaceSubscriptionSchema
25979
+ });
25980
+ var AzureSuspendSchema = external_exports.object({
25981
+ action: external_exports.literal("Suspend"),
25982
+ // Non-blank, not merely non-empty. A bare z.string() accepts "", which would
25983
+ // derive the slug "az-" — 3 chars, trailing dash, invalid under the repository
25984
+ // slug invariant. And .min(1) alone still admits " ", which normalizes to
25985
+ // nothing and falls through to the hash fallback, quietly provisioning an org
25986
+ // under a hashed slug for input that is plainly garbage. Reject it at the
25987
+ // boundary instead.
25988
+ subscriptionId: external_exports.string().min(1).refine((v) => v.trim().length > 0, {
25989
+ message: "subscriptionId must not be blank"
25990
+ }),
25991
+ offerId: external_exports.string(),
25992
+ planId: external_exports.string(),
25993
+ timeStamp: external_exports.string(),
25994
+ operationId: external_exports.string(),
25995
+ subscription: AzureMarketplaceSubscriptionSchema
25996
+ });
25997
+ var AzureReinstateSchema = external_exports.object({
25998
+ action: external_exports.literal("Reinstate"),
25999
+ // Non-blank, not merely non-empty. A bare z.string() accepts "", which would
26000
+ // derive the slug "az-" — 3 chars, trailing dash, invalid under the repository
26001
+ // slug invariant. And .min(1) alone still admits " ", which normalizes to
26002
+ // nothing and falls through to the hash fallback, quietly provisioning an org
26003
+ // under a hashed slug for input that is plainly garbage. Reject it at the
26004
+ // boundary instead.
26005
+ subscriptionId: external_exports.string().min(1).refine((v) => v.trim().length > 0, {
26006
+ message: "subscriptionId must not be blank"
26007
+ }),
26008
+ offerId: external_exports.string(),
26009
+ planId: external_exports.string(),
26010
+ timeStamp: external_exports.string(),
26011
+ operationId: external_exports.string(),
26012
+ subscription: AzureMarketplaceSubscriptionSchema
26013
+ });
26014
+ var AzureMarketplaceWebhookEventSchema = external_exports.discriminatedUnion("action", [
26015
+ AzureSubscribedSchema,
26016
+ AzureUnsubscribedSchema,
26017
+ AzureChangePlanSchema,
26018
+ AzureSuspendSchema,
26019
+ AzureReinstateSchema
26020
+ ]);
26021
+ var AZURE_MARKETPLACE_WEBHOOK_ACTIONS = AzureMarketplaceWebhookEventSchema.options.map(
26022
+ (o) => o.shape.action.value
26023
+ );
26024
+ var AzureMarketplaceActivateBodySchema = external_exports.object({
26025
+ token: external_exports.string().min(1)
26026
+ });
26027
+
25908
26028
  // ../../libs/harmonica-schemas/src/compose-beat-versions.ts
25909
26029
  var BeatVersionDraftSchema = external_exports.object({
25910
26030
  title: external_exports.string().min(1).max(300),
@@ -26504,7 +26624,7 @@ var CheckApiSchema = external_exports.object({
26504
26624
  createdAt: external_exports.string(),
26505
26625
  updatedAt: external_exports.string()
26506
26626
  }).passthrough();
26507
- var ProjectCheckApiSchema = external_exports.object({
26627
+ var SystemCheckApiSchema = external_exports.object({
26508
26628
  checkId: external_exports.string(),
26509
26629
  checkType: CheckTypeSchema,
26510
26630
  targetType: CheckTargetTypeSchema,
@@ -26520,7 +26640,7 @@ var ProjectCheckApiSchema = external_exports.object({
26520
26640
  dimensions: external_exports.array(CheckDimensionScoreSchema).optional()
26521
26641
  }).passthrough();
26522
26642
  var CheckResponseSchema = external_exports.object({ check: CheckApiSchema }).passthrough();
26523
- var ChecksResponseSchema = external_exports.object({ checks: external_exports.array(ProjectCheckApiSchema), nextCursor: external_exports.string().optional() }).passthrough();
26643
+ var ChecksResponseSchema = external_exports.object({ checks: external_exports.array(SystemCheckApiSchema), nextCursor: external_exports.string().optional() }).passthrough();
26524
26644
  var TaskStatusSchema2 = external_exports.enum(["pending", "running", "completed", "failed"]);
26525
26645
  var TaskProgressSchema = external_exports.object({
26526
26646
  current: external_exports.number(),
@@ -27273,10 +27393,10 @@ ${formatDeliveryPolicy(
27273
27393
  function registerNoteTools(server, ctx, client) {
27274
27394
  server.tool(
27275
27395
  "list_notes",
27276
- "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.",
27396
+ "List Notes for a system. Without beatId: returns all notes (system-level + beat-level). With beatId: returns only notes scoped to that beat. Filterable by type, status, revisionId, beatVersionId, and significance. Use offset/limit to paginate beat-scoped results; use cursor for project-wide results.",
27277
27397
  {
27278
- systemId: external_exports.string().describe("The project ID"),
27279
- beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all project notes)"),
27398
+ systemId: external_exports.string().describe("The system ID"),
27399
+ beatId: external_exports.string().optional().describe("Scope to a specific beat (omit for all system notes)"),
27280
27400
  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"])'),
27281
27401
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("Filter by status"),
27282
27402
  revisionId: external_exports.string().optional().describe("Filter to notes scoped to a specific revision"),
@@ -27441,9 +27561,9 @@ function registerNoteTools(server, ctx, client) {
27441
27561
  );
27442
27562
  server.tool(
27443
27563
  "list_documents",
27444
- "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.",
27564
+ "List uploaded documents for a system with processing status, filename, child note count, and AI summary. Use get_note with a document note ID for full details.",
27445
27565
  {
27446
- systemId: external_exports.string().describe("The project ID")
27566
+ systemId: external_exports.string().describe("The system ID")
27447
27567
  },
27448
27568
  async ({ systemId }) => {
27449
27569
  await assertProjectInOrg(client, systemId, ctx.orgId);
@@ -27459,7 +27579,7 @@ function registerNoteTools(server, ctx, client) {
27459
27579
  "get_note",
27460
27580
  "Get full details of a single Note by ID",
27461
27581
  {
27462
- systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
27582
+ systemId: external_exports.string().optional().describe("The system ID (for access control). Provide systemId or orgId, not both."),
27463
27583
  orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
27464
27584
  noteId: external_exports.string().describe("The note ID")
27465
27585
  },
@@ -27496,9 +27616,9 @@ function registerNoteTools(server, ctx, client) {
27496
27616
  );
27497
27617
  server.tool(
27498
27618
  "create_note",
27499
- "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.",
27619
+ "Create a new Note. Provide exactly one of systemId, teamspaceId, movementId, or none (org-level): systemId scopes the note to a system (optionally with beatId), teamspaceId scopes it to a teamspace (no cascade to child systems), movementId scopes it to a Movement (commercial SOW container \u2014 engagement-level knowledge), and omitting all creates an org-level note that applies across all systems.",
27500
27620
  {
27501
- systemId: external_exports.string().optional().describe("The project ID (omit for teamspace-, movement-, or org-level note)"),
27621
+ systemId: external_exports.string().optional().describe("The system ID (omit for teamspace-, movement-, or org-level note)"),
27502
27622
  teamspaceId: external_exports.string().optional().describe("The teamspace ID (omit for project-, movement-, or org-level note)"),
27503
27623
  movementId: external_exports.string().min(1).optional().describe("The Movement ID (omit for project-, teamspace-, or org-level note)"),
27504
27624
  noteType: external_exports.enum(NOTE_TYPE_VALUES).describe("The type of note"),
@@ -27597,7 +27717,7 @@ ${text}` }] };
27597
27717
  "update_note",
27598
27718
  "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.",
27599
27719
  {
27600
- systemId: external_exports.string().optional().describe("The project ID (for access control). Provide systemId or orgId, not both."),
27720
+ systemId: external_exports.string().optional().describe("The system ID (for access control). Provide systemId or orgId, not both."),
27601
27721
  orgId: external_exports.string().optional().describe("The org ID (for org-level notes). Provide orgId or systemId, not both."),
27602
27722
  noteId: external_exports.string().describe("The note ID to update"),
27603
27723
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify the note type (e.g. assumption \u2192 guidance)"),
@@ -27679,7 +27799,7 @@ ${text}` }] };
27679
27799
  "bulk_update_notes",
27680
27800
  "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.",
27681
27801
  {
27682
- systemId: external_exports.string().describe("The project ID (for access control)"),
27802
+ systemId: external_exports.string().describe("The system ID (for access control)"),
27683
27803
  noteIds: external_exports.array(external_exports.string()).min(1).max(100).describe("Note IDs to update (max 100 per call)"),
27684
27804
  noteType: external_exports.enum(NOTE_TYPE_VALUES).optional().describe("Reclassify all notes to this type"),
27685
27805
  status: external_exports.enum(NOTE_STATUS_VALUES).optional().describe("New status for all notes"),
@@ -27721,7 +27841,7 @@ ${text}` }] };
27721
27841
  "reassign_note",
27722
27842
  "Reassign a misattributed note to a different beat (with audit trail)",
27723
27843
  {
27724
- systemId: external_exports.string().describe("The project ID (for access control)"),
27844
+ systemId: external_exports.string().describe("The system ID (for access control)"),
27725
27845
  noteId: external_exports.string().describe("The note ID to reassign"),
27726
27846
  targetBeatId: external_exports.string().describe("The beat ID to move the note to"),
27727
27847
  targetRevisionId: external_exports.string().optional().describe("Optional revision ID to scope the note to"),
@@ -27745,7 +27865,7 @@ ${text}` }] };
27745
27865
  "remove_note",
27746
27866
  "Soft-remove a misattributed note by dismissing it (with audit trail)",
27747
27867
  {
27748
- systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
27868
+ systemId: external_exports.string().min(1).describe("The system ID (for access control)"),
27749
27869
  noteId: external_exports.string().min(1).describe("The note ID to remove"),
27750
27870
  reason: external_exports.string().min(1).max(500).optional().describe('Why this note is being removed (defaults to "Removed via remove_note")')
27751
27871
  },
@@ -27772,7 +27892,7 @@ ${text}` }] };
27772
27892
  "dismiss_document",
27773
27893
  "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.",
27774
27894
  {
27775
- systemId: external_exports.string().min(1).describe("The project ID (for access control)"),
27895
+ systemId: external_exports.string().min(1).describe("The system ID (for access control)"),
27776
27896
  noteId: external_exports.string().min(1).describe("The document note ID to dismiss"),
27777
27897
  reason: external_exports.string().min(1).max(500).optional().describe('Why this document is being dismissed (defaults to "Dismissed via dismiss_document")')
27778
27898
  },
@@ -27805,7 +27925,7 @@ ${text}` }] };
27805
27925
  "list_key_assumptions",
27806
27926
  "Get key assumptions sorted by criticality (highest risk first)",
27807
27927
  {
27808
- systemId: external_exports.string().describe("The project ID")
27928
+ systemId: external_exports.string().describe("The system ID")
27809
27929
  },
27810
27930
  async ({ systemId }) => {
27811
27931
  await assertProjectInOrg(client, systemId, ctx.orgId);
@@ -27888,7 +28008,7 @@ function formatTree(node, depth = 0) {
27888
28008
  function registerNotebookTools(server, ctx, client) {
27889
28009
  server.tool(
27890
28010
  "create_notebook",
27891
- "Create a new Notebook in the configured organization. Optionally bind it to one or more scopes (org, teamspace, or project) at creation.",
28011
+ "Create a new Notebook in the configured organization. Optionally bind it to one or more scopes (org, teamspace, or system) at creation.",
27892
28012
  {
27893
28013
  title: external_exports.string().min(1).max(300).describe("Notebook title (1\u2013300 characters)"),
27894
28014
  description: external_exports.string().max(2e4).optional().describe("Optional description (max 20 000 characters)"),
@@ -28065,7 +28185,7 @@ ${formatTree(tree.root)}` }] };
28065
28185
  );
28066
28186
  server.tool(
28067
28187
  "add_notebook_collection",
28068
- "Add a live-collection section to a Notebook page. Unlike a normal section, its members are resolved from a live query at read time (never hand-picked), so it stays current on its own as notes are created, edited, or retired. v1 supports the project Decision Log: every decision note in a project (including beat-scoped), newest first. Attach under a page (defaults to the notebook root).",
28188
+ "Add a live-collection section to a Notebook page. Unlike a normal section, its members are resolved from a live query at read time (never hand-picked), so it stays current on its own as notes are created, edited, or retired. v1 supports the system Decision Log: every decision note in a system (including beat-scoped), newest first. Attach under a page (defaults to the notebook root).",
28069
28189
  {
28070
28190
  notebookId: external_exports.string().describe("The Notebook ID"),
28071
28191
  heading: external_exports.string().min(1).max(200).describe('Section label, e.g. "Decision Log"'),
@@ -28288,7 +28408,7 @@ ${scopeText}` }] };
28288
28408
  );
28289
28409
  server.tool(
28290
28410
  "list_notebooks",
28291
- "List Notebooks reachable from a scope (org, teamspace, or project). Returns all notebooks bound to that scope.",
28411
+ "List Notebooks reachable from a scope (org, teamspace, or system). Returns all notebooks bound to that scope.",
28292
28412
  {
28293
28413
  scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, or project"),
28294
28414
  scopeId: external_exports.string().min(1).describe("ID of the scope entity"),
@@ -28499,27 +28619,27 @@ function registerOnboardingTools(server, ctx, client) {
28499
28619
  server.tool(
28500
28620
  "draft_onboarding_beats",
28501
28621
  [
28502
- "Generate an initial Beat structure for a newly created project.",
28503
- "Call this after create_teamspace_onboarding_batch to draft 3\u20135 Beats based on the project context.",
28622
+ "Generate an initial Beat structure for a newly created system.",
28623
+ "Call this after create_teamspace_onboarding_batch to draft 3\u20135 Beats based on the system context.",
28504
28624
  "Returns Beat suggestions with titles, descriptions, and any Maya recommendations to convert a suggestion to a Note instead.",
28505
28625
  "The PM reviews and confirms \u2014 use create_beat for each approved Beat, create_note for any Note conversions."
28506
28626
  ].join(" "),
28507
28627
  {
28508
- systemId: external_exports.string().describe("The newly created project ID"),
28509
- projectTitle: external_exports.string().describe("The project title"),
28628
+ systemId: external_exports.string().describe("The newly created system ID"),
28629
+ systemTitle: external_exports.string().describe("The system title"),
28510
28630
  projectDescription: external_exports.string().optional().describe("Discovery context: pain points, goals, and constraints summary to inform the Beat structure"),
28511
28631
  analysisJson: external_exports.string().optional().describe("JSON-serialized DiscoveryAnalysis from initiate_teamspace_onboarding. Pass this to produce Beats grounded in the actual discovery data.")
28512
28632
  },
28513
- async ({ systemId, projectTitle, projectDescription, analysisJson }) => {
28633
+ async ({ systemId, systemTitle, projectDescription, analysisJson }) => {
28514
28634
  await assertProjectInOrg(client, systemId, ctx.orgId);
28515
- const result = await client.draftOnboardingBeats(systemId, { projectTitle, projectDescription, analysisJson });
28635
+ const result = await client.draftOnboardingBeats(systemId, { systemTitle, projectDescription, analysisJson });
28516
28636
  if (!result.suggestions || result.suggestions.length === 0) {
28517
28637
  return {
28518
28638
  content: [{ type: "text", text: "Failed to generate Beat suggestions. Try providing more project context." }]
28519
28639
  };
28520
28640
  }
28521
28641
  const lines = [
28522
- `## Beat Draft for "${projectTitle}" (${result.suggestions.length} suggestions)`,
28642
+ `## Beat Draft for "${systemTitle}" (${result.suggestions.length} suggestions)`,
28523
28643
  "",
28524
28644
  ...result.suggestions.flatMap((s, i) => {
28525
28645
  const sug = s;
@@ -28816,7 +28936,7 @@ function registerOrganizationTools(server, ctx, client) {
28816
28936
  if (result.error) {
28817
28937
  return { content: [{ type: "text", text: result.error }], isError: true };
28818
28938
  }
28819
- const transferred = result.project;
28939
+ const transferred = result.system;
28820
28940
  if (!transferred) {
28821
28941
  return { content: [{ type: "text", text: "Transfer succeeded but system data was not returned." }], isError: true };
28822
28942
  }
@@ -28946,11 +29066,11 @@ ${d.rationale}${d.suggestions?.length ? `
28946
29066
  function registerPlanQualityTools(server, ctx, client) {
28947
29067
  const schema = {
28948
29068
  beatVersionId: external_exports.string().describe("The Beat Version ID (e.g., bv-abc123). beat_version_quality targets Beat Versions only."),
28949
- systemId: external_exports.string().describe("The project ID"),
29069
+ systemId: external_exports.string().describe("The system ID"),
28950
29070
  // The completeness dimension probes the codebase, so this tool accepts the
28951
29071
  // same repo overrides as run_check — without them the probe could only ever
28952
29072
  // read the default branch from this surface.
28953
- branch: external_exports.string().optional().describe("Override the git branch the completeness dimension probes (default: the branch of an open child Revision, else the project default branch)"),
29073
+ branch: external_exports.string().optional().describe("Override the git branch the completeness dimension probes (default: the branch of an open child Revision, else the system default branch)"),
28954
29074
  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"),
28955
29075
  wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
28956
29076
  };
@@ -29103,9 +29223,9 @@ function formatConsolidationResult(primaryId, primary, archivedBeatIds, notesRea
29103
29223
  function registerPortfolioCoherenceTools(server, ctx, client) {
29104
29224
  server.tool(
29105
29225
  "check_portfolio_coherence",
29106
- "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.",
29226
+ "Run a portfolio coherence check on a system. 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.",
29107
29227
  {
29108
- systemId: external_exports.string().min(1).describe("The project ID"),
29228
+ systemId: external_exports.string().min(1).describe("The system ID"),
29109
29229
  wait: external_exports.boolean().optional().describe("Block until the check completes and return the report inline. Default: false.")
29110
29230
  },
29111
29231
  async ({ systemId, wait }) => {
@@ -29137,7 +29257,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
29137
29257
  "consolidate_beats",
29138
29258
  "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.",
29139
29259
  {
29140
- systemId: external_exports.string().min(1).describe("The project ID \u2014 all beatIds must belong to this project"),
29260
+ systemId: external_exports.string().min(1).describe("The system ID \u2014 all beatIds must belong to this system"),
29141
29261
  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)"),
29142
29262
  primaryBeatId: external_exports.string().min(1).optional().describe("Beat ID that survives \u2014 defaults to beatIds[0] if omitted"),
29143
29263
  rationale: external_exports.string().max(2e3).optional().describe("Why these Beats are being merged \u2014 auto-generated if omitted"),
@@ -29321,14 +29441,14 @@ This is a deliberate choice by whoever configured the System, and \`confirm: tru
29321
29441
  function registerRevisionLifecycleTools(server, ctx, client) {
29322
29442
  server.tool(
29323
29443
  "create_revision",
29324
- "Create a new revision on a Beat. A Revision's canonical status starts `active`; PR progression is tracked separately on `scm.state` (`draft` \u2192 `open` \u2192 `merged` / `closed`). Environment progression (`in_staging`, `ready_for_production`, `live`) is tracked on the parent Beat Version, not the Revision. `beatVersionId` is optional \u2014 omit it for hotfixes, dep bumps, or other operational fixes that don't belong to a planning increment. Orphan Revisions (no BV) must carry their own `description` since there's no BV context to inherit. `baseRevisionId` stacks this Revision's PR/session checkout on another Revision's branch instead of the project default \u2014 for GitHub stacked PRs, must reference a Revision in the same project (validated server-side). Use this to add revisions alongside existing ones \u2014 no idempotency guard. AFTER creating a revision you intend to work on, call `create_claim` on it before writing any code \u2014 the claim signals to other agents/humans that the Revision is actively being worked, preventing duplicate work \u2014 and `release_claim` when you finish or hand off.",
29444
+ "Create a new revision on a Beat. A Revision's canonical status starts `active`; PR progression is tracked separately on `scm.state` (`draft` \u2192 `open` \u2192 `merged` / `closed`). Environment progression (`in_staging`, `ready_for_production`, `live`) is tracked on the parent Beat Version, not the Revision. `beatVersionId` is optional \u2014 omit it for hotfixes, dep bumps, or other operational fixes that don't belong to a planning increment. Orphan Revisions (no BV) must carry their own `description` since there's no BV context to inherit. `baseRevisionId` stacks this Revision's PR/session checkout on another Revision's branch instead of the system default \u2014 for GitHub stacked PRs, must reference a Revision in the same system (validated server-side). Use this to add revisions alongside existing ones \u2014 no idempotency guard. AFTER creating a revision you intend to work on, call `create_claim` on it before writing any code \u2014 the claim signals to other agents/humans that the Revision is actively being worked, preventing duplicate work \u2014 and `release_claim` when you finish or hand off.",
29325
29445
  {
29326
29446
  beatId: external_exports.string().describe("The beat ID to create the revision on"),
29327
29447
  title: external_exports.string().describe("Title for this revision"),
29328
29448
  description: external_exports.string().describe("What this revision delivers. Required when beatVersionId is omitted (the description is the audit-trail floor for orphan Revisions)."),
29329
29449
  changeSummary: external_exports.string().describe("Brief summary of the change"),
29330
29450
  beatVersionId: external_exports.string().min(1).optional().describe("Beat Version ID to associate this revision with. Omit for hotfixes / orphan changes that don't fit a planning increment."),
29331
- baseRevisionId: external_exports.string().min(1).optional().describe("Existing Revision (same project) to stack this one on top of \u2014 its PR and agent session will target that Revision's branch instead of the project default (GitHub stacked PRs)."),
29451
+ baseRevisionId: external_exports.string().min(1).optional().describe("Existing Revision (same system) to stack this one on top of \u2014 its PR and agent session will target that Revision's branch instead of the system default (GitHub stacked PRs)."),
29332
29452
  tags: external_exports.array(external_exports.string()).optional().describe("Tags for categorization"),
29333
29453
  priority: external_exports.coerce.number().optional().describe("Priority (lower = higher priority)"),
29334
29454
  estimatedEffort: external_exports.string().optional().describe("Estimated effort (e.g., S, M, L, XL)"),
@@ -29449,10 +29569,13 @@ function registerRevisionLifecycleTools(server, ctx, client) {
29449
29569
  return { content: [{ type: "text", text: `Beat Version not found: "${beatVersionId}"` }], isError: true };
29450
29570
  }
29451
29571
  const bvSystemId = bv.systemId ?? bv.projectId;
29452
- const revSystemId = revision.systemId ?? revision.projectId;
29453
- if (!bvSystemId || !revSystemId) {
29572
+ if (!bvSystemId) {
29454
29573
  return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" has no system identifier \u2014 record may be corrupt.` }], isError: true };
29455
29574
  }
29575
+ const revSystemId = revision.systemId ?? revision.projectId;
29576
+ if (!revSystemId) {
29577
+ return { content: [{ type: "text", text: `Revision "${revisionId}" has no system identifier \u2014 record may be corrupt.` }], isError: true };
29578
+ }
29456
29579
  if (bv.beatId !== revision.beatId || bvSystemId !== revSystemId) {
29457
29580
  return { content: [{ type: "text", text: `Beat Version "${beatVersionId}" belongs to a different Beat.` }], isError: true };
29458
29581
  }
@@ -29595,7 +29718,7 @@ Accepts the full PR metadata (number, url, branch, state, optional mergeSha). Fi
29595
29718
  );
29596
29719
  server.tool(
29597
29720
  "create_pr_for_revision",
29598
- "Open a Harmonica-initiated DRAFT pull request for a revision and link it back via revision.scm. Cuts a branch (agent/rev-<short>-<slug>), bootstraps it with an empty commit, opens a draft PR whose body starts with the `Harmonica: rev-<uuid>` marker, then records the PR on the revision. Rejects if the revision already has a linked PR, or if the project has no repo configured. Dry-run by default \u2014 call with `confirm: true` to actually create the branch and PR. Leaves the revision in `draft`; transition it to `open` when the PR is marked ready for review.",
29721
+ "Open a Harmonica-initiated DRAFT pull request for a revision and link it back via revision.scm. Cuts a branch (agent/rev-<short>-<slug>), bootstraps it with an empty commit, opens a draft PR whose body starts with the `Harmonica: rev-<uuid>` marker, then records the PR on the revision. Rejects if the revision already has a linked PR, or if the system has no repo configured. Dry-run by default \u2014 call with `confirm: true` to actually create the branch and PR. Leaves the revision in `draft`; transition it to `open` when the PR is marked ready for review.",
29599
29722
  {
29600
29723
  revisionId: external_exports.string().describe("The revision ID (e.g., rev-abc123)"),
29601
29724
  confirm: external_exports.boolean().optional().describe("Set true to create the branch + draft PR. Omitted/false performs a dry-run that only describes what would happen \u2014 no GitHub or DB writes.")
@@ -29885,7 +30008,7 @@ function registerRevisionQualityTools(server, ctx, client) {
29885
30008
  "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.",
29886
30009
  {
29887
30010
  revisionId: external_exports.string().describe("The Revision ID (e.g., rev-abc123). revision_quality targets Revisions only."),
29888
- systemId: external_exports.string().describe("The project ID"),
30011
+ systemId: external_exports.string().describe("The system ID"),
29889
30012
  wait: external_exports.boolean().optional().describe("If true, block until the check completes and return the scorecard inline. Default: false (returns taskId immediately).")
29890
30013
  },
29891
30014
  async ({ revisionId, systemId, wait }) => {
@@ -30446,8 +30569,8 @@ function registerSubscriptionTools(server, ctx, client) {
30446
30569
  "Follow a Project, Beat, or Revision to receive notifications when it changes. You are auto-subscribed to things you own \u2014 use this to follow additional entities.",
30447
30570
  {
30448
30571
  entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
30449
- entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
30450
- systemId: external_exports.string().describe("The project ID (for access control)")
30572
+ entityId: external_exports.string().describe("The entity ID (system ID, beat ID, or revision ID)"),
30573
+ systemId: external_exports.string().describe("The system ID (for access control)")
30451
30574
  },
30452
30575
  async ({ entityType, entityId, systemId }) => {
30453
30576
  try {
@@ -30491,7 +30614,7 @@ function registerSubscriptionTools(server, ctx, client) {
30491
30614
  );
30492
30615
  server.tool(
30493
30616
  "list_subscriptions",
30494
- "List entities you are currently following. Optionally filter by entity type (project, beat, revision).",
30617
+ "List entities you are currently following. Optionally filter by entity type (system, beat, revision).",
30495
30618
  {
30496
30619
  entityType: external_exports.enum(ENTITY_TYPES).optional().describe("Filter by entity type")
30497
30620
  },
@@ -30603,7 +30726,7 @@ function registerProjectTools(server, ctx, client) {
30603
30726
  };
30604
30727
  server.tool(
30605
30728
  "list_systems",
30606
- "List all systems (projects) in the configured organization",
30729
+ "List all systems in the configured organization",
30607
30730
  {
30608
30731
  teamspaceId: external_exports.string().optional().describe("Filter to systems belonging to a specific teamspace. Empty or whitespace-only treated as no filter.")
30609
30732
  },
@@ -31104,9 +31227,9 @@ function registerValueVelocityTools(server, ctx, client) {
31104
31227
  );
31105
31228
  server.tool(
31106
31229
  "propose_enablement_edges",
31107
- '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.',
31230
+ 'Propose directed enablement edges for a system \u2014 "Beat A accelerates Beat B". The LLM analyzes system 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.',
31108
31231
  {
31109
- systemId: external_exports.string().describe("The project ID"),
31232
+ systemId: external_exports.string().describe("The system ID"),
31110
31233
  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).")
31111
31234
  },
31112
31235
  async ({ systemId, beatId }) => {
@@ -31492,9 +31615,9 @@ ${JSON.stringify(task.result, null, 2)}
31492
31615
  );
31493
31616
  server.tool(
31494
31617
  "list_tasks",
31495
- "DEPRECATED: use `list_jobs`. List background jobs for a project with optional status filter.",
31618
+ "DEPRECATED: use `list_jobs`. List background jobs for a system with optional status filter.",
31496
31619
  {
31497
- systemId: external_exports.string().describe("The project ID"),
31620
+ systemId: external_exports.string().describe("The system ID"),
31498
31621
  status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
31499
31622
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31500
31623
  },
@@ -31550,9 +31673,9 @@ ${JSON.stringify(task.result, null, 2)}
31550
31673
  );
31551
31674
  server.tool(
31552
31675
  "list_jobs",
31553
- "List background jobs for a project with optional status filter",
31676
+ "List background jobs for a system with optional status filter",
31554
31677
  {
31555
- systemId: external_exports.string().describe("The project ID"),
31678
+ systemId: external_exports.string().describe("The system ID"),
31556
31679
  status: external_exports.enum(TASK_STATUSES).optional().describe("Filter by status"),
31557
31680
  limit: external_exports.coerce.number().optional().default(20).describe("Max number of jobs to return")
31558
31681
  },
@@ -31576,7 +31699,7 @@ ${JSON.stringify(task.result, null, 2)}
31576
31699
  "draft_coda",
31577
31700
  "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.",
31578
31701
  {
31579
- systemId: external_exports.string().describe("The project ID"),
31702
+ systemId: external_exports.string().describe("The system ID"),
31580
31703
  beatId: external_exports.string().describe("The beat ID (must have a title but no description)")
31581
31704
  },
31582
31705
  async ({ systemId, beatId }) => {
@@ -31609,7 +31732,7 @@ ${JSON.stringify(task.result, null, 2)}
31609
31732
  "validate_assumptions",
31610
31733
  "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.",
31611
31734
  {
31612
- systemId: external_exports.string().describe("The project ID"),
31735
+ systemId: external_exports.string().describe("The system ID"),
31613
31736
  noteIds: external_exports.array(external_exports.string()).optional().describe("Specific assumption note IDs to validate. If omitted, validates all unvalidated assumptions.")
31614
31737
  },
31615
31738
  async ({ systemId, noteIds }) => {
@@ -31641,9 +31764,9 @@ ${JSON.stringify(task.result, null, 2)}
31641
31764
  );
31642
31765
  server.tool(
31643
31766
  "start_session",
31644
- '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.',
31767
+ 'Start an agent session from a free-form message \u2014 like opening a Claude Code conversation pointed at your system. 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 system.',
31645
31768
  {
31646
- systemId: external_exports.string().min(1).describe("The project ID \u2014 the trusted scope the session is rooted at"),
31769
+ systemId: external_exports.string().min(1).describe("The system ID \u2014 the trusted scope the session is rooted at"),
31647
31770
  message: external_exports.string().min(1).describe("Free-form first message describing what the session should do"),
31648
31771
  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.")
31649
31772
  },
@@ -31681,7 +31804,7 @@ ${JSON.stringify(task.result, null, 2)}
31681
31804
  "plan_revision_batch",
31682
31805
  "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.",
31683
31806
  {
31684
- systemId: external_exports.string().describe("The project ID"),
31807
+ systemId: external_exports.string().describe("The system ID"),
31685
31808
  revisionIds: external_exports.array(external_exports.string()).optional().describe("Specific revision IDs to plan. If omitted, auto-discovers all unplanned planning revisions.")
31686
31809
  },
31687
31810
  async ({ systemId, revisionIds }) => {
@@ -31712,9 +31835,9 @@ ${JSON.stringify(task.result, null, 2)}
31712
31835
  );
31713
31836
  server.tool(
31714
31837
  "run_triage",
31715
- "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.",
31838
+ "Run the autonomous triage loop for a system. 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 system. Returns a job ID \u2014 use get_job_status to track progress.",
31716
31839
  {
31717
- systemId: external_exports.string().describe("The project ID")
31840
+ systemId: external_exports.string().describe("The system ID")
31718
31841
  },
31719
31842
  async ({ systemId }) => {
31720
31843
  try {
@@ -31757,7 +31880,7 @@ ${JSON.stringify(task.result, null, 2)}
31757
31880
  "Returns a job ID \u2014 use `get_job_status` to track progress."
31758
31881
  ].join("\n"),
31759
31882
  {
31760
- systemId: external_exports.string().describe("The project ID"),
31883
+ systemId: external_exports.string().describe("The system ID"),
31761
31884
  content: external_exports.string().describe('Document content \u2014 plain text (default) or base64-encoded binary (when encoding is "base64")'),
31762
31885
  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.'),
31763
31886
  encoding: external_exports.enum(["base64"]).optional().describe('Set to "base64" when submitting binary files (DOCX, XLSX, PPTX, PDF). Omit for plain text.')
@@ -32207,13 +32330,13 @@ function createHttpClient(config2) {
32207
32330
  `/api/organizations/${encodeURIComponent(sourceOrgId)}/transfer-project`,
32208
32331
  { systemId: projectId, targetOrgId }
32209
32332
  );
32210
- const project = result?.project ? {
32211
- ...result.project,
32212
- systemId: result.project.id ?? result.project.systemId ?? result.project.projectId,
32213
- title: result.project.name ?? result.project.title,
32214
- orgId: result.project.organizationId ?? result.project.orgId
32333
+ const system = result?.system ? {
32334
+ ...result.system,
32335
+ systemId: result.system.id ?? result.system.systemId ?? result.system.projectId,
32336
+ title: result.system.name ?? result.system.title,
32337
+ orgId: result.system.organizationId ?? result.system.orgId
32215
32338
  } : void 0;
32216
- return { success: result?.success ?? false, project };
32339
+ return { success: result?.success ?? false, system };
32217
32340
  } catch (err) {
32218
32341
  if (err instanceof Error) return { success: false, error: err.message };
32219
32342
  return { success: false, error: String(err) };
@@ -34194,7 +34317,7 @@ function loadConfig() {
34194
34317
  };
34195
34318
  }
34196
34319
  async function main() {
34197
- console.error(`[harmonica-mcp] v${"3.7.1"} starting\u2026`);
34320
+ console.error(`[harmonica-mcp] v${"3.8.0"} starting\u2026`);
34198
34321
  const config2 = loadConfig();
34199
34322
  const client = createHttpClient({
34200
34323
  apiBaseUrl: config2.apiBaseUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codazen/harmonica-mcp",
3
- "version": "3.7.1",
3
+ "version": "3.8.0",
4
4
  "description": "MCP server for Harmonica — connect any MCP-compatible AI assistant to Harmonica",
5
5
  "license": "MIT",
6
6
  "bin": {