@codazen/harmonica-mcp 2.0.0 → 2.1.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.
- package/dist/index.js +495 -352
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22598,7 +22598,7 @@ ${beat.description}`);
|
|
|
22598
22598
|
return sections.join("\n");
|
|
22599
22599
|
}
|
|
22600
22600
|
|
|
22601
|
-
// ../../libs/harmonica-services/src/mcp/formatters/
|
|
22601
|
+
// ../../libs/harmonica-services/src/mcp/formatters/system-formatter.ts
|
|
22602
22602
|
function formatProjectSummaryTable(projects) {
|
|
22603
22603
|
if (projects.length === 0) return "_No projects found._";
|
|
22604
22604
|
const header = "| Project ID | Title | Status |\n|------------|-------|--------|";
|
|
@@ -23018,7 +23018,7 @@ function registerBeatTools(server, ctx, client) {
|
|
|
23018
23018
|
},
|
|
23019
23019
|
async ({ projectId, offset = 0, limit = 100, includeArchived }) => {
|
|
23020
23020
|
await assertProjectInOrg(client, projectId, ctx.orgId);
|
|
23021
|
-
let beats = await client.
|
|
23021
|
+
let beats = await client.listSystemBeats(projectId);
|
|
23022
23022
|
if (!includeArchived) {
|
|
23023
23023
|
beats = beats.filter((b) => b.beatStatus !== "archived");
|
|
23024
23024
|
}
|
|
@@ -23803,7 +23803,7 @@ function registerCheckTools(server, ctx, client) {
|
|
|
23803
23803
|
checks = await client.listBeatChecks(beatId, projectId, checkType);
|
|
23804
23804
|
scope = `beat ${beatId}`;
|
|
23805
23805
|
} else {
|
|
23806
|
-
const result = await client.
|
|
23806
|
+
const result = await client.listSystemChecks(projectId, checkType, { limit: 200 });
|
|
23807
23807
|
checks = result.checks;
|
|
23808
23808
|
scope = `project ${projectId}`;
|
|
23809
23809
|
}
|
|
@@ -26759,6 +26759,14 @@ var MeasureApiSchema = external_exports.object({
|
|
|
26759
26759
|
var MeasuresResponseSchema = external_exports.object({ measures: external_exports.array(MeasureApiSchema) }).passthrough();
|
|
26760
26760
|
var MeasureResponseSchema = external_exports.object({ measure: MeasureApiSchema }).passthrough();
|
|
26761
26761
|
var WorkItemStatusSchema = external_exports.enum(["not_started", "in_progress", "blocked", "done"]);
|
|
26762
|
+
var WORK_ITEM_EFFORT_SIZES = [
|
|
26763
|
+
"XS",
|
|
26764
|
+
"S",
|
|
26765
|
+
"M",
|
|
26766
|
+
"L",
|
|
26767
|
+
"XL"
|
|
26768
|
+
];
|
|
26769
|
+
var WorkItemEffortSizeSchema = external_exports.enum(WORK_ITEM_EFFORT_SIZES);
|
|
26762
26770
|
var HumanAssigneeApiSchema = external_exports.object({
|
|
26763
26771
|
name: external_exports.string(),
|
|
26764
26772
|
email: external_exports.string()
|
|
@@ -26769,10 +26777,16 @@ var WorkItemApiSchema = external_exports.object({
|
|
|
26769
26777
|
teamspaceId: external_exports.string(),
|
|
26770
26778
|
orgId: external_exports.string(),
|
|
26771
26779
|
title: external_exports.string(),
|
|
26772
|
-
|
|
26780
|
+
/** Optional since N-4E09-6672 — a work item may be unowned. */
|
|
26781
|
+
owner: HumanAssigneeApiSchema.optional(),
|
|
26773
26782
|
status: WorkItemStatusSchema,
|
|
26774
26783
|
committedEstimateHours: external_exports.number().optional(),
|
|
26775
26784
|
actualHours: external_exports.number().optional(),
|
|
26785
|
+
/** Delivery lens — rough t-shirt effort sizing (N-4E09-6672). */
|
|
26786
|
+
effortSize: WorkItemEffortSizeSchema.optional(),
|
|
26787
|
+
/** Commercial lens — deliberately unblended with `effortSize` (N-4E09-6672). */
|
|
26788
|
+
valueUnits: external_exports.number().optional(),
|
|
26789
|
+
costPerValueUnit: external_exports.number().optional(),
|
|
26776
26790
|
/** Optional capability-lineage link to a Beat Version (v2, N-4E09-6574). */
|
|
26777
26791
|
beatVersionId: external_exports.string().optional(),
|
|
26778
26792
|
createdBy: external_exports.string().optional(),
|
|
@@ -28882,7 +28896,7 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
28882
28896
|
isError: true
|
|
28883
28897
|
};
|
|
28884
28898
|
}
|
|
28885
|
-
const allProjectBeats = await client.
|
|
28899
|
+
const allProjectBeats = await client.listSystemBeats(projectId);
|
|
28886
28900
|
const beatMap = new Map(allProjectBeats.map((b) => [b.beatId, b]));
|
|
28887
28901
|
const primaryBeat = beatMap.get(resolvedPrimaryId);
|
|
28888
28902
|
if (!primaryBeat) {
|
|
@@ -28967,269 +28981,6 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
28967
28981
|
);
|
|
28968
28982
|
}
|
|
28969
28983
|
|
|
28970
|
-
// ../../libs/harmonica-services/src/mcp/tools/project-lifecycle-tools.ts
|
|
28971
|
-
function registerProjectLifecycleTools(server, ctx, client) {
|
|
28972
|
-
server.tool(
|
|
28973
|
-
"transition_system_lifecycle",
|
|
28974
|
-
"Advance a System's capability-maturity lifecycle state. Allowed transitions follow the state machine (Concept \u2192 Incubating \u2192 Piloting \u2192 Activated \u2192 Commercializing \u2192 Scaled, plus paused/killed/sunset/archived exits). The activated \u2192 commercializing edge requires a `decisionNoteId` pointing to a system-scoped Decision Note (the governance review). Other gates are advisory.",
|
|
28975
|
-
{
|
|
28976
|
-
systemId: external_exports.string().describe("The system ID"),
|
|
28977
|
-
targetState: external_exports.enum([
|
|
28978
|
-
"concept",
|
|
28979
|
-
"incubating",
|
|
28980
|
-
"piloting",
|
|
28981
|
-
"activated",
|
|
28982
|
-
"commercializing",
|
|
28983
|
-
"scaled",
|
|
28984
|
-
"paused",
|
|
28985
|
-
"killed",
|
|
28986
|
-
"sunset",
|
|
28987
|
-
"archived"
|
|
28988
|
-
]).describe("Target lifecycle state. Active: concept, incubating, piloting, activated, commercializing, scaled. Exits: paused, killed, sunset, archived."),
|
|
28989
|
-
reason: external_exports.string().optional().describe("Why this transition is being made (recorded in audit metadata)"),
|
|
28990
|
-
decisionNoteId: external_exports.string().optional().describe("Note ID of the governance Decision Note. Required for the activated \u2192 commercializing transition; optional otherwise.")
|
|
28991
|
-
},
|
|
28992
|
-
async ({ systemId, targetState, reason, decisionNoteId }) => {
|
|
28993
|
-
try {
|
|
28994
|
-
const system = await client.getSystem(systemId);
|
|
28995
|
-
if (!system) {
|
|
28996
|
-
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
28997
|
-
}
|
|
28998
|
-
if (system.orgId !== ctx.orgId) {
|
|
28999
|
-
return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
|
|
29000
|
-
}
|
|
29001
|
-
const result = await client.transitionProjectLifecycleState(systemId, targetState, {
|
|
29002
|
-
actor: { type: "human", id: ctx.user.userId, name: ctx.user.name },
|
|
29003
|
-
reason,
|
|
29004
|
-
decisionNoteId
|
|
29005
|
-
});
|
|
29006
|
-
if (!result.success) {
|
|
29007
|
-
const failedGates = result.error?.failedGates?.length ? ` (failed gates: ${result.error.failedGates.join(", ")})` : "";
|
|
29008
|
-
return {
|
|
29009
|
-
content: [{ type: "text", text: `Transition failed: ${result.error?.message ?? "Unknown error"}${failedGates}` }],
|
|
29010
|
-
isError: true
|
|
29011
|
-
};
|
|
29012
|
-
}
|
|
29013
|
-
const lines = [
|
|
29014
|
-
`System lifecycle transitioned successfully.`,
|
|
29015
|
-
"",
|
|
29016
|
-
`**System:** ${systemId}`,
|
|
29017
|
-
`**Title:** ${system.title}`,
|
|
29018
|
-
`**Transition:** ${result.previousState} \u2192 ${result.newState}`
|
|
29019
|
-
];
|
|
29020
|
-
if (reason) lines.push(`**Reason:** ${reason}`);
|
|
29021
|
-
if (decisionNoteId) lines.push(`**Decision Note:** ${decisionNoteId}`);
|
|
29022
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
29023
|
-
} catch (err) {
|
|
29024
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29025
|
-
return { content: [{ type: "text", text: `Failed to transition system lifecycle: ${message}` }], isError: true };
|
|
29026
|
-
}
|
|
29027
|
-
}
|
|
29028
|
-
);
|
|
29029
|
-
}
|
|
29030
|
-
|
|
29031
|
-
// ../../libs/harmonica-services/src/mcp/tools/project-tools.ts
|
|
29032
|
-
var import_crypto5 = require("crypto");
|
|
29033
|
-
function accountLine(request, resolved) {
|
|
29034
|
-
const requested = request["accountId"];
|
|
29035
|
-
if (requested === null || requested === "") return "**Account:** (unlinked)";
|
|
29036
|
-
return resolved ? `**Account:** ${resolved}` : "";
|
|
29037
|
-
}
|
|
29038
|
-
var PROJECT_EMBEDDING_FIELDS = ["title", "description", "strategy"];
|
|
29039
|
-
function registerProjectTools(server, ctx, client) {
|
|
29040
|
-
const listSystemsHandler = async ({ teamspaceId }) => {
|
|
29041
|
-
try {
|
|
29042
|
-
const projects = await client.listOrgSystems(ctx.orgId);
|
|
29043
|
-
const trimmed = teamspaceId?.trim();
|
|
29044
|
-
const filtered = trimmed ? projects.filter((p) => p.teamspaceId === trimmed) : projects;
|
|
29045
|
-
const text = formatProjectSummaryTable(filtered);
|
|
29046
|
-
return { content: [{ type: "text", text }] };
|
|
29047
|
-
} catch (err) {
|
|
29048
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29049
|
-
return { content: [{ type: "text", text: `Failed to list systems: ${message}` }], isError: true };
|
|
29050
|
-
}
|
|
29051
|
-
};
|
|
29052
|
-
server.tool(
|
|
29053
|
-
"list_systems",
|
|
29054
|
-
"List all systems (projects) in the configured organization",
|
|
29055
|
-
{
|
|
29056
|
-
teamspaceId: external_exports.string().optional().describe("Filter to systems belonging to a specific teamspace. Empty or whitespace-only treated as no filter.")
|
|
29057
|
-
},
|
|
29058
|
-
listSystemsHandler
|
|
29059
|
-
);
|
|
29060
|
-
const getSystemContextSchema = {
|
|
29061
|
-
systemId: external_exports.string().describe("The system ID"),
|
|
29062
|
-
noteLimit: external_exports.number().int().min(1).max(MAX_CONTEXT_NOTE_LIMIT).optional().describe(
|
|
29063
|
-
`Max Notes to include, prioritised by note type (default ${DEFAULT_CONTEXT_NOTE_LIMIT}, max ${MAX_CONTEXT_NOTE_LIMIT}). Use list_notes or search for the full set.`
|
|
29064
|
-
)
|
|
29065
|
-
};
|
|
29066
|
-
const getSystemContextHandler = async ({
|
|
29067
|
-
systemId,
|
|
29068
|
-
noteLimit
|
|
29069
|
-
}) => {
|
|
29070
|
-
try {
|
|
29071
|
-
const [project, org] = await Promise.all([
|
|
29072
|
-
fetchProjectInOrg(client, systemId, ctx.orgId),
|
|
29073
|
-
client.getOrg(ctx.orgId)
|
|
29074
|
-
]);
|
|
29075
|
-
const notes = await client.listProjectNotes(systemId, {
|
|
29076
|
-
limit: noteLimit ?? DEFAULT_CONTEXT_NOTE_LIMIT
|
|
29077
|
-
});
|
|
29078
|
-
const text = formatProjectContext(project, notes, org?.coda);
|
|
29079
|
-
return { content: [{ type: "text", text }] };
|
|
29080
|
-
} catch (err) {
|
|
29081
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29082
|
-
return { content: [{ type: "text", text: `Failed to get system context: ${message}` }], isError: true };
|
|
29083
|
-
}
|
|
29084
|
-
};
|
|
29085
|
-
server.tool(
|
|
29086
|
-
"get_system_context",
|
|
29087
|
-
"Get system metadata, description, and notes in one view",
|
|
29088
|
-
getSystemContextSchema,
|
|
29089
|
-
getSystemContextHandler
|
|
29090
|
-
);
|
|
29091
|
-
const updateSystemSchema = {
|
|
29092
|
-
systemId: external_exports.string().describe("The system ID"),
|
|
29093
|
-
title: external_exports.string().optional().describe("New system title"),
|
|
29094
|
-
description: external_exports.string().optional().describe("New system description"),
|
|
29095
|
-
strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
|
|
29096
|
-
teamspaceId: external_exports.string().nullable().optional().describe("Teamspace ID to associate this system with; pass null to remove the association"),
|
|
29097
|
-
accountId: external_exports.string().nullable().optional().describe("Account that owns this System \u2014 the client or internal org unit. Pass null (or an empty string) to unlink. Reassigning moves the System so it is listed under exactly one Account."),
|
|
29098
|
-
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
29099
|
-
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
29100
|
-
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
|
|
29101
|
-
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
|
|
29102
|
-
};
|
|
29103
|
-
const updateSystemHandler = async ({ systemId, ...updates }) => {
|
|
29104
|
-
try {
|
|
29105
|
-
const nonEmpty = Object.fromEntries(
|
|
29106
|
-
Object.entries(updates).filter(([, v]) => v !== void 0)
|
|
29107
|
-
);
|
|
29108
|
-
if (Object.keys(nonEmpty).length === 0) {
|
|
29109
|
-
return { content: [{ type: "text", text: "No updates provided." }], isError: true };
|
|
29110
|
-
}
|
|
29111
|
-
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
29112
|
-
if (nonEmpty["accountId"] === "") nonEmpty["accountId"] = null;
|
|
29113
|
-
if (typeof updates.teamspaceId === "string") {
|
|
29114
|
-
const teamspace = await client.getTeamspace(updates.teamspaceId);
|
|
29115
|
-
if (!teamspace) {
|
|
29116
|
-
return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
|
|
29117
|
-
}
|
|
29118
|
-
if (teamspace.orgId !== ctx.orgId) {
|
|
29119
|
-
return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
|
|
29120
|
-
}
|
|
29121
|
-
}
|
|
29122
|
-
const updated = await client.updateSystem(systemId, nonEmpty);
|
|
29123
|
-
if (!updated) {
|
|
29124
|
-
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
29125
|
-
}
|
|
29126
|
-
if (PROJECT_EMBEDDING_FIELDS.some((f) => f in nonEmpty)) {
|
|
29127
|
-
void client.triggerProjectEmbedding(systemId);
|
|
29128
|
-
}
|
|
29129
|
-
const lines = [
|
|
29130
|
-
`System updated successfully.`,
|
|
29131
|
-
"",
|
|
29132
|
-
`**ID:** ${updated.projectId}`,
|
|
29133
|
-
`**Title:** ${updated.title}`,
|
|
29134
|
-
accountLine(nonEmpty, updated.accountId),
|
|
29135
|
-
updated.strategy ? `**Strategy:** (updated)` : "",
|
|
29136
|
-
updated.repoOwner ? `**Repo:** ${updated.repoOwner}/${updated.repoName}` : "",
|
|
29137
|
-
updated.repoDefaultBranch ? `**Default Branch:** ${updated.repoDefaultBranch}` : ""
|
|
29138
|
-
].filter(Boolean);
|
|
29139
|
-
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
29140
|
-
} catch (err) {
|
|
29141
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29142
|
-
return { content: [{ type: "text", text: `Failed to update system: ${message}` }], isError: true };
|
|
29143
|
-
}
|
|
29144
|
-
};
|
|
29145
|
-
server.tool("update_system", "Update system settings such as title, description, repository configuration, or the Account that owns it", updateSystemSchema, updateSystemHandler);
|
|
29146
|
-
server.tool(
|
|
29147
|
-
"archive_system",
|
|
29148
|
-
"Archive a system, hiding it from the system dropdown and all active system views. Use this when a system is no longer active and should be removed from navigation.",
|
|
29149
|
-
{ systemId: external_exports.string().describe("The system ID to archive") },
|
|
29150
|
-
async ({ systemId }) => {
|
|
29151
|
-
const system = await client.getSystem(systemId);
|
|
29152
|
-
if (!system) {
|
|
29153
|
-
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
29154
|
-
}
|
|
29155
|
-
if (system.orgId !== ctx.orgId) {
|
|
29156
|
-
return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
|
|
29157
|
-
}
|
|
29158
|
-
try {
|
|
29159
|
-
const updated = await client.archiveSystem(systemId);
|
|
29160
|
-
if (!updated) {
|
|
29161
|
-
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
29162
|
-
}
|
|
29163
|
-
return {
|
|
29164
|
-
content: [{
|
|
29165
|
-
type: "text",
|
|
29166
|
-
text: [
|
|
29167
|
-
"System archived successfully.",
|
|
29168
|
-
"",
|
|
29169
|
-
`**ID:** ${updated.projectId}`,
|
|
29170
|
-
`**Title:** ${updated.title}`,
|
|
29171
|
-
`**Status:** ${updated.status}`
|
|
29172
|
-
].join("\n")
|
|
29173
|
-
}]
|
|
29174
|
-
};
|
|
29175
|
-
} catch (err) {
|
|
29176
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29177
|
-
return { content: [{ type: "text", text: `Failed to archive system: ${message}` }], isError: true };
|
|
29178
|
-
}
|
|
29179
|
-
}
|
|
29180
|
-
);
|
|
29181
|
-
const createSystemSchema = {
|
|
29182
|
-
title: external_exports.string().describe("System title"),
|
|
29183
|
-
description: external_exports.string().optional().describe("System description"),
|
|
29184
|
-
strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
|
|
29185
|
-
teamspaceId: external_exports.string().optional().describe("Teamspace ID to associate this system with"),
|
|
29186
|
-
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
29187
|
-
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
29188
|
-
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")')
|
|
29189
|
-
};
|
|
29190
|
-
const createSystemHandler = async ({ title, description, strategy, teamspaceId, repoOwner, repoName, repoDefaultBranch }) => {
|
|
29191
|
-
if (teamspaceId) {
|
|
29192
|
-
const teamspace = await client.getTeamspace(teamspaceId);
|
|
29193
|
-
if (!teamspace) {
|
|
29194
|
-
return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
|
|
29195
|
-
}
|
|
29196
|
-
if (teamspace.orgId !== ctx.orgId) {
|
|
29197
|
-
return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
|
|
29198
|
-
}
|
|
29199
|
-
}
|
|
29200
|
-
try {
|
|
29201
|
-
const project = await client.createSystem({
|
|
29202
|
-
projectId: (0, import_crypto5.randomUUID)(),
|
|
29203
|
-
orgId: ctx.orgId,
|
|
29204
|
-
ownerUserId: ctx.user.userId,
|
|
29205
|
-
title,
|
|
29206
|
-
description,
|
|
29207
|
-
strategy,
|
|
29208
|
-
status: "active",
|
|
29209
|
-
teamspaceId,
|
|
29210
|
-
repoOwner,
|
|
29211
|
-
repoName,
|
|
29212
|
-
repoDefaultBranch
|
|
29213
|
-
});
|
|
29214
|
-
const text = [
|
|
29215
|
-
`System created successfully.`,
|
|
29216
|
-
"",
|
|
29217
|
-
`**ID:** ${project.projectId}`,
|
|
29218
|
-
`**Title:** ${project.title}`,
|
|
29219
|
-
`**Status:** ${project.status}`,
|
|
29220
|
-
project.description ? `**Description:** ${project.description}` : "",
|
|
29221
|
-
project.repoOwner ? `**Repo:** ${project.repoOwner}/${project.repoName}` : "",
|
|
29222
|
-
project.repoDefaultBranch ? `**Default Branch:** ${project.repoDefaultBranch}` : ""
|
|
29223
|
-
].filter(Boolean).join("\n");
|
|
29224
|
-
return { content: [{ type: "text", text }] };
|
|
29225
|
-
} catch (err) {
|
|
29226
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
29227
|
-
return { content: [{ type: "text", text: `Failed to create system: ${message}` }], isError: true };
|
|
29228
|
-
}
|
|
29229
|
-
};
|
|
29230
|
-
server.tool("create_system", "Create a new system in the configured organization", createSystemSchema, createSystemHandler);
|
|
29231
|
-
}
|
|
29232
|
-
|
|
29233
28984
|
// ../../libs/harmonica-services/src/mcp/tools/pulse-report-tools.ts
|
|
29234
28985
|
function formatBeatRow(r) {
|
|
29235
28986
|
const downbeatState = r.hasDownbeat ? "" : " \u2014 no Downbeat ever set";
|
|
@@ -30018,8 +29769,8 @@ var import_promises = require("node:fs/promises");
|
|
|
30018
29769
|
var import_node_os = require("node:os");
|
|
30019
29770
|
var import_node_path = require("node:path");
|
|
30020
29771
|
|
|
30021
|
-
// ../../libs/harmonica-services/src/
|
|
30022
|
-
var SNAPSHOT_VERSION =
|
|
29772
|
+
// ../../libs/harmonica-services/src/system-snapshot.constants.ts
|
|
29773
|
+
var SNAPSHOT_VERSION = 4;
|
|
30023
29774
|
|
|
30024
29775
|
// ../../libs/harmonica-services/src/mcp/tools/snapshot-tools.ts
|
|
30025
29776
|
function registerSnapshotTools(server, ctx, client) {
|
|
@@ -30051,7 +29802,7 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30051
29802
|
const filename = `harmonica-snapshot-${systemId}-${Date.now()}.json`;
|
|
30052
29803
|
const filePath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), filename);
|
|
30053
29804
|
await (0, import_promises.writeFile)(filePath, JSON.stringify(snapshot), "utf-8");
|
|
30054
|
-
const title = snapshot.
|
|
29805
|
+
const title = snapshot.system?.title ?? systemId;
|
|
30055
29806
|
const versionNote = snapshot.version !== SNAPSHOT_VERSION ? [`Warning: snapshot version ${snapshot.version} (local expects ${SNAPSHOT_VERSION}) \u2014 some counts may be zero.`, ""] : [];
|
|
30056
29807
|
const summary = [
|
|
30057
29808
|
`Exported "${title}" to: ${filePath}`,
|
|
@@ -30066,6 +29817,10 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30066
29817
|
` PromptLogs: ${snapshot.promptLogs.length}`,
|
|
30067
29818
|
` Drops: ${snapshot.drops?.length ?? 0}`,
|
|
30068
29819
|
` Deliverables: ${snapshot.deliverables?.length ?? 0}`,
|
|
29820
|
+
` Teamspaces: ${snapshot.teamspaces?.length ?? 0}`,
|
|
29821
|
+
` Layers: ${snapshot.layers?.length ?? 0}`,
|
|
29822
|
+
` Tracks: ${snapshot.tracks?.length ?? 0}`,
|
|
29823
|
+
` Work Items: ${snapshot.workItems?.length ?? 0}`,
|
|
30069
29824
|
"",
|
|
30070
29825
|
"Use import_system_snapshot with this file path to import into another environment."
|
|
30071
29826
|
];
|
|
@@ -30099,7 +29854,7 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30099
29854
|
if (isPrivateHost(trimmed)) {
|
|
30100
29855
|
throw new Error("Snapshot URL must point to a public host \u2014 private, link-local, and loopback addresses are not permitted.");
|
|
30101
29856
|
}
|
|
30102
|
-
const result2 = await client.
|
|
29857
|
+
const result2 = await client.importSystemSnapshotFromUrl(trimmed, importOptions);
|
|
30103
29858
|
return { content: [{ type: "text", text: formatImportSummary(result2, targetTeamspaceId) }] };
|
|
30104
29859
|
}
|
|
30105
29860
|
if (trimmed.startsWith("http://")) {
|
|
@@ -30114,21 +29869,25 @@ function registerSnapshotTools(server, ctx, client) {
|
|
|
30114
29869
|
}
|
|
30115
29870
|
assertSnapshotShape(parsed, "");
|
|
30116
29871
|
const normalized = normalizeSnapshot(parsed);
|
|
30117
|
-
const result = await client.
|
|
29872
|
+
const result = await client.importSystemSnapshot(normalized, importOptions);
|
|
30118
29873
|
return { content: [{ type: "text", text: formatImportSummary(result, targetTeamspaceId) }] };
|
|
30119
29874
|
}
|
|
30120
29875
|
);
|
|
30121
29876
|
}
|
|
30122
29877
|
function normalizeSnapshot(raw) {
|
|
29878
|
+
const anyRaw = raw;
|
|
30123
29879
|
return {
|
|
30124
29880
|
...raw,
|
|
29881
|
+
// Compat: v3 and earlier snapshots have 'project' instead of 'system'
|
|
29882
|
+
system: raw.system ?? anyRaw["project"],
|
|
30125
29883
|
beats: raw.beats ?? [],
|
|
30126
29884
|
beatIndex: raw.beatIndex ?? [],
|
|
30127
29885
|
proposals: raw.proposals ?? [],
|
|
30128
29886
|
proposalIndex: raw.proposalIndex ?? [],
|
|
30129
29887
|
revisions: raw.revisions ?? [],
|
|
30130
29888
|
revisionIndex: raw.revisionIndex ?? [],
|
|
30131
|
-
|
|
29889
|
+
// Compat: v3 and earlier snapshots have 'projectRevisionIndex'
|
|
29890
|
+
systemRevisionIndex: raw.systemRevisionIndex ?? anyRaw["projectRevisionIndex"] ?? [],
|
|
30132
29891
|
activities: raw.activities ?? [],
|
|
30133
29892
|
activityIndex: raw.activityIndex ?? [],
|
|
30134
29893
|
notes: raw.notes ?? [],
|
|
@@ -30141,13 +29900,26 @@ function normalizeSnapshot(raw) {
|
|
|
30141
29900
|
promptLogs: raw.promptLogs ?? [],
|
|
30142
29901
|
beatVersions: raw.beatVersions ?? [],
|
|
30143
29902
|
beatBeatVersionIndex: raw.beatBeatVersionIndex ?? [],
|
|
30144
|
-
|
|
29903
|
+
// Compat: v3 and earlier snapshots have 'projectBeatVersionIndex'
|
|
29904
|
+
systemBeatVersionIndex: raw.systemBeatVersionIndex ?? anyRaw["projectBeatVersionIndex"] ?? [],
|
|
30145
29905
|
beatVersionDropAssignments: raw.beatVersionDropAssignments ?? [],
|
|
30146
29906
|
drops: raw.drops ?? [],
|
|
30147
29907
|
accountDropIndex: raw.accountDropIndex ?? [],
|
|
30148
29908
|
deliverables: raw.deliverables ?? [],
|
|
30149
29909
|
teamspaceDeliverableIndex: raw.teamspaceDeliverableIndex ?? [],
|
|
30150
|
-
|
|
29910
|
+
// Compat: v3 and earlier snapshots have 'projectDeliverableIndex'
|
|
29911
|
+
systemDeliverableIndex: raw.systemDeliverableIndex ?? anyRaw["projectDeliverableIndex"] ?? [],
|
|
29912
|
+
teamspaces: raw.teamspaces ?? [],
|
|
29913
|
+
teamspaceAccountIndex: raw.teamspaceAccountIndex ?? [],
|
|
29914
|
+
accountTeamspaceIndex: raw.accountTeamspaceIndex ?? [],
|
|
29915
|
+
layers: raw.layers ?? [],
|
|
29916
|
+
layerIndex: raw.layerIndex ?? [],
|
|
29917
|
+
tracks: raw.tracks ?? [],
|
|
29918
|
+
// Compat: early v4 snapshots (before field rename) have 'trackProjectIndex'
|
|
29919
|
+
trackSystemIndex: raw.trackSystemIndex ?? anyRaw["trackProjectIndex"] ?? [],
|
|
29920
|
+
trackTeamspaceIndex: raw.trackTeamspaceIndex ?? [],
|
|
29921
|
+
workItems: raw.workItems ?? [],
|
|
29922
|
+
workItemIndex: raw.workItemIndex ?? []
|
|
30151
29923
|
};
|
|
30152
29924
|
}
|
|
30153
29925
|
async function resolveSnapshotInput(input) {
|
|
@@ -30170,7 +29942,11 @@ function formatImportSummary(result, targetTeamspaceId) {
|
|
|
30170
29942
|
` PromptLogs: ${result.counts.promptLogs}`,
|
|
30171
29943
|
` Beat Versions: ${result.counts.beatVersions ?? 0}`,
|
|
30172
29944
|
` Drops: ${result.counts.drops ?? 0}`,
|
|
30173
|
-
` Deliverables: ${result.counts.deliverables ?? 0}
|
|
29945
|
+
` Deliverables: ${result.counts.deliverables ?? 0}`,
|
|
29946
|
+
` Teamspaces: ${result.counts.teamspaces ?? 0}`,
|
|
29947
|
+
` Layers: ${result.counts.layers ?? 0}`,
|
|
29948
|
+
` Tracks: ${result.counts.tracks ?? 0}`,
|
|
29949
|
+
` Work Items: ${result.counts.workItems ?? 0}`
|
|
30174
29950
|
];
|
|
30175
29951
|
if (result.errors.length > 0) {
|
|
30176
29952
|
lines.push("", `Errors (${result.errors.length}):`);
|
|
@@ -30217,9 +29993,9 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30217
29993
|
if (typeof obj["version"] !== "number" || obj["version"] <= 0) {
|
|
30218
29994
|
throw new Error(`Snapshot${sourceHint} has an invalid or missing 'version' field \u2014 it may not be a valid project snapshot.`);
|
|
30219
29995
|
}
|
|
30220
|
-
const
|
|
30221
|
-
if (typeof
|
|
30222
|
-
throw new Error(`Snapshot${sourceHint} is missing '
|
|
29996
|
+
const systemOrProject = obj["system"] ?? obj["project"];
|
|
29997
|
+
if (typeof systemOrProject !== "object" || systemOrProject === null || typeof systemOrProject["projectId"] !== "string") {
|
|
29998
|
+
throw new Error(`Snapshot${sourceHint} is missing 'system.projectId' \u2014 it may not be a valid project snapshot.`);
|
|
30223
29999
|
}
|
|
30224
30000
|
}
|
|
30225
30001
|
|
|
@@ -30227,76 +30003,339 @@ function assertSnapshotShape(val, sourceHint) {
|
|
|
30227
30003
|
var ENTITY_TYPES = ["project", "beat", "revision"];
|
|
30228
30004
|
function registerSubscriptionTools(server, ctx, client) {
|
|
30229
30005
|
server.tool(
|
|
30230
|
-
"subscribe_to_entity",
|
|
30231
|
-
"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.",
|
|
30232
|
-
{
|
|
30233
|
-
entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
|
|
30234
|
-
entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
|
|
30235
|
-
projectId: external_exports.string().describe("The project ID (for access control)")
|
|
30236
|
-
},
|
|
30237
|
-
async ({ entityType, entityId, projectId }) => {
|
|
30238
|
-
try {
|
|
30239
|
-
await assertProjectInOrg(client, projectId, ctx.orgId);
|
|
30240
|
-
await client.subscribe(ctx.user.userId, entityType, entityId, projectId, "manual");
|
|
30241
|
-
return {
|
|
30242
|
-
content: [{
|
|
30243
|
-
type: "text",
|
|
30244
|
-
text: `Now following ${entityType} ${entityId}. You'll receive notifications when it changes.`
|
|
30245
|
-
}]
|
|
30246
|
-
};
|
|
30247
|
-
} catch (err) {
|
|
30248
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
30249
|
-
return { content: [{ type: "text", text: `Failed to subscribe: ${message}` }], isError: true };
|
|
30006
|
+
"subscribe_to_entity",
|
|
30007
|
+
"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.",
|
|
30008
|
+
{
|
|
30009
|
+
entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to follow"),
|
|
30010
|
+
entityId: external_exports.string().describe("The entity ID (project ID, beat ID, or revision ID)"),
|
|
30011
|
+
projectId: external_exports.string().describe("The project ID (for access control)")
|
|
30012
|
+
},
|
|
30013
|
+
async ({ entityType, entityId, projectId }) => {
|
|
30014
|
+
try {
|
|
30015
|
+
await assertProjectInOrg(client, projectId, ctx.orgId);
|
|
30016
|
+
await client.subscribe(ctx.user.userId, entityType, entityId, projectId, "manual");
|
|
30017
|
+
return {
|
|
30018
|
+
content: [{
|
|
30019
|
+
type: "text",
|
|
30020
|
+
text: `Now following ${entityType} ${entityId}. You'll receive notifications when it changes.`
|
|
30021
|
+
}]
|
|
30022
|
+
};
|
|
30023
|
+
} catch (err) {
|
|
30024
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30025
|
+
return { content: [{ type: "text", text: `Failed to subscribe: ${message}` }], isError: true };
|
|
30026
|
+
}
|
|
30027
|
+
}
|
|
30028
|
+
);
|
|
30029
|
+
server.tool(
|
|
30030
|
+
"unsubscribe_from_entity",
|
|
30031
|
+
"Stop following a Project, Beat, or Revision. Use durable=true to prevent auto-resubscribe if you are later re-assigned.",
|
|
30032
|
+
{
|
|
30033
|
+
entityType: external_exports.enum(ENTITY_TYPES).describe("Type of entity to unfollow"),
|
|
30034
|
+
entityId: external_exports.string().describe("The entity ID"),
|
|
30035
|
+
durable: external_exports.boolean().optional().default(false).describe("If true, prevents auto-resubscribe on reassignment")
|
|
30036
|
+
},
|
|
30037
|
+
async ({ entityType, entityId, durable }) => {
|
|
30038
|
+
try {
|
|
30039
|
+
await client.unsubscribe(ctx.user.userId, entityType, entityId, durable);
|
|
30040
|
+
const durableMsg = durable ? " (durable \u2014 will not auto-resubscribe on reassignment)" : "";
|
|
30041
|
+
return {
|
|
30042
|
+
content: [{
|
|
30043
|
+
type: "text",
|
|
30044
|
+
text: `Unfollowed ${entityType} ${entityId}${durableMsg}.`
|
|
30045
|
+
}]
|
|
30046
|
+
};
|
|
30047
|
+
} catch (err) {
|
|
30048
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30049
|
+
return { content: [{ type: "text", text: `Failed to unsubscribe: ${message}` }], isError: true };
|
|
30050
|
+
}
|
|
30051
|
+
}
|
|
30052
|
+
);
|
|
30053
|
+
server.tool(
|
|
30054
|
+
"list_subscriptions",
|
|
30055
|
+
"List entities you are currently following. Optionally filter by entity type (project, beat, revision).",
|
|
30056
|
+
{
|
|
30057
|
+
entityType: external_exports.enum(ENTITY_TYPES).optional().describe("Filter by entity type")
|
|
30058
|
+
},
|
|
30059
|
+
async ({ entityType }) => {
|
|
30060
|
+
try {
|
|
30061
|
+
const subs = await client.listSubscriptions(ctx.user.userId, entityType);
|
|
30062
|
+
if (subs.length === 0) {
|
|
30063
|
+
return { content: [{ type: "text", text: "You are not following any entities." }] };
|
|
30064
|
+
}
|
|
30065
|
+
const lines = [`# Your Subscriptions (${subs.length})`, ""];
|
|
30066
|
+
for (const sub of subs) {
|
|
30067
|
+
lines.push(`- **${sub.entityType}** ${sub.entityId} (${sub.source}, since ${sub.createdAt?.split("T")[0] ?? "unknown"})`);
|
|
30068
|
+
}
|
|
30069
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
30070
|
+
} catch (err) {
|
|
30071
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30072
|
+
return { content: [{ type: "text", text: `Failed to list subscriptions: ${message}` }], isError: true };
|
|
30073
|
+
}
|
|
30074
|
+
}
|
|
30075
|
+
);
|
|
30076
|
+
}
|
|
30077
|
+
|
|
30078
|
+
// ../../libs/harmonica-services/src/mcp/tools/system-lifecycle-tools.ts
|
|
30079
|
+
function registerProjectLifecycleTools(server, ctx, client) {
|
|
30080
|
+
server.tool(
|
|
30081
|
+
"transition_system_lifecycle",
|
|
30082
|
+
"Advance a System's capability-maturity lifecycle state. Allowed transitions follow the state machine (Concept \u2192 Incubating \u2192 Piloting \u2192 Activated \u2192 Commercializing \u2192 Scaled, plus paused/killed/sunset/archived exits). The activated \u2192 commercializing edge requires a `decisionNoteId` pointing to a system-scoped Decision Note (the governance review). Other gates are advisory.",
|
|
30083
|
+
{
|
|
30084
|
+
systemId: external_exports.string().describe("The system ID"),
|
|
30085
|
+
targetState: external_exports.enum([
|
|
30086
|
+
"concept",
|
|
30087
|
+
"incubating",
|
|
30088
|
+
"piloting",
|
|
30089
|
+
"activated",
|
|
30090
|
+
"commercializing",
|
|
30091
|
+
"scaled",
|
|
30092
|
+
"paused",
|
|
30093
|
+
"killed",
|
|
30094
|
+
"sunset",
|
|
30095
|
+
"archived"
|
|
30096
|
+
]).describe("Target lifecycle state. Active: concept, incubating, piloting, activated, commercializing, scaled. Exits: paused, killed, sunset, archived."),
|
|
30097
|
+
reason: external_exports.string().optional().describe("Why this transition is being made (recorded in audit metadata)"),
|
|
30098
|
+
decisionNoteId: external_exports.string().optional().describe("Note ID of the governance Decision Note. Required for the activated \u2192 commercializing transition; optional otherwise.")
|
|
30099
|
+
},
|
|
30100
|
+
async ({ systemId, targetState, reason, decisionNoteId }) => {
|
|
30101
|
+
try {
|
|
30102
|
+
const system = await client.getSystem(systemId);
|
|
30103
|
+
if (!system) {
|
|
30104
|
+
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
30105
|
+
}
|
|
30106
|
+
if (system.orgId !== ctx.orgId) {
|
|
30107
|
+
return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
|
|
30108
|
+
}
|
|
30109
|
+
const result = await client.transitionProjectLifecycleState(systemId, targetState, {
|
|
30110
|
+
actor: { type: "human", id: ctx.user.userId, name: ctx.user.name },
|
|
30111
|
+
reason,
|
|
30112
|
+
decisionNoteId
|
|
30113
|
+
});
|
|
30114
|
+
if (!result.success) {
|
|
30115
|
+
const failedGates = result.error?.failedGates?.length ? ` (failed gates: ${result.error.failedGates.join(", ")})` : "";
|
|
30116
|
+
return {
|
|
30117
|
+
content: [{ type: "text", text: `Transition failed: ${result.error?.message ?? "Unknown error"}${failedGates}` }],
|
|
30118
|
+
isError: true
|
|
30119
|
+
};
|
|
30120
|
+
}
|
|
30121
|
+
const lines = [
|
|
30122
|
+
`System lifecycle transitioned successfully.`,
|
|
30123
|
+
"",
|
|
30124
|
+
`**System:** ${systemId}`,
|
|
30125
|
+
`**Title:** ${system.title}`,
|
|
30126
|
+
`**Transition:** ${result.previousState} \u2192 ${result.newState}`
|
|
30127
|
+
];
|
|
30128
|
+
if (reason) lines.push(`**Reason:** ${reason}`);
|
|
30129
|
+
if (decisionNoteId) lines.push(`**Decision Note:** ${decisionNoteId}`);
|
|
30130
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
30131
|
+
} catch (err) {
|
|
30132
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30133
|
+
return { content: [{ type: "text", text: `Failed to transition system lifecycle: ${message}` }], isError: true };
|
|
30134
|
+
}
|
|
30135
|
+
}
|
|
30136
|
+
);
|
|
30137
|
+
}
|
|
30138
|
+
|
|
30139
|
+
// ../../libs/harmonica-services/src/mcp/tools/system-tools.ts
|
|
30140
|
+
var import_crypto5 = require("crypto");
|
|
30141
|
+
function accountLine(request, resolved) {
|
|
30142
|
+
const requested = request["accountId"];
|
|
30143
|
+
if (requested === null || requested === "") return "**Account:** (unlinked)";
|
|
30144
|
+
return resolved ? `**Account:** ${resolved}` : "";
|
|
30145
|
+
}
|
|
30146
|
+
var PROJECT_EMBEDDING_FIELDS = ["title", "description", "strategy"];
|
|
30147
|
+
function registerProjectTools(server, ctx, client) {
|
|
30148
|
+
const listSystemsHandler = async ({ teamspaceId }) => {
|
|
30149
|
+
try {
|
|
30150
|
+
const projects = await client.listOrgSystems(ctx.orgId);
|
|
30151
|
+
const trimmed = teamspaceId?.trim();
|
|
30152
|
+
const filtered = trimmed ? projects.filter((p) => p.teamspaceId === trimmed) : projects;
|
|
30153
|
+
const text = formatProjectSummaryTable(filtered);
|
|
30154
|
+
return { content: [{ type: "text", text }] };
|
|
30155
|
+
} catch (err) {
|
|
30156
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30157
|
+
return { content: [{ type: "text", text: `Failed to list systems: ${message}` }], isError: true };
|
|
30158
|
+
}
|
|
30159
|
+
};
|
|
30160
|
+
server.tool(
|
|
30161
|
+
"list_systems",
|
|
30162
|
+
"List all systems (projects) in the configured organization",
|
|
30163
|
+
{
|
|
30164
|
+
teamspaceId: external_exports.string().optional().describe("Filter to systems belonging to a specific teamspace. Empty or whitespace-only treated as no filter.")
|
|
30165
|
+
},
|
|
30166
|
+
listSystemsHandler
|
|
30167
|
+
);
|
|
30168
|
+
const getSystemContextSchema = {
|
|
30169
|
+
systemId: external_exports.string().describe("The system ID"),
|
|
30170
|
+
noteLimit: external_exports.number().int().min(1).max(MAX_CONTEXT_NOTE_LIMIT).optional().describe(
|
|
30171
|
+
`Max Notes to include, prioritised by note type (default ${DEFAULT_CONTEXT_NOTE_LIMIT}, max ${MAX_CONTEXT_NOTE_LIMIT}). Use list_notes or search for the full set.`
|
|
30172
|
+
)
|
|
30173
|
+
};
|
|
30174
|
+
const getSystemContextHandler = async ({
|
|
30175
|
+
systemId,
|
|
30176
|
+
noteLimit
|
|
30177
|
+
}) => {
|
|
30178
|
+
try {
|
|
30179
|
+
const [project, org] = await Promise.all([
|
|
30180
|
+
fetchProjectInOrg(client, systemId, ctx.orgId),
|
|
30181
|
+
client.getOrg(ctx.orgId)
|
|
30182
|
+
]);
|
|
30183
|
+
const notes = await client.listProjectNotes(systemId, {
|
|
30184
|
+
limit: noteLimit ?? DEFAULT_CONTEXT_NOTE_LIMIT
|
|
30185
|
+
});
|
|
30186
|
+
const text = formatProjectContext(project, notes, org?.coda);
|
|
30187
|
+
return { content: [{ type: "text", text }] };
|
|
30188
|
+
} catch (err) {
|
|
30189
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30190
|
+
return { content: [{ type: "text", text: `Failed to get system context: ${message}` }], isError: true };
|
|
30191
|
+
}
|
|
30192
|
+
};
|
|
30193
|
+
server.tool(
|
|
30194
|
+
"get_system_context",
|
|
30195
|
+
"Get system metadata, description, and notes in one view",
|
|
30196
|
+
getSystemContextSchema,
|
|
30197
|
+
getSystemContextHandler
|
|
30198
|
+
);
|
|
30199
|
+
const updateSystemSchema = {
|
|
30200
|
+
systemId: external_exports.string().describe("The system ID"),
|
|
30201
|
+
title: external_exports.string().optional().describe("New system title"),
|
|
30202
|
+
description: external_exports.string().optional().describe("New system description"),
|
|
30203
|
+
strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
|
|
30204
|
+
teamspaceId: external_exports.string().nullable().optional().describe("Teamspace ID to associate this system with; pass null to remove the association"),
|
|
30205
|
+
accountId: external_exports.string().nullable().optional().describe("Account that owns this System \u2014 the client or internal org unit. Pass null (or an empty string) to unlink. Reassigning moves the System so it is listed under exactly one Account."),
|
|
30206
|
+
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
30207
|
+
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
30208
|
+
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")'),
|
|
30209
|
+
rateLimitOverrides: external_exports.record(external_exports.string(), external_exports.object({ maxPerHour: external_exports.number().min(0) })).optional().describe('Per-task-type rate limit overrides, e.g. {"agent_chat":{"maxPerHour":100}}. Overrides env var and compiled defaults.')
|
|
30210
|
+
};
|
|
30211
|
+
const updateSystemHandler = async ({ systemId, ...updates }) => {
|
|
30212
|
+
try {
|
|
30213
|
+
const nonEmpty = Object.fromEntries(
|
|
30214
|
+
Object.entries(updates).filter(([, v]) => v !== void 0)
|
|
30215
|
+
);
|
|
30216
|
+
if (Object.keys(nonEmpty).length === 0) {
|
|
30217
|
+
return { content: [{ type: "text", text: "No updates provided." }], isError: true };
|
|
30218
|
+
}
|
|
30219
|
+
await assertProjectInOrg(client, systemId, ctx.orgId);
|
|
30220
|
+
if (nonEmpty["accountId"] === "") nonEmpty["accountId"] = null;
|
|
30221
|
+
if (typeof updates.teamspaceId === "string") {
|
|
30222
|
+
const teamspace = await client.getTeamspace(updates.teamspaceId);
|
|
30223
|
+
if (!teamspace) {
|
|
30224
|
+
return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
|
|
30225
|
+
}
|
|
30226
|
+
if (teamspace.orgId !== ctx.orgId) {
|
|
30227
|
+
return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
|
|
30228
|
+
}
|
|
30229
|
+
}
|
|
30230
|
+
const updated = await client.updateSystem(systemId, nonEmpty);
|
|
30231
|
+
if (!updated) {
|
|
30232
|
+
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
30250
30233
|
}
|
|
30234
|
+
if (PROJECT_EMBEDDING_FIELDS.some((f) => f in nonEmpty)) {
|
|
30235
|
+
void client.triggerProjectEmbedding(systemId);
|
|
30236
|
+
}
|
|
30237
|
+
const lines = [
|
|
30238
|
+
`System updated successfully.`,
|
|
30239
|
+
"",
|
|
30240
|
+
`**ID:** ${updated.projectId}`,
|
|
30241
|
+
`**Title:** ${updated.title}`,
|
|
30242
|
+
accountLine(nonEmpty, updated.accountId),
|
|
30243
|
+
updated.strategy ? `**Strategy:** (updated)` : "",
|
|
30244
|
+
updated.repoOwner ? `**Repo:** ${updated.repoOwner}/${updated.repoName}` : "",
|
|
30245
|
+
updated.repoDefaultBranch ? `**Default Branch:** ${updated.repoDefaultBranch}` : ""
|
|
30246
|
+
].filter(Boolean);
|
|
30247
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
30248
|
+
} catch (err) {
|
|
30249
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30250
|
+
return { content: [{ type: "text", text: `Failed to update system: ${message}` }], isError: true };
|
|
30251
30251
|
}
|
|
30252
|
-
|
|
30252
|
+
};
|
|
30253
|
+
server.tool("update_system", "Update system settings such as title, description, repository configuration, or the Account that owns it", updateSystemSchema, updateSystemHandler);
|
|
30253
30254
|
server.tool(
|
|
30254
|
-
"
|
|
30255
|
-
"
|
|
30256
|
-
{
|
|
30257
|
-
|
|
30258
|
-
|
|
30259
|
-
|
|
30260
|
-
|
|
30261
|
-
|
|
30255
|
+
"archive_system",
|
|
30256
|
+
"Archive a system, hiding it from the system dropdown and all active system views. Use this when a system is no longer active and should be removed from navigation.",
|
|
30257
|
+
{ systemId: external_exports.string().describe("The system ID to archive") },
|
|
30258
|
+
async ({ systemId }) => {
|
|
30259
|
+
const system = await client.getSystem(systemId);
|
|
30260
|
+
if (!system) {
|
|
30261
|
+
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
30262
|
+
}
|
|
30263
|
+
if (system.orgId !== ctx.orgId) {
|
|
30264
|
+
return { content: [{ type: "text", text: `System "${systemId}" is not in this organization` }], isError: true };
|
|
30265
|
+
}
|
|
30262
30266
|
try {
|
|
30263
|
-
await client.
|
|
30264
|
-
|
|
30267
|
+
const updated = await client.archiveSystem(systemId);
|
|
30268
|
+
if (!updated) {
|
|
30269
|
+
return { content: [{ type: "text", text: `System not found: "${systemId}"` }], isError: true };
|
|
30270
|
+
}
|
|
30265
30271
|
return {
|
|
30266
30272
|
content: [{
|
|
30267
30273
|
type: "text",
|
|
30268
|
-
text:
|
|
30274
|
+
text: [
|
|
30275
|
+
"System archived successfully.",
|
|
30276
|
+
"",
|
|
30277
|
+
`**ID:** ${updated.projectId}`,
|
|
30278
|
+
`**Title:** ${updated.title}`,
|
|
30279
|
+
`**Status:** ${updated.status}`
|
|
30280
|
+
].join("\n")
|
|
30269
30281
|
}]
|
|
30270
30282
|
};
|
|
30271
30283
|
} catch (err) {
|
|
30272
30284
|
const message = err instanceof Error ? err.message : String(err);
|
|
30273
|
-
return { content: [{ type: "text", text: `Failed to
|
|
30285
|
+
return { content: [{ type: "text", text: `Failed to archive system: ${message}` }], isError: true };
|
|
30274
30286
|
}
|
|
30275
30287
|
}
|
|
30276
30288
|
);
|
|
30277
|
-
|
|
30278
|
-
"
|
|
30279
|
-
|
|
30280
|
-
|
|
30281
|
-
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
|
|
30291
|
-
|
|
30292
|
-
|
|
30293
|
-
return { content: [{ type: "text", text:
|
|
30294
|
-
} catch (err) {
|
|
30295
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
30296
|
-
return { content: [{ type: "text", text: `Failed to list subscriptions: ${message}` }], isError: true };
|
|
30289
|
+
const createSystemSchema = {
|
|
30290
|
+
title: external_exports.string().describe("System title"),
|
|
30291
|
+
description: external_exports.string().optional().describe("System description"),
|
|
30292
|
+
strategy: external_exports.string().optional().describe('System strategy markdown \u2014 Org Strategy + System Strategy ("System Coda")'),
|
|
30293
|
+
teamspaceId: external_exports.string().optional().describe("Teamspace ID to associate this system with"),
|
|
30294
|
+
repoOwner: external_exports.string().optional().describe("GitHub repository owner (org or user)"),
|
|
30295
|
+
repoName: external_exports.string().optional().describe("GitHub repository name"),
|
|
30296
|
+
repoDefaultBranch: external_exports.string().optional().describe('Default branch for code check (defaults to "main")')
|
|
30297
|
+
};
|
|
30298
|
+
const createSystemHandler = async ({ title, description, strategy, teamspaceId, repoOwner, repoName, repoDefaultBranch }) => {
|
|
30299
|
+
if (teamspaceId) {
|
|
30300
|
+
const teamspace = await client.getTeamspace(teamspaceId);
|
|
30301
|
+
if (!teamspace) {
|
|
30302
|
+
return { content: [{ type: "text", text: "Teamspace not found" }], isError: true };
|
|
30303
|
+
}
|
|
30304
|
+
if (teamspace.orgId !== ctx.orgId) {
|
|
30305
|
+
return { content: [{ type: "text", text: "Teamspace does not belong to this organization" }], isError: true };
|
|
30297
30306
|
}
|
|
30298
30307
|
}
|
|
30299
|
-
|
|
30308
|
+
try {
|
|
30309
|
+
const project = await client.createSystem({
|
|
30310
|
+
projectId: (0, import_crypto5.randomUUID)(),
|
|
30311
|
+
orgId: ctx.orgId,
|
|
30312
|
+
ownerUserId: ctx.user.userId,
|
|
30313
|
+
title,
|
|
30314
|
+
description,
|
|
30315
|
+
strategy,
|
|
30316
|
+
status: "active",
|
|
30317
|
+
teamspaceId,
|
|
30318
|
+
repoOwner,
|
|
30319
|
+
repoName,
|
|
30320
|
+
repoDefaultBranch
|
|
30321
|
+
});
|
|
30322
|
+
const text = [
|
|
30323
|
+
`System created successfully.`,
|
|
30324
|
+
"",
|
|
30325
|
+
`**ID:** ${project.projectId}`,
|
|
30326
|
+
`**Title:** ${project.title}`,
|
|
30327
|
+
`**Status:** ${project.status}`,
|
|
30328
|
+
project.description ? `**Description:** ${project.description}` : "",
|
|
30329
|
+
project.repoOwner ? `**Repo:** ${project.repoOwner}/${project.repoName}` : "",
|
|
30330
|
+
project.repoDefaultBranch ? `**Default Branch:** ${project.repoDefaultBranch}` : ""
|
|
30331
|
+
].filter(Boolean).join("\n");
|
|
30332
|
+
return { content: [{ type: "text", text }] };
|
|
30333
|
+
} catch (err) {
|
|
30334
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
30335
|
+
return { content: [{ type: "text", text: `Failed to create system: ${message}` }], isError: true };
|
|
30336
|
+
}
|
|
30337
|
+
};
|
|
30338
|
+
server.tool("create_system", "Create a new system in the configured organization", createSystemSchema, createSystemHandler);
|
|
30300
30339
|
}
|
|
30301
30340
|
|
|
30302
30341
|
// ../../libs/harmonica-services/src/mcp/tools/teamspace-tools.ts
|
|
@@ -30754,8 +30793,15 @@ function registerValueVelocityTools(server, ctx, client) {
|
|
|
30754
30793
|
// ../../libs/harmonica-services/src/mcp/tools/work-item-tools.ts
|
|
30755
30794
|
var WORK_ITEM_STATUS_ENUM = ["not_started", "in_progress", "blocked", "done"];
|
|
30756
30795
|
function formatOwner(owner) {
|
|
30757
|
-
return `${owner.name} <${owner.email}
|
|
30758
|
-
}
|
|
30796
|
+
return owner ? `${owner.name} <${owner.email}>` : "unassigned";
|
|
30797
|
+
}
|
|
30798
|
+
var WORK_ITEM_EFFORT_SIZE_ENUM = [
|
|
30799
|
+
"XS",
|
|
30800
|
+
"S",
|
|
30801
|
+
"M",
|
|
30802
|
+
"L",
|
|
30803
|
+
"XL"
|
|
30804
|
+
];
|
|
30759
30805
|
function registerWorkItemTools(server, _ctx, client) {
|
|
30760
30806
|
server.tool(
|
|
30761
30807
|
"list_track_work_items",
|
|
@@ -30791,6 +30837,9 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
30791
30837
|
`Status: ${wi.status}`,
|
|
30792
30838
|
wi.committedEstimateHours !== void 0 ? `Committed Estimate: ${wi.committedEstimateHours}h` : null,
|
|
30793
30839
|
wi.actualHours !== void 0 ? `Actual Hours: ${wi.actualHours}h` : null,
|
|
30840
|
+
wi.effortSize !== void 0 ? `Effort Size: ${wi.effortSize}` : null,
|
|
30841
|
+
wi.valueUnits !== void 0 ? `Value Units: ${wi.valueUnits}` : null,
|
|
30842
|
+
wi.costPerValueUnit !== void 0 ? `Cost per Value Unit: ${wi.costPerValueUnit}` : null,
|
|
30794
30843
|
wi.beatVersionId ? `Linked Beat Version: ${wi.beatVersionId}` : null,
|
|
30795
30844
|
estimateHint ? `Estimate Hint: ${estimateHint.hours}h (source: ${estimateHint.source})` : null,
|
|
30796
30845
|
wi.createdBy ? `Created By: ${wi.createdBy}` : null,
|
|
@@ -30806,18 +30855,30 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
30806
30855
|
{
|
|
30807
30856
|
trackId: external_exports.string().describe("The Track (DeliverableGroup) ID this work item belongs to"),
|
|
30808
30857
|
title: external_exports.string().min(1).max(300).describe("Work item title"),
|
|
30809
|
-
ownerName: external_exports.string().min(1).describe("
|
|
30810
|
-
ownerEmail: external_exports.string().email().describe("
|
|
30858
|
+
ownerName: external_exports.string().min(1).optional().describe("Optional owner display name (requires ownerEmail too)"),
|
|
30859
|
+
ownerEmail: external_exports.string().email().optional().describe("Optional owner email (requires ownerName too). A work item may be left unowned."),
|
|
30811
30860
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("Optional committed estimate in hours"),
|
|
30861
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).optional().describe("Optional rough t-shirt effort size (delivery lens) \u2014 independent of valueUnits"),
|
|
30862
|
+
valueUnits: external_exports.number().nonnegative().optional().describe("Optional Value Units (commercial lens) \u2014 independent of effortSize"),
|
|
30863
|
+
costPerValueUnit: external_exports.number().nonnegative().optional().describe("Optional price per Value Unit"),
|
|
30812
30864
|
createdBy: external_exports.string().optional().describe("Optional creator identity for audit"),
|
|
30813
30865
|
beatVersionId: external_exports.string().min(1).optional().describe("Optional Beat Version to link for capability lineage. Must exist.")
|
|
30814
30866
|
},
|
|
30815
|
-
async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, createdBy, beatVersionId }) => {
|
|
30867
|
+
async ({ trackId, title, ownerName, ownerEmail, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId }) => {
|
|
30868
|
+
if (ownerName === void 0 !== (ownerEmail === void 0)) {
|
|
30869
|
+
return {
|
|
30870
|
+
content: [{ type: "text", text: "Setting the owner requires both ownerName and ownerEmail. Omit both to create an unowned work item." }],
|
|
30871
|
+
isError: true
|
|
30872
|
+
};
|
|
30873
|
+
}
|
|
30816
30874
|
const wi = await client.createWorkItem({
|
|
30817
30875
|
trackId,
|
|
30818
30876
|
title,
|
|
30819
|
-
owner: { name: ownerName, email: ownerEmail },
|
|
30877
|
+
...ownerName !== void 0 && ownerEmail !== void 0 && { owner: { name: ownerName, email: ownerEmail } },
|
|
30820
30878
|
...committedEstimateHours !== void 0 && { committedEstimateHours },
|
|
30879
|
+
...effortSize !== void 0 && { effortSize },
|
|
30880
|
+
...valueUnits !== void 0 && { valueUnits },
|
|
30881
|
+
...costPerValueUnit !== void 0 && { costPerValueUnit },
|
|
30821
30882
|
...createdBy !== void 0 && { createdBy },
|
|
30822
30883
|
...beatVersionId !== void 0 && { beatVersionId }
|
|
30823
30884
|
});
|
|
@@ -30833,17 +30894,21 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
30833
30894
|
);
|
|
30834
30895
|
server.tool(
|
|
30835
30896
|
"update_work_item",
|
|
30836
|
-
"Update mutable content fields on a work item (title, owner, committedEstimateHours, actualHours, beatVersionId). To move it between Tracks use move_work_item; to change its status use transition_work_item_status.",
|
|
30897
|
+
"Update mutable content fields on a work item (title, owner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId). To move it between Tracks use move_work_item; to change its status use transition_work_item_status.",
|
|
30837
30898
|
{
|
|
30838
30899
|
workItemId: external_exports.string().describe("The work item ID"),
|
|
30839
30900
|
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
30840
30901
|
ownerName: external_exports.string().min(1).optional().describe("New owner display name (requires ownerEmail too)"),
|
|
30841
30902
|
ownerEmail: external_exports.string().email().optional().describe("New owner email (requires ownerName too)"),
|
|
30903
|
+
unassignOwner: external_exports.boolean().optional().describe("Pass true to clear the owner, leaving the work item unowned. Cannot be combined with ownerName/ownerEmail."),
|
|
30842
30904
|
committedEstimateHours: external_exports.number().nonnegative().optional().describe("New committed estimate in hours"),
|
|
30843
30905
|
actualHours: external_exports.number().nonnegative().optional().describe("New actual hours logged"),
|
|
30906
|
+
effortSize: external_exports.enum(WORK_ITEM_EFFORT_SIZE_ENUM).nullable().optional().describe("Rough t-shirt effort size (delivery lens). Pass null to clear; omit to leave untouched."),
|
|
30907
|
+
valueUnits: external_exports.number().nonnegative().nullable().optional().describe("Value Units (commercial lens). Pass null to clear; omit to leave untouched."),
|
|
30908
|
+
costPerValueUnit: external_exports.number().nonnegative().nullable().optional().describe("Price per Value Unit. Pass null to clear; omit to leave untouched."),
|
|
30844
30909
|
beatVersionId: external_exports.string().min(1).nullable().optional().describe("Link to a Beat Version for capability lineage. Pass null to unlink; omit to leave the current link untouched.")
|
|
30845
30910
|
},
|
|
30846
|
-
async ({ workItemId, title, ownerName, ownerEmail, committedEstimateHours, actualHours, beatVersionId }) => {
|
|
30911
|
+
async ({ workItemId, title, ownerName, ownerEmail, unassignOwner, committedEstimateHours, actualHours, effortSize, valueUnits, costPerValueUnit, beatVersionId }) => {
|
|
30847
30912
|
const hasOwnerUpdate = ownerName !== void 0 || ownerEmail !== void 0;
|
|
30848
30913
|
if (hasOwnerUpdate && (ownerName === void 0 || ownerEmail === void 0)) {
|
|
30849
30914
|
return {
|
|
@@ -30851,7 +30916,13 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
30851
30916
|
isError: true
|
|
30852
30917
|
};
|
|
30853
30918
|
}
|
|
30854
|
-
|
|
30919
|
+
if (unassignOwner && hasOwnerUpdate) {
|
|
30920
|
+
return {
|
|
30921
|
+
content: [{ type: "text", text: "Cannot set and clear the owner in one update \u2014 pass either unassignOwner or ownerName/ownerEmail." }],
|
|
30922
|
+
isError: true
|
|
30923
|
+
};
|
|
30924
|
+
}
|
|
30925
|
+
const hasUpdates = title !== void 0 || hasOwnerUpdate || unassignOwner === true || committedEstimateHours !== void 0 || actualHours !== void 0 || effortSize !== void 0 || valueUnits !== void 0 || costPerValueUnit !== void 0 || beatVersionId !== void 0;
|
|
30855
30926
|
if (!hasUpdates) {
|
|
30856
30927
|
return {
|
|
30857
30928
|
content: [{ type: "text", text: `No updates provided for work item: ${workItemId}` }],
|
|
@@ -30861,8 +30932,12 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
30861
30932
|
const wi = await client.updateWorkItem(workItemId, {
|
|
30862
30933
|
...title !== void 0 && { title },
|
|
30863
30934
|
...hasOwnerUpdate && { owner: { name: ownerName, email: ownerEmail } },
|
|
30935
|
+
...unassignOwner === true && { owner: null },
|
|
30864
30936
|
...committedEstimateHours !== void 0 && { committedEstimateHours },
|
|
30865
30937
|
...actualHours !== void 0 && { actualHours },
|
|
30938
|
+
...effortSize !== void 0 && { effortSize },
|
|
30939
|
+
...valueUnits !== void 0 && { valueUnits },
|
|
30940
|
+
...costPerValueUnit !== void 0 && { costPerValueUnit },
|
|
30866
30941
|
...beatVersionId !== void 0 && { beatVersionId }
|
|
30867
30942
|
});
|
|
30868
30943
|
if (!wi) {
|
|
@@ -31408,7 +31483,7 @@ function registerBeatResources(server, ctx, client) {
|
|
|
31408
31483
|
async (uri, { projectId }) => {
|
|
31409
31484
|
const pid = String(projectId);
|
|
31410
31485
|
await assertProjectInOrg(client, pid, ctx.orgId);
|
|
31411
|
-
const beats = await client.
|
|
31486
|
+
const beats = await client.listSystemBeats(pid);
|
|
31412
31487
|
const text = formatBeatSummaryTable(beats);
|
|
31413
31488
|
return { contents: [{ uri: uri.href, text }] };
|
|
31414
31489
|
}
|
|
@@ -31442,7 +31517,7 @@ function registerGuidelinesResources(server, client) {
|
|
|
31442
31517
|
);
|
|
31443
31518
|
}
|
|
31444
31519
|
|
|
31445
|
-
// ../../libs/harmonica-services/src/mcp/resources/
|
|
31520
|
+
// ../../libs/harmonica-services/src/mcp/resources/system-resources.ts
|
|
31446
31521
|
function registerProjectResources(server, ctx, client) {
|
|
31447
31522
|
server.resource(
|
|
31448
31523
|
"project-context",
|
|
@@ -31450,7 +31525,7 @@ function registerProjectResources(server, ctx, client) {
|
|
|
31450
31525
|
async (uri, { projectId }) => {
|
|
31451
31526
|
const pid = String(projectId);
|
|
31452
31527
|
const project = await fetchProjectInOrg(client, pid, ctx.orgId);
|
|
31453
|
-
const notes = await client.
|
|
31528
|
+
const notes = await client.listSystemNotes(pid, { limit: DEFAULT_CONTEXT_NOTE_LIMIT });
|
|
31454
31529
|
const text = formatProjectContext(project, notes);
|
|
31455
31530
|
return { contents: [{ uri: uri.href, text }] };
|
|
31456
31531
|
}
|
|
@@ -31771,7 +31846,7 @@ function createHttpClient(config2) {
|
|
|
31771
31846
|
const result = await request("POST", `/api/systems/${encodeURIComponent(projectId)}/beats/next-id`);
|
|
31772
31847
|
return result.beatId;
|
|
31773
31848
|
},
|
|
31774
|
-
|
|
31849
|
+
listSystemBeats: async (projectId) => {
|
|
31775
31850
|
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/beats`);
|
|
31776
31851
|
return (result?.beats ?? []).map((b) => ({
|
|
31777
31852
|
...b,
|
|
@@ -31927,6 +32002,21 @@ function createHttpClient(config2) {
|
|
|
31927
32002
|
const result = await request("POST", `/api/layers/${encodeURIComponent(layerId)}/status`, { status, reason });
|
|
31928
32003
|
return result?.layer;
|
|
31929
32004
|
},
|
|
32005
|
+
listSystemNotes: async (projectId, filters) => {
|
|
32006
|
+
const params = new URLSearchParams();
|
|
32007
|
+
if (filters?.noteType) {
|
|
32008
|
+
const types = Array.isArray(filters.noteType) ? filters.noteType : [filters.noteType];
|
|
32009
|
+
types.forEach((t) => params.append("type", t));
|
|
32010
|
+
}
|
|
32011
|
+
if (filters?.status) params.set("status", filters.status);
|
|
32012
|
+
if (filters?.revisionId) params.set("revisionId", filters.revisionId);
|
|
32013
|
+
if (filters?.excludeChildren) params.set("excludeChildren", "true");
|
|
32014
|
+
if (filters?.significance) params.set("significance", filters.significance);
|
|
32015
|
+
if (filters?.limit !== void 0) params.set("limit", String(filters.limit));
|
|
32016
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
32017
|
+
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/notes${qs}`);
|
|
32018
|
+
return result?.notes ?? [];
|
|
32019
|
+
},
|
|
31930
32020
|
listProjectNotes: async (projectId, filters) => {
|
|
31931
32021
|
const params = new URLSearchParams();
|
|
31932
32022
|
if (filters?.noteType) {
|
|
@@ -32066,6 +32156,14 @@ function createHttpClient(config2) {
|
|
|
32066
32156
|
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/revisions/pending${qs}`);
|
|
32067
32157
|
return result ?? [];
|
|
32068
32158
|
},
|
|
32159
|
+
listSystemRevisions: async (projectId, options) => {
|
|
32160
|
+
const params = new URLSearchParams();
|
|
32161
|
+
if (options?.status) params.set("status", options.status);
|
|
32162
|
+
if (options?.includeArchived) params.set("includeArchived", "true");
|
|
32163
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
32164
|
+
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/revisions${qs}`);
|
|
32165
|
+
return result ?? [];
|
|
32166
|
+
},
|
|
32069
32167
|
listProjectRevisions: async (projectId, options) => {
|
|
32070
32168
|
const params = new URLSearchParams();
|
|
32071
32169
|
if (options?.status) params.set("status", options.status);
|
|
@@ -32295,6 +32393,14 @@ function createHttpClient(config2) {
|
|
|
32295
32393
|
// partitions, but the REST surface is unified per the project-level
|
|
32296
32394
|
// "avoid nested/duplicate routes" guidance.
|
|
32297
32395
|
getSession: (sessionId) => request("GET", `/api/sessions/${encodeURIComponent(sessionId)}`),
|
|
32396
|
+
listSystemSessions: async (projectId, options) => {
|
|
32397
|
+
const params = new URLSearchParams();
|
|
32398
|
+
params.set("projectId", projectId);
|
|
32399
|
+
if (options?.status) params.set("status", options.status);
|
|
32400
|
+
if (options?.includeTerminal) params.set("includeTerminal", "true");
|
|
32401
|
+
const result = await request("GET", `/api/sessions?${params.toString()}`);
|
|
32402
|
+
return result ?? [];
|
|
32403
|
+
},
|
|
32298
32404
|
listProjectSessions: async (projectId, options) => {
|
|
32299
32405
|
const params = new URLSearchParams();
|
|
32300
32406
|
params.set("projectId", projectId);
|
|
@@ -32482,6 +32588,14 @@ function createHttpClient(config2) {
|
|
|
32482
32588
|
getTask: async (taskId) => {
|
|
32483
32589
|
return request("GET", `/api/tasks/${encodeURIComponent(taskId)}`);
|
|
32484
32590
|
},
|
|
32591
|
+
listSystemTasks: async (projectId, options) => {
|
|
32592
|
+
const params = new URLSearchParams();
|
|
32593
|
+
if (options?.status) params.set("status", options.status);
|
|
32594
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
32595
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
32596
|
+
const result = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/tasks${qs}`);
|
|
32597
|
+
return result?.tasks ?? [];
|
|
32598
|
+
},
|
|
32485
32599
|
listProjectTasks: async (projectId, options) => {
|
|
32486
32600
|
const params = new URLSearchParams();
|
|
32487
32601
|
if (options?.status) params.set("status", options.status);
|
|
@@ -32513,6 +32627,15 @@ function createHttpClient(config2) {
|
|
|
32513
32627
|
}, LONG_RUNNING_TIMEOUT_MS);
|
|
32514
32628
|
},
|
|
32515
32629
|
// Activity — forward to API endpoint
|
|
32630
|
+
listSystemActivities: async (projectId, options) => {
|
|
32631
|
+
const params = new URLSearchParams();
|
|
32632
|
+
if (options?.limit) params.set("limit", String(options.limit));
|
|
32633
|
+
if (options?.minImportance) params.set("importance", options.minImportance);
|
|
32634
|
+
const qs = params.toString();
|
|
32635
|
+
const path = `/api/systems/${encodeURIComponent(projectId)}/activities${qs ? `?${qs}` : ""}`;
|
|
32636
|
+
const result = await request("GET", path);
|
|
32637
|
+
return { entries: result.activities ?? [], hasMore: false };
|
|
32638
|
+
},
|
|
32516
32639
|
listProjectActivities: async (projectId, options) => {
|
|
32517
32640
|
const params = new URLSearchParams();
|
|
32518
32641
|
if (options?.limit) params.set("limit", String(options.limit));
|
|
@@ -32794,8 +32917,8 @@ function createHttpClient(config2) {
|
|
|
32794
32917
|
}
|
|
32795
32918
|
return download;
|
|
32796
32919
|
},
|
|
32797
|
-
|
|
32798
|
-
|
|
32920
|
+
importSystemSnapshot: (snapshot, options) => request("POST", "/api/systems/import", { ...snapshot, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
|
|
32921
|
+
importSystemSnapshotFromUrl: (url2, options) => request("POST", "/api/systems/import", { snapshotUrl: url2, importOptions: options }, LONG_RUNNING_TIMEOUT_MS),
|
|
32799
32922
|
// Embedding similarity
|
|
32800
32923
|
embedProjectEntities: (projectId) => request("POST", `/api/systems/${encodeURIComponent(projectId)}/embeddings/generate`),
|
|
32801
32924
|
findSimilarNotes: async (noteId, projectId, options) => {
|
|
@@ -32861,6 +32984,18 @@ function createHttpClient(config2) {
|
|
|
32861
32984
|
const res = await request("GET", `/api/checks/${encodeURIComponent(checkId)}`);
|
|
32862
32985
|
return res.check;
|
|
32863
32986
|
},
|
|
32987
|
+
listSystemChecks: async (projectId, checkType, options) => {
|
|
32988
|
+
const params = new URLSearchParams();
|
|
32989
|
+
if (checkType) params.set("type", checkType);
|
|
32990
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
32991
|
+
if (options?.cursor) params.set("cursor", options.cursor);
|
|
32992
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
32993
|
+
const res = await request(
|
|
32994
|
+
"GET",
|
|
32995
|
+
`/api/systems/${encodeURIComponent(projectId)}/checks${qs}`
|
|
32996
|
+
);
|
|
32997
|
+
return { checks: res?.checks ?? [], nextCursor: res?.nextCursor };
|
|
32998
|
+
},
|
|
32864
32999
|
listProjectChecks: async (projectId, checkType, options) => {
|
|
32865
33000
|
const params = new URLSearchParams();
|
|
32866
33001
|
if (checkType) params.set("type", checkType);
|
|
@@ -32959,6 +33094,11 @@ function createHttpClient(config2) {
|
|
|
32959
33094
|
if (res === void 0) throw new Error(`Teamspace ${teamspaceId} not found`);
|
|
32960
33095
|
return res.deliverableGroups;
|
|
32961
33096
|
},
|
|
33097
|
+
listSystemDeliverableGroups: async (projectId) => {
|
|
33098
|
+
const res = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/deliverable-groups`);
|
|
33099
|
+
if (res === void 0) throw new Error(`Project ${projectId} not found`);
|
|
33100
|
+
return res.deliverableGroups;
|
|
33101
|
+
},
|
|
32962
33102
|
listProjectDeliverableGroups: async (projectId) => {
|
|
32963
33103
|
const res = await request("GET", `/api/systems/${encodeURIComponent(projectId)}/deliverable-groups`);
|
|
32964
33104
|
if (res === void 0) throw new Error(`Project ${projectId} not found`);
|
|
@@ -33138,11 +33278,14 @@ function createHttpClient(config2) {
|
|
|
33138
33278
|
},
|
|
33139
33279
|
// WorkItems (WorkItem exposure layer — owned, statused unit of operational work under a Track)
|
|
33140
33280
|
createWorkItem: async (input) => {
|
|
33141
|
-
const { trackId, title, owner, committedEstimateHours, createdBy, beatVersionId } = input;
|
|
33281
|
+
const { trackId, title, owner, committedEstimateHours, effortSize, valueUnits, costPerValueUnit, createdBy, beatVersionId } = input;
|
|
33142
33282
|
const body = {
|
|
33143
33283
|
title,
|
|
33144
|
-
owner,
|
|
33284
|
+
...owner !== void 0 && { owner },
|
|
33145
33285
|
...committedEstimateHours !== void 0 && { committedEstimateHours },
|
|
33286
|
+
...effortSize !== void 0 && { effortSize },
|
|
33287
|
+
...valueUnits !== void 0 && { valueUnits },
|
|
33288
|
+
...costPerValueUnit !== void 0 && { costPerValueUnit },
|
|
33146
33289
|
...createdBy !== void 0 && { createdBy },
|
|
33147
33290
|
...beatVersionId !== void 0 && { beatVersionId }
|
|
33148
33291
|
};
|
|
@@ -33685,7 +33828,7 @@ function loadConfig() {
|
|
|
33685
33828
|
};
|
|
33686
33829
|
}
|
|
33687
33830
|
async function main() {
|
|
33688
|
-
console.error(`[harmonica-mcp] v${"2.
|
|
33831
|
+
console.error(`[harmonica-mcp] v${"2.1.0"} starting\u2026`);
|
|
33689
33832
|
const config2 = loadConfig();
|
|
33690
33833
|
const client = createHttpClient({
|
|
33691
33834
|
apiBaseUrl: config2.apiBaseUrl,
|