@codazen/harmonica-mcp 5.0.0 → 5.2.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 +321 -137
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22032,22 +22032,33 @@ function registerAuditIntegrityTools(server, ctx, client) {
|
|
|
22032
22032
|
);
|
|
22033
22033
|
}
|
|
22034
22034
|
|
|
22035
|
-
// ../../libs/harmonica-services/src/
|
|
22035
|
+
// ../../libs/harmonica-services/src/tool-schemas/bar.ts
|
|
22036
22036
|
var BAR_STATUSES = ["open", "closed"];
|
|
22037
22037
|
var idSegmentSchema = external_exports.string().min(1).max(128).refine((id) => !id.includes("#"), {
|
|
22038
22038
|
message: "must not contain '#' \u2014 it is a DynamoDB key delimiter"
|
|
22039
22039
|
});
|
|
22040
|
+
var create_bar = {
|
|
22041
|
+
accountId: idSegmentSchema.describe("The Account ID this Bar belongs to"),
|
|
22042
|
+
barId: idSegmentSchema.describe(
|
|
22043
|
+
'Caller-supplied Bar ID (must not contain "#")'
|
|
22044
|
+
),
|
|
22045
|
+
startDate: external_exports.string().datetime().describe("ISO 8601 start of the planning period"),
|
|
22046
|
+
endDate: external_exports.string().datetime().describe("ISO 8601 end of the planning period; must be after startDate"),
|
|
22047
|
+
status: external_exports.enum(BAR_STATUSES).optional().describe('Defaults to "open"')
|
|
22048
|
+
};
|
|
22049
|
+
var get_bar = {
|
|
22050
|
+
barId: idSegmentSchema.describe("The Bar ID")
|
|
22051
|
+
};
|
|
22052
|
+
var list_account_bars = {
|
|
22053
|
+
accountId: idSegmentSchema.describe("The Account ID")
|
|
22054
|
+
};
|
|
22055
|
+
|
|
22056
|
+
// ../../libs/harmonica-services/src/mcp/tools/bar-tools.ts
|
|
22040
22057
|
function registerBarTools(server, _ctx, client) {
|
|
22041
22058
|
server.tool(
|
|
22042
22059
|
"create_bar",
|
|
22043
22060
|
"Create a Bar \u2014 the recurring planning-period time-box a client Account plans and delivers within (e.g. one or two weeks). Bars are Account-scoped and independent of any CadenceConfig already set on the Account; the caller supplies explicit start/end dates.",
|
|
22044
|
-
|
|
22045
|
-
accountId: idSegmentSchema.describe("The Account ID this Bar belongs to"),
|
|
22046
|
-
barId: idSegmentSchema.describe('Caller-supplied Bar ID (must not contain "#")'),
|
|
22047
|
-
startDate: external_exports.string().datetime().describe("ISO 8601 start of the planning period"),
|
|
22048
|
-
endDate: external_exports.string().datetime().describe("ISO 8601 end of the planning period; must be after startDate"),
|
|
22049
|
-
status: external_exports.enum(BAR_STATUSES).optional().describe('Defaults to "open"')
|
|
22050
|
-
},
|
|
22061
|
+
create_bar,
|
|
22051
22062
|
async ({ accountId, barId, startDate, endDate, status }) => {
|
|
22052
22063
|
const startMs = new Date(startDate).getTime();
|
|
22053
22064
|
const endMs = new Date(endDate).getTime();
|
|
@@ -22077,9 +22088,7 @@ function registerBarTools(server, _ctx, client) {
|
|
|
22077
22088
|
server.tool(
|
|
22078
22089
|
"get_bar",
|
|
22079
22090
|
"Get a Bar by ID.",
|
|
22080
|
-
|
|
22081
|
-
barId: idSegmentSchema.describe("The Bar ID")
|
|
22082
|
-
},
|
|
22091
|
+
get_bar,
|
|
22083
22092
|
async ({ barId }) => {
|
|
22084
22093
|
try {
|
|
22085
22094
|
const bar = await client.getBar(barId);
|
|
@@ -22098,28 +22107,28 @@ function registerBarTools(server, _ctx, client) {
|
|
|
22098
22107
|
}
|
|
22099
22108
|
}
|
|
22100
22109
|
);
|
|
22101
|
-
|
|
22102
|
-
|
|
22103
|
-
|
|
22104
|
-
|
|
22105
|
-
|
|
22106
|
-
|
|
22107
|
-
|
|
22108
|
-
|
|
22109
|
-
|
|
22110
|
-
|
|
22111
|
-
|
|
22112
|
-
|
|
22113
|
-
|
|
22114
|
-
|
|
22110
|
+
for (const name of ["list_account_bars", "list_bars"]) {
|
|
22111
|
+
server.tool(
|
|
22112
|
+
name,
|
|
22113
|
+
"List an Account's Bars, chronological by start date.",
|
|
22114
|
+
list_account_bars,
|
|
22115
|
+
async ({ accountId }) => {
|
|
22116
|
+
try {
|
|
22117
|
+
const bars = await client.listAccountBars(accountId);
|
|
22118
|
+
if (bars === void 0) {
|
|
22119
|
+
return { content: [{ type: "text", text: `Account not found: ${accountId}` }], isError: true };
|
|
22120
|
+
}
|
|
22121
|
+
if (bars.length === 0) {
|
|
22122
|
+
return { content: [{ type: "text", text: "No bars found." }] };
|
|
22123
|
+
}
|
|
22124
|
+
const text = bars.map((b) => `${b.barId} ${b.startDate} \u2192 ${b.endDate} \u2014 ${b.status}`).join("\n");
|
|
22125
|
+
return { content: [{ type: "text", text }] };
|
|
22126
|
+
} catch (err) {
|
|
22127
|
+
return { content: [{ type: "text", text: `Failed to list bars: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
22115
22128
|
}
|
|
22116
|
-
const text = bars.map((b) => `${b.barId} ${b.startDate} \u2192 ${b.endDate} \u2014 ${b.status}`).join("\n");
|
|
22117
|
-
return { content: [{ type: "text", text }] };
|
|
22118
|
-
} catch (err) {
|
|
22119
|
-
return { content: [{ type: "text", text: `Failed to list bars: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
22120
22129
|
}
|
|
22121
|
-
|
|
22122
|
-
|
|
22130
|
+
);
|
|
22131
|
+
}
|
|
22123
22132
|
}
|
|
22124
22133
|
|
|
22125
22134
|
// ../../libs/harmonica-services/src/mcp/tools/baseline-tools.ts
|
|
@@ -22471,20 +22480,30 @@ var POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
22471
22480
|
async function pollForCheck(client, taskId) {
|
|
22472
22481
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
22473
22482
|
let consecutiveErrors = 0;
|
|
22483
|
+
let consecutiveMissing = 0;
|
|
22474
22484
|
while (Date.now() < deadline) {
|
|
22475
22485
|
const [task, err] = await client.getTask(taskId).then(
|
|
22476
22486
|
(t) => [t, null],
|
|
22477
22487
|
(e) => [null, e]
|
|
22478
22488
|
);
|
|
22479
22489
|
if (err) {
|
|
22490
|
+
consecutiveMissing = 0;
|
|
22480
22491
|
consecutiveErrors++;
|
|
22481
22492
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22482
22493
|
throw new Error(`getTask failed ${consecutiveErrors} consecutive times: ${err instanceof Error ? err.message : String(err)}`);
|
|
22483
22494
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22484
22495
|
continue;
|
|
22485
22496
|
}
|
|
22497
|
+
if (!task) {
|
|
22498
|
+
consecutiveErrors = 0;
|
|
22499
|
+
consecutiveMissing++;
|
|
22500
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS)
|
|
22501
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
22502
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
22503
|
+
continue;
|
|
22504
|
+
}
|
|
22486
22505
|
consecutiveErrors = 0;
|
|
22487
|
-
|
|
22506
|
+
consecutiveMissing = 0;
|
|
22488
22507
|
if (task.status === "completed") {
|
|
22489
22508
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
22490
22509
|
return task.result;
|
|
@@ -22792,6 +22811,18 @@ ${revision.description}`);
|
|
|
22792
22811
|
}
|
|
22793
22812
|
return sections.join("\n");
|
|
22794
22813
|
}
|
|
22814
|
+
function formatRevisionPreview(revisionId, preview) {
|
|
22815
|
+
if (!preview) return `No preview recorded for revision: ${revisionId}`;
|
|
22816
|
+
const freshness = preview.isCurrent ? "Current \u2014 built from the head commit." : `Stale \u2014 built from ${preview.commitSha?.slice(0, 7) ?? "an unknown commit"}, which is no longer the head. Still opens.`;
|
|
22817
|
+
return [
|
|
22818
|
+
`# Preview: ${revisionId}`,
|
|
22819
|
+
`**URL:** ${preview.url}`,
|
|
22820
|
+
`**Built from:** ${preview.commitSha}`,
|
|
22821
|
+
`**Recorded:** ${preview.recordedAt}`,
|
|
22822
|
+
`
|
|
22823
|
+
${freshness}`
|
|
22824
|
+
].join("\n");
|
|
22825
|
+
}
|
|
22795
22826
|
|
|
22796
22827
|
// ../../libs/harmonica-services/src/mcp/formatters/note-formatter.ts
|
|
22797
22828
|
var TYPE_LABELS = {
|
|
@@ -23300,6 +23331,26 @@ ${formatDeliveryPolicy(
|
|
|
23300
23331
|
}
|
|
23301
23332
|
}
|
|
23302
23333
|
);
|
|
23334
|
+
server.tool(
|
|
23335
|
+
"get_revision_preview",
|
|
23336
|
+
"Get the preview link recorded for a Revision \u2014 the running environment its own pipeline published, and whether that build is still the pull request's head. A Revision with no recorded preview is reported plainly, not as an error: one whose change artifact is a document never gets one. A stale link still opens; it was built before the latest commit.",
|
|
23337
|
+
{
|
|
23338
|
+
revisionId: external_exports.string().describe("The revision ID (e.g., rev-abc123)")
|
|
23339
|
+
},
|
|
23340
|
+
async ({ revisionId }) => {
|
|
23341
|
+
try {
|
|
23342
|
+
const revision = await client.getRevision(revisionId);
|
|
23343
|
+
if (!revision) {
|
|
23344
|
+
return { content: [{ type: "text", text: `Revision not found: ${revisionId}` }], isError: true };
|
|
23345
|
+
}
|
|
23346
|
+
const preview = await client.resolveRevisionPreview(revisionId);
|
|
23347
|
+
return { content: [{ type: "text", text: formatRevisionPreview(revisionId, preview) }] };
|
|
23348
|
+
} catch (err) {
|
|
23349
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23350
|
+
return { content: [{ type: "text", text: `Failed to get revision preview: ${message}` }], isError: true };
|
|
23351
|
+
}
|
|
23352
|
+
}
|
|
23353
|
+
);
|
|
23303
23354
|
}
|
|
23304
23355
|
async function logBeatActivity(client, params) {
|
|
23305
23356
|
try {
|
|
@@ -24218,12 +24269,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS2 = 3;
|
|
|
24218
24269
|
async function pollForCheck2(client, taskId) {
|
|
24219
24270
|
const deadline = Date.now() + POLL_TIMEOUT_MS2;
|
|
24220
24271
|
let consecutiveErrors = 0;
|
|
24272
|
+
let consecutiveMissing = 0;
|
|
24221
24273
|
while (Date.now() < deadline) {
|
|
24222
24274
|
const [task, err] = await client.getTask(taskId).then(
|
|
24223
24275
|
(t) => [t, null],
|
|
24224
24276
|
(e) => [null, e]
|
|
24225
24277
|
);
|
|
24226
24278
|
if (err) {
|
|
24279
|
+
consecutiveMissing = 0;
|
|
24227
24280
|
consecutiveErrors++;
|
|
24228
24281
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS2) {
|
|
24229
24282
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -24231,8 +24284,16 @@ async function pollForCheck2(client, taskId) {
|
|
|
24231
24284
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24232
24285
|
continue;
|
|
24233
24286
|
}
|
|
24287
|
+
if (!task) {
|
|
24288
|
+
consecutiveErrors = 0;
|
|
24289
|
+
consecutiveMissing++;
|
|
24290
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS2)
|
|
24291
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
24292
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
|
|
24293
|
+
continue;
|
|
24294
|
+
}
|
|
24234
24295
|
consecutiveErrors = 0;
|
|
24235
|
-
|
|
24296
|
+
consecutiveMissing = 0;
|
|
24236
24297
|
if (task.status === "completed") {
|
|
24237
24298
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
24238
24299
|
return task.result;
|
|
@@ -25212,6 +25273,41 @@ var BeatDraftSuggestionSchema = external_exports.object({
|
|
|
25212
25273
|
});
|
|
25213
25274
|
var BeatDraftSuggestionsSchema = external_exports.array(BeatDraftSuggestionSchema);
|
|
25214
25275
|
|
|
25276
|
+
// ../../libs/harmonica-schemas/src/progress-evidence.ts
|
|
25277
|
+
var ProgressEvidenceQuerySchema = external_exports.object({
|
|
25278
|
+
start: external_exports.string().min(1).max(40),
|
|
25279
|
+
end: external_exports.string().min(1).max(40),
|
|
25280
|
+
timezone: external_exports.string().min(1).max(100),
|
|
25281
|
+
contributor: external_exports.string().trim().min(1).max(256).optional(),
|
|
25282
|
+
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
25283
|
+
cursor: external_exports.string().min(1).max(4096).optional()
|
|
25284
|
+
}).strict();
|
|
25285
|
+
var ProgressCitationSchema = external_exports.object({ kind: external_exports.string(), id: external_exports.string(), url: external_exports.string() });
|
|
25286
|
+
var ProgressEvidenceItemSchema = external_exports.object({
|
|
25287
|
+
id: external_exports.string(),
|
|
25288
|
+
source: external_exports.enum(["activity", "session"]),
|
|
25289
|
+
occurredAt: external_exports.string(),
|
|
25290
|
+
recordedAt: external_exports.string(),
|
|
25291
|
+
summary: external_exports.string(),
|
|
25292
|
+
classification: external_exports.enum(["planned", "progressed", "delivered"]),
|
|
25293
|
+
attribution: external_exports.object({ basis: external_exports.enum(["recorded_actor", "session_initiator", "unknown"]), actorId: external_exports.string().nullable(), agentId: external_exports.string().optional() }),
|
|
25294
|
+
citations: external_exports.array(ProgressCitationSchema),
|
|
25295
|
+
context: external_exports.array(external_exports.object({ kind: external_exports.string(), id: external_exports.string(), title: external_exports.string().optional(), asOf: external_exports.literal("current") })),
|
|
25296
|
+
deliveryProof: external_exports.object({ kind: external_exports.literal("recorded_release"), occurredAt: external_exports.string(), citation: ProgressCitationSchema }).nullable()
|
|
25297
|
+
});
|
|
25298
|
+
var ProgressCoverageGapSchema = external_exports.object({ source: external_exports.string(), code: external_exports.string(), message: external_exports.string(), entityId: external_exports.string().optional() });
|
|
25299
|
+
var ProgressEvidenceResponseSchema = external_exports.object({
|
|
25300
|
+
systemId: external_exports.string(),
|
|
25301
|
+
accountId: external_exports.string().nullable(),
|
|
25302
|
+
window: external_exports.object({ start: external_exports.string(), end: external_exports.string(), timezone: external_exports.string() }),
|
|
25303
|
+
contributor: external_exports.string().nullable(),
|
|
25304
|
+
watermark: external_exports.string(),
|
|
25305
|
+
items: external_exports.array(ProgressEvidenceItemSchema),
|
|
25306
|
+
nextCursor: external_exports.string().optional(),
|
|
25307
|
+
coverage: external_exports.object({ status: external_exports.literal("partial"), gaps: external_exports.array(ProgressCoverageGapSchema) }),
|
|
25308
|
+
metrics: external_exports.object({ queryCalls: external_exports.number(), batchCalls: external_exports.number(), scannedCount: external_exports.number(), referenceCount: external_exports.number(), elapsedMs: external_exports.number() })
|
|
25309
|
+
});
|
|
25310
|
+
|
|
25215
25311
|
// ../../libs/harmonica-schemas/src/api-responses.ts
|
|
25216
25312
|
var HumanAssigneeSchema = external_exports.object({ email: external_exports.string(), name: external_exports.string() });
|
|
25217
25313
|
var AgentAssigneeSchema = external_exports.object({ agentId: external_exports.string(), name: external_exports.string() });
|
|
@@ -25300,6 +25396,14 @@ var RevisionScmSchema = external_exports.object({
|
|
|
25300
25396
|
state: external_exports.enum(["draft", "open", "merged", "closed"]),
|
|
25301
25397
|
mergeSha: external_exports.string().optional()
|
|
25302
25398
|
});
|
|
25399
|
+
var RevisionPreviewSchema = external_exports.object({
|
|
25400
|
+
url: external_exports.string(),
|
|
25401
|
+
commitSha: external_exports.string(),
|
|
25402
|
+
recordedAt: external_exports.string()
|
|
25403
|
+
});
|
|
25404
|
+
var RevisionPreviewApiSchema = RevisionPreviewSchema.extend({
|
|
25405
|
+
isCurrent: external_exports.boolean()
|
|
25406
|
+
});
|
|
25303
25407
|
var RevisionApiSchema = external_exports.object({
|
|
25304
25408
|
revisionId: external_exports.string(),
|
|
25305
25409
|
beatId: external_exports.string(),
|
|
@@ -25913,7 +26017,11 @@ var HumanAssigneeApiSchema = external_exports.object({
|
|
|
25913
26017
|
name: external_exports.string(),
|
|
25914
26018
|
email: external_exports.string()
|
|
25915
26019
|
});
|
|
26020
|
+
var ArchiveWorkItemRequestSchema = external_exports.object({ reason: external_exports.string().trim().min(1).max(1e3) });
|
|
25916
26021
|
var WorkItemApiSchema = external_exports.object({
|
|
26022
|
+
archived: external_exports.boolean().optional(),
|
|
26023
|
+
archivedAt: external_exports.string().optional(),
|
|
26024
|
+
archivedReason: external_exports.string().optional(),
|
|
25917
26025
|
workItemId: external_exports.string(),
|
|
25918
26026
|
trackId: external_exports.string(),
|
|
25919
26027
|
teamspaceId: external_exports.string(),
|
|
@@ -25947,6 +26055,12 @@ var WorkItemApiSchema = external_exports.object({
|
|
|
25947
26055
|
* not, so a move on either side can separate the two.
|
|
25948
26056
|
*/
|
|
25949
26057
|
barId: external_exports.string().min(1).optional(),
|
|
26058
|
+
/**
|
|
26059
|
+
* Optional per-Track fractional ordering rank (N-4E09-7665). Absent when
|
|
26060
|
+
* the item is unranked — an unranked item is a first-class state. The
|
|
26061
|
+
* Work Queue uses this to order items within their displayed group.
|
|
26062
|
+
*/
|
|
26063
|
+
rank: external_exports.number().optional(),
|
|
25950
26064
|
createdBy: external_exports.string().optional(),
|
|
25951
26065
|
version: external_exports.number().optional(),
|
|
25952
26066
|
createdAt: external_exports.string(),
|
|
@@ -27017,19 +27131,19 @@ function registerMovementTools(server, _ctx, client) {
|
|
|
27017
27131
|
);
|
|
27018
27132
|
server.tool(
|
|
27019
27133
|
"create_movement",
|
|
27020
|
-
|
|
27134
|
+
`Create a Movement for an Account, in one of that Account's Teamspaces. A Movement is a client-SOW container for an engagement \u2014 it is NOT a Beat. Recognition cues: "new SOW", "create a contract scope", "start an engagement container" \u2192 create_movement.`,
|
|
27021
27135
|
{
|
|
27022
27136
|
teamspaceId: external_exports.string().describe("The teamspace ID"),
|
|
27023
27137
|
title: external_exports.string().min(1).max(300).describe('Movement title (e.g. "Q3 Platform Modernisation SOW")'),
|
|
27024
27138
|
description: external_exports.string().max(2e3).optional().describe("Optional description"),
|
|
27025
|
-
accountId: external_exports.string().min(1).max(128).
|
|
27139
|
+
accountId: external_exports.string().min(1).max(128).describe("The Account this Movement belongs to. Required (rev-950e828e). Must be in the same org as the Teamspace and linked to it.")
|
|
27026
27140
|
},
|
|
27027
27141
|
async ({ teamspaceId, title, description, accountId }) => {
|
|
27028
27142
|
const m = await client.createMovement({
|
|
27029
27143
|
teamspaceId,
|
|
27030
27144
|
title,
|
|
27031
27145
|
...description !== void 0 && { description },
|
|
27032
|
-
|
|
27146
|
+
accountId
|
|
27033
27147
|
});
|
|
27034
27148
|
return {
|
|
27035
27149
|
content: [
|
|
@@ -27048,7 +27162,7 @@ function registerMovementTools(server, _ctx, client) {
|
|
|
27048
27162
|
movementId: external_exports.string().describe("The movement ID"),
|
|
27049
27163
|
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
27050
27164
|
description: external_exports.string().max(2e3).optional().describe("New description"),
|
|
27051
|
-
accountId: external_exports.string().
|
|
27165
|
+
accountId: external_exports.string().min(1).max(128).optional().describe("Reassign this Movement to a different Account. Must be in the same org and linked to the teamspace. Omit to leave unchanged \u2014 a Movement cannot be left without an Account (rev-950e828e).")
|
|
27052
27166
|
},
|
|
27053
27167
|
async ({ movementId, title, description, accountId }) => {
|
|
27054
27168
|
const hasUpdates = title !== void 0 || description !== void 0 || accountId !== void 0;
|
|
@@ -27058,12 +27172,11 @@ function registerMovementTools(server, _ctx, client) {
|
|
|
27058
27172
|
isError: true
|
|
27059
27173
|
};
|
|
27060
27174
|
}
|
|
27061
|
-
const resolvedAccountId = accountId === "" ? null : accountId;
|
|
27062
27175
|
try {
|
|
27063
27176
|
const m = await client.updateMovement(movementId, {
|
|
27064
27177
|
...title !== void 0 && { title },
|
|
27065
27178
|
...description !== void 0 && { description },
|
|
27066
|
-
...accountId !== void 0 && { accountId
|
|
27179
|
+
...accountId !== void 0 && { accountId }
|
|
27067
27180
|
});
|
|
27068
27181
|
if (!m) {
|
|
27069
27182
|
return {
|
|
@@ -27071,7 +27184,7 @@ function registerMovementTools(server, _ctx, client) {
|
|
|
27071
27184
|
isError: true
|
|
27072
27185
|
};
|
|
27073
27186
|
}
|
|
27074
|
-
const account =
|
|
27187
|
+
const account = ` \u2014 Account: ${accountId ?? m.accountId}`;
|
|
27075
27188
|
return {
|
|
27076
27189
|
content: [
|
|
27077
27190
|
{
|
|
@@ -28309,7 +28422,7 @@ ${formatTree(tree.root)}` }] };
|
|
|
28309
28422
|
);
|
|
28310
28423
|
server.tool(
|
|
28311
28424
|
"update_notebook_collection",
|
|
28312
|
-
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type,
|
|
28425
|
+
"Reconfigure a placed live-collection section in place \u2014 change what it collects (note type, system scope, limit, order, significance tier) or its heading, without removing and re-adding it. Only the fields you pass change; the rest keep their current values. Identify the section by its elementId (from get_notebook_tree) and the page it sits on.",
|
|
28313
28426
|
{
|
|
28314
28427
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28315
28428
|
elementId: external_exports.string().describe("The collection section's elementId (from get_notebook_tree)"),
|
|
@@ -28450,9 +28563,9 @@ ${scopeText}` }] };
|
|
|
28450
28563
|
);
|
|
28451
28564
|
server.tool(
|
|
28452
28565
|
"list_notebooks",
|
|
28453
|
-
"List Notebooks reachable from a scope (org, teamspace, or
|
|
28566
|
+
"List Notebooks reachable from a scope (org, teamspace, system, or account). Returns all notebooks bound to that scope.",
|
|
28454
28567
|
{
|
|
28455
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, or
|
|
28568
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28456
28569
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity"),
|
|
28457
28570
|
includeArchived: external_exports.boolean().optional().describe("Include archived Notebooks in the results. Default: false (archived excluded).")
|
|
28458
28571
|
},
|
|
@@ -28513,10 +28626,10 @@ ${formatNotebook(updated)}` }] };
|
|
|
28513
28626
|
);
|
|
28514
28627
|
server.tool(
|
|
28515
28628
|
"add_notebook_shortcut",
|
|
28516
|
-
"Add a curated, read-only shortcut from a scope (org, teamspace,
|
|
28629
|
+
"Add a curated, read-only shortcut from a scope (org, teamspace, system, or account) to a Notebook owned elsewhere in the SAME organization. Unlike set_notebook_scopes, a shortcut grants no rename/archive/delete rights over the Notebook in the consuming scope \u2014 it is a link, not ownership.",
|
|
28517
28630
|
{
|
|
28518
28631
|
notebookId: external_exports.string().describe("The Notebook ID to shortcut"),
|
|
28519
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28632
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28520
28633
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity"),
|
|
28521
28634
|
createdBy: external_exports.string().optional().describe("Actor ID \u2014 defaults to the session user")
|
|
28522
28635
|
},
|
|
@@ -28545,7 +28658,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28545
28658
|
"Remove a Notebook shortcut from a scope. Only removes the shortcut reference \u2014 never affects the source Notebook or its real scope bindings.",
|
|
28546
28659
|
{
|
|
28547
28660
|
notebookId: external_exports.string().describe("The Notebook ID"),
|
|
28548
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace,
|
|
28661
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Consuming scope type \u2014 org, teamspace, system, or account"),
|
|
28549
28662
|
scopeId: external_exports.string().min(1).describe("ID of the consuming scope entity")
|
|
28550
28663
|
},
|
|
28551
28664
|
async ({ notebookId, scopeType, scopeId }) => {
|
|
@@ -28568,7 +28681,7 @@ ${formatNotebook(updated)}` }] };
|
|
|
28568
28681
|
"list_notebook_shortcuts",
|
|
28569
28682
|
"List a scope\u2019s curated Notebook shortcuts \u2014 Notebooks owned elsewhere in the org that this scope has chosen to surface, hydrated with each source Notebook\u2019s live title/status.",
|
|
28570
28683
|
{
|
|
28571
|
-
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace,
|
|
28684
|
+
scopeType: external_exports.enum(SCOPE_TYPE_VALUES).describe("Scope type \u2014 org, teamspace, system, or account"),
|
|
28572
28685
|
scopeId: external_exports.string().min(1).describe("ID of the scope entity")
|
|
28573
28686
|
},
|
|
28574
28687
|
async ({ scopeType, scopeId }) => {
|
|
@@ -28923,12 +29036,14 @@ var ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS = 3;
|
|
|
28923
29036
|
async function pollForRootCodaCheck(client, taskId) {
|
|
28924
29037
|
const deadline = Date.now() + ROOT_CODA_POLL_TIMEOUT_MS;
|
|
28925
29038
|
let consecutiveErrors = 0;
|
|
29039
|
+
let consecutiveMissing = 0;
|
|
28926
29040
|
while (Date.now() < deadline) {
|
|
28927
29041
|
const [task, err] = await client.getTask(taskId).then(
|
|
28928
29042
|
(t) => [t, null],
|
|
28929
29043
|
(e) => [null, e]
|
|
28930
29044
|
);
|
|
28931
29045
|
if (err) {
|
|
29046
|
+
consecutiveMissing = 0;
|
|
28932
29047
|
consecutiveErrors++;
|
|
28933
29048
|
if (consecutiveErrors >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS) {
|
|
28934
29049
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -28936,8 +29051,16 @@ async function pollForRootCodaCheck(client, taskId) {
|
|
|
28936
29051
|
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
28937
29052
|
continue;
|
|
28938
29053
|
}
|
|
29054
|
+
if (!task) {
|
|
29055
|
+
consecutiveErrors = 0;
|
|
29056
|
+
consecutiveMissing++;
|
|
29057
|
+
if (consecutiveMissing >= ROOT_CODA_POLL_MAX_CONSECUTIVE_ERRORS)
|
|
29058
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29059
|
+
await new Promise((r) => setTimeout(r, ROOT_CODA_POLL_INTERVAL_MS));
|
|
29060
|
+
continue;
|
|
29061
|
+
}
|
|
28939
29062
|
consecutiveErrors = 0;
|
|
28940
|
-
|
|
29063
|
+
consecutiveMissing = 0;
|
|
28941
29064
|
if (task.status === "completed") {
|
|
28942
29065
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
28943
29066
|
const result = task.result;
|
|
@@ -29280,12 +29403,14 @@ var POLL_MAX_CONSECUTIVE_ERRORS4 = 3;
|
|
|
29280
29403
|
async function pollForCheck4(client, taskId) {
|
|
29281
29404
|
const deadline = Date.now() + POLL_TIMEOUT_MS4;
|
|
29282
29405
|
let consecutiveErrors = 0;
|
|
29406
|
+
let consecutiveMissing = 0;
|
|
29283
29407
|
while (Date.now() < deadline) {
|
|
29284
29408
|
const [task, err] = await client.getTask(taskId).then(
|
|
29285
29409
|
(t) => [t, null],
|
|
29286
29410
|
(e) => [null, e]
|
|
29287
29411
|
);
|
|
29288
29412
|
if (err) {
|
|
29413
|
+
consecutiveMissing = 0;
|
|
29289
29414
|
consecutiveErrors++;
|
|
29290
29415
|
if (consecutiveErrors >= POLL_MAX_CONSECUTIVE_ERRORS4) {
|
|
29291
29416
|
throw new Error(`getTask failed ${consecutiveErrors} times: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -29293,8 +29418,16 @@ async function pollForCheck4(client, taskId) {
|
|
|
29293
29418
|
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29294
29419
|
continue;
|
|
29295
29420
|
}
|
|
29421
|
+
if (!task) {
|
|
29422
|
+
consecutiveErrors = 0;
|
|
29423
|
+
consecutiveMissing++;
|
|
29424
|
+
if (consecutiveMissing >= POLL_MAX_CONSECUTIVE_ERRORS4)
|
|
29425
|
+
throw new Error(`Task ${taskId} not found after ${consecutiveMissing} consecutive attempts`);
|
|
29426
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS4));
|
|
29427
|
+
continue;
|
|
29428
|
+
}
|
|
29296
29429
|
consecutiveErrors = 0;
|
|
29297
|
-
|
|
29430
|
+
consecutiveMissing = 0;
|
|
29298
29431
|
if (task.status === "completed") {
|
|
29299
29432
|
if (!task.result) throw new CheckResultPendingError(taskId);
|
|
29300
29433
|
return task.result;
|
|
@@ -29521,6 +29654,24 @@ function registerPortfolioCoherenceTools(server, ctx, client) {
|
|
|
29521
29654
|
);
|
|
29522
29655
|
}
|
|
29523
29656
|
|
|
29657
|
+
// ../../libs/harmonica-services/src/mcp/tools/progress-evidence-tools.ts
|
|
29658
|
+
function registerProgressEvidenceTools(server, _ctx, client) {
|
|
29659
|
+
server.tool(
|
|
29660
|
+
"get_progress_evidence",
|
|
29661
|
+
"Read cited progress evidence for a system and [start,end) reporting window. Calendar dates use the required IANA timezone; timestamps need an offset. Optional contributor matches recorded actors or explicitly labeled session initiators, never assignees. Returns planned/progressed/delivered evidence, source gaps, a watermark and continuation. Follow nextCursor even on empty pages. No writes.",
|
|
29662
|
+
{ systemId: external_exports.string().min(1).max(128).regex(/^[^#]+$/), ...ProgressEvidenceQuerySchema.shape },
|
|
29663
|
+
async ({ systemId, ...query }) => {
|
|
29664
|
+
try {
|
|
29665
|
+
const report = await client.getProgressEvidence(systemId, query);
|
|
29666
|
+
if (!report) return { isError: true, content: [{ type: "text", text: "System not found." }] };
|
|
29667
|
+
return { content: [{ type: "text", text: JSON.stringify(report, null, 2) }] };
|
|
29668
|
+
} catch (error2) {
|
|
29669
|
+
return { isError: true, content: [{ type: "text", text: `Progress evidence query failed: ${error2 instanceof Error ? error2.message : String(error2)}` }] };
|
|
29670
|
+
}
|
|
29671
|
+
}
|
|
29672
|
+
);
|
|
29673
|
+
}
|
|
29674
|
+
|
|
29524
29675
|
// ../../libs/harmonica-services/src/mcp/tools/pulse-report-tools.ts
|
|
29525
29676
|
function formatBeatRow(r) {
|
|
29526
29677
|
const downbeatState = r.hasDownbeat ? "" : " \u2014 no Downbeat ever set";
|
|
@@ -31235,6 +31386,30 @@ function registerTeamspaceTools(server, ctx, client) {
|
|
|
31235
31386
|
);
|
|
31236
31387
|
}
|
|
31237
31388
|
|
|
31389
|
+
// ../../libs/harmonica-services/src/tool-schemas/track.ts
|
|
31390
|
+
var get_track = {
|
|
31391
|
+
trackId: external_exports.string().describe("The Track ID")
|
|
31392
|
+
};
|
|
31393
|
+
var update_track = {
|
|
31394
|
+
trackId: external_exports.string().describe("The Track ID"),
|
|
31395
|
+
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
31396
|
+
description: external_exports.string().max(2e3).optional().describe("New description"),
|
|
31397
|
+
deliverables: external_exports.string().max(2e4).optional().describe(`Replace the Track's deliverables Markdown ("" clears)`),
|
|
31398
|
+
acceptanceCriteria: external_exports.string().max(2e4).optional().describe(`Replace the Track's acceptance-criteria Markdown ("" clears)`),
|
|
31399
|
+
vuRate: external_exports.number().nonnegative().optional().describe("New VU rate")
|
|
31400
|
+
};
|
|
31401
|
+
var transition_track = {
|
|
31402
|
+
trackId: external_exports.string().describe("The Track ID"),
|
|
31403
|
+
targetState: external_exports.enum(["proposed", "approved", "active", "fulfilled", "cancelled"]).describe("Target lifecycle state"),
|
|
31404
|
+
approvalNoteId: external_exports.string().optional().describe(
|
|
31405
|
+
'Required when transitioning to "approved" \u2014 the Note ID capturing the approval decision'
|
|
31406
|
+
),
|
|
31407
|
+
reason: external_exports.string().optional().describe("Reason for the transition (especially useful for cancellation)"),
|
|
31408
|
+
confirm: external_exports.boolean().optional().default(false).describe(
|
|
31409
|
+
"false (default) = dry-run preview; true = execute the transition"
|
|
31410
|
+
)
|
|
31411
|
+
};
|
|
31412
|
+
|
|
31238
31413
|
// ../../libs/harmonica-services/src/mcp/tools/track-tools.ts
|
|
31239
31414
|
function registerTrackTools(server, ctx, client) {
|
|
31240
31415
|
server.tool(
|
|
@@ -31255,9 +31430,7 @@ function registerTrackTools(server, ctx, client) {
|
|
|
31255
31430
|
server.tool(
|
|
31256
31431
|
"get_track",
|
|
31257
31432
|
"Get a Track by ID. Returns full details including commercial lifecycle status, VU rate, and audit stamps.",
|
|
31258
|
-
|
|
31259
|
-
trackId: external_exports.string().describe("The Track ID")
|
|
31260
|
-
},
|
|
31433
|
+
get_track,
|
|
31261
31434
|
async ({ trackId }) => {
|
|
31262
31435
|
const t = await client.getTrack(trackId);
|
|
31263
31436
|
if (!t) {
|
|
@@ -31331,14 +31504,7 @@ ${t.acceptanceCriteria}` : null,
|
|
|
31331
31504
|
server.tool(
|
|
31332
31505
|
"update_track",
|
|
31333
31506
|
"Update mutable content fields on a Track (title, description, deliverables, acceptanceCriteria, vuRate). To change commercial status use transition_track instead.",
|
|
31334
|
-
|
|
31335
|
-
trackId: external_exports.string().describe("The Track ID"),
|
|
31336
|
-
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
31337
|
-
description: external_exports.string().max(2e3).optional().describe("New description"),
|
|
31338
|
-
deliverables: external_exports.string().max(2e4).optional().describe(`Replace the Track's deliverables Markdown ("" clears)`),
|
|
31339
|
-
acceptanceCriteria: external_exports.string().max(2e4).optional().describe(`Replace the Track's acceptance-criteria Markdown ("" clears)`),
|
|
31340
|
-
vuRate: external_exports.number().nonnegative().optional().describe("New VU rate")
|
|
31341
|
-
},
|
|
31507
|
+
update_track,
|
|
31342
31508
|
async ({ trackId, title, description, deliverables, acceptanceCriteria, vuRate }) => {
|
|
31343
31509
|
const t = await client.updateTrack(trackId, {
|
|
31344
31510
|
...title !== void 0 && { title },
|
|
@@ -31365,13 +31531,7 @@ ${t.acceptanceCriteria}` : null,
|
|
|
31365
31531
|
server.tool(
|
|
31366
31532
|
"transition_track",
|
|
31367
31533
|
'Transition a Track through its commercial lifecycle: draft \u2192 proposed \u2192 approved \u2192 active \u2192 fulfilled. Cancelled is reachable from any non-terminal state. Advancing to "approved" requires an approvalNoteId. Use confirm=false (default) to preview the transition without writing; confirm=true to execute.',
|
|
31368
|
-
|
|
31369
|
-
trackId: external_exports.string().describe("The Track ID"),
|
|
31370
|
-
targetState: external_exports.enum(["proposed", "approved", "active", "fulfilled", "cancelled"]).describe("Target lifecycle state"),
|
|
31371
|
-
approvalNoteId: external_exports.string().optional().describe('Required when transitioning to "approved" \u2014 the Note ID capturing the approval decision'),
|
|
31372
|
-
reason: external_exports.string().optional().describe("Reason for the transition (especially useful for cancellation)"),
|
|
31373
|
-
confirm: external_exports.boolean().optional().default(false).describe("false (default) = dry-run preview; true = execute the transition")
|
|
31374
|
-
},
|
|
31534
|
+
transition_track,
|
|
31375
31535
|
async ({ trackId, targetState, approvalNoteId, reason, confirm }) => {
|
|
31376
31536
|
if (!confirm) {
|
|
31377
31537
|
return {
|
|
@@ -32002,6 +32162,24 @@ function registerWorkItemTools(server, _ctx, client) {
|
|
|
32002
32162
|
);
|
|
32003
32163
|
}
|
|
32004
32164
|
|
|
32165
|
+
// ../../libs/harmonica-services/src/tool-schemas/work-order.ts
|
|
32166
|
+
var WORK_ORDER_SIZE_ENUM = ["XS", "S", "M", "L", "XL"];
|
|
32167
|
+
var create_work_order = {
|
|
32168
|
+
workRequestId: external_exports.string().min(1).describe("The Work Request this order was split from \u2014 immutable lineage"),
|
|
32169
|
+
title: external_exports.string().min(1).max(300).describe("Work order title"),
|
|
32170
|
+
description: external_exports.string().max(1e4).optional().describe("Optional detail beyond the title"),
|
|
32171
|
+
size: external_exports.enum(WORK_ORDER_SIZE_ENUM).optional().describe(
|
|
32172
|
+
"Optional t-shirt size. An order can arrive before it is sized; a request's composition counts unsized orders rather than hiding them."
|
|
32173
|
+
),
|
|
32174
|
+
createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
|
|
32175
|
+
};
|
|
32176
|
+
var get_work_order = {
|
|
32177
|
+
workOrderId: external_exports.string().describe("The Work Order ID (e.g., wo-abc123)")
|
|
32178
|
+
};
|
|
32179
|
+
var list_movement_work_orders = {
|
|
32180
|
+
movementId: external_exports.string().describe("The Movement (engagement) ID")
|
|
32181
|
+
};
|
|
32182
|
+
|
|
32005
32183
|
// ../../libs/harmonica-services/src/mcp/tools/work-order-tools.ts
|
|
32006
32184
|
var WORK_INTAKE_STATUS_ENUM = [
|
|
32007
32185
|
"requested",
|
|
@@ -32014,7 +32192,7 @@ var NEEDS_DECISION_REASON_ENUM = [
|
|
|
32014
32192
|
"exceeds_capacity",
|
|
32015
32193
|
"incoherent_with_decision"
|
|
32016
32194
|
];
|
|
32017
|
-
var
|
|
32195
|
+
var WORK_ORDER_SIZE_ENUM2 = [
|
|
32018
32196
|
"XS",
|
|
32019
32197
|
"S",
|
|
32020
32198
|
"M",
|
|
@@ -32026,8 +32204,7 @@ function oneLine(value) {
|
|
|
32026
32204
|
}
|
|
32027
32205
|
function formatOrderLine(o) {
|
|
32028
32206
|
const reason = o.needsDecisionReason ? ` [${o.needsDecisionReason}]` : "";
|
|
32029
|
-
|
|
32030
|
-
return `[${o.workOrderId}] ${oneLine(o.title)} \u2014 ${o.status}${reason}, ${o.size ?? "unsized"}, ${track}`;
|
|
32207
|
+
return `[${o.workOrderId}] ${oneLine(o.title)} \u2014 ${o.status}${reason}, ${o.size ?? "unsized"}`;
|
|
32031
32208
|
}
|
|
32032
32209
|
function registerWorkOrderTools(server, _ctx, client) {
|
|
32033
32210
|
server.tool(
|
|
@@ -32057,7 +32234,7 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32057
32234
|
);
|
|
32058
32235
|
server.tool(
|
|
32059
32236
|
"list_work_request_work_orders",
|
|
32060
|
-
"List the Work Orders one Work Request split into \u2014 the lineage view. A four-way split shows four orders here, each
|
|
32237
|
+
"List the Work Orders one Work Request split into \u2014 the lineage view. A four-way split shows four orders here, each with its own Items and independent state.",
|
|
32061
32238
|
{
|
|
32062
32239
|
workRequestId: external_exports.string().describe("The Work Request ID")
|
|
32063
32240
|
},
|
|
@@ -32069,10 +32246,8 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32069
32246
|
);
|
|
32070
32247
|
server.tool(
|
|
32071
32248
|
"get_work_order",
|
|
32072
|
-
"Get a Work Order by ID. Returns its title, status (with the routing reason when it is awaiting a decision), t-shirt size,
|
|
32073
|
-
|
|
32074
|
-
workOrderId: external_exports.string().describe("The Work Order ID (e.g., wo-abc123)")
|
|
32075
|
-
},
|
|
32249
|
+
"Get a Work Order by ID. Returns its title, status (with the routing reason when it is awaiting a decision), t-shirt size, and the Work Request it was split from.",
|
|
32250
|
+
get_work_order,
|
|
32076
32251
|
async ({ workOrderId }) => {
|
|
32077
32252
|
const wo = await client.getWorkOrder(workOrderId);
|
|
32078
32253
|
if (!wo) {
|
|
@@ -32082,7 +32257,6 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32082
32257
|
`ID: ${wo.workOrderId}`,
|
|
32083
32258
|
`Title: ${oneLine(wo.title)}`,
|
|
32084
32259
|
`From Work Request: ${wo.workRequestId}`,
|
|
32085
|
-
`Track: ${wo.trackId}`,
|
|
32086
32260
|
`Status: ${wo.status}`,
|
|
32087
32261
|
wo.needsDecisionReason ? `Routing Reason: ${wo.needsDecisionReason}` : null,
|
|
32088
32262
|
wo.size !== void 0 ? `Size: ${wo.size}` : "Size: unsized",
|
|
@@ -32094,41 +32268,37 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32094
32268
|
return { content: [{ type: "text", text: lines }] };
|
|
32095
32269
|
}
|
|
32096
32270
|
);
|
|
32097
|
-
|
|
32098
|
-
|
|
32099
|
-
|
|
32100
|
-
|
|
32101
|
-
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32109
|
-
|
|
32110
|
-
|
|
32111
|
-
|
|
32112
|
-
|
|
32113
|
-
|
|
32114
|
-
|
|
32115
|
-
|
|
32116
|
-
|
|
32117
|
-
|
|
32118
|
-
|
|
32119
|
-
|
|
32120
|
-
|
|
32121
|
-
|
|
32122
|
-
|
|
32123
|
-
|
|
32124
|
-
|
|
32125
|
-
|
|
32126
|
-
|
|
32127
|
-
|
|
32128
|
-
const text = orders.length === 0 ? "No work orders found." : orders.map(formatOrderLine).join("\n");
|
|
32129
|
-
return { content: [{ type: "text", text }] };
|
|
32130
|
-
}
|
|
32131
|
-
);
|
|
32271
|
+
for (const name of ["create_work_order", "promote_work_request"]) {
|
|
32272
|
+
server.tool(
|
|
32273
|
+
name,
|
|
32274
|
+
`Create (promote) a Work Order from a Work Request. Each call creates another Order; it does not accept the Order or change Request status. Called once per piece: a request covering two separate lines of work becomes two orders. Starts in "requested" status \u2014 moving it past the coherence gate is transition_work_order_status. The Order's scope is derived from the Work Request's Movement; Tracks are assigned only to Work Items.`,
|
|
32275
|
+
create_work_order,
|
|
32276
|
+
async ({ workRequestId, title, description, size, createdBy }) => {
|
|
32277
|
+
const wo = await client.createWorkOrder({
|
|
32278
|
+
workRequestId,
|
|
32279
|
+
title,
|
|
32280
|
+
...description !== void 0 && { description },
|
|
32281
|
+
...size !== void 0 && { size },
|
|
32282
|
+
...createdBy !== void 0 && { createdBy }
|
|
32283
|
+
});
|
|
32284
|
+
return {
|
|
32285
|
+
content: [{ type: "text", text: `Created work order: ${formatOrderLine(wo)}` }]
|
|
32286
|
+
};
|
|
32287
|
+
}
|
|
32288
|
+
);
|
|
32289
|
+
}
|
|
32290
|
+
for (const name of ["list_movement_work_orders", "list_work_orders"]) {
|
|
32291
|
+
server.tool(
|
|
32292
|
+
name,
|
|
32293
|
+
'List the Work Orders belonging to a Movement (engagement). A Work Order is the internal unit of agreed work \u2014 it is split from a Work Request and scoped to the Movement that owns the Request. Recognition cues: "what is committed on this engagement", "list the orders" \u2192 list_movement_work_orders.',
|
|
32294
|
+
list_movement_work_orders,
|
|
32295
|
+
async ({ movementId }) => {
|
|
32296
|
+
const orders = await client.listMovementWorkOrders(movementId);
|
|
32297
|
+
const text = orders.length === 0 ? "No work orders found." : orders.map(formatOrderLine).join("\n");
|
|
32298
|
+
return { content: [{ type: "text", text }] };
|
|
32299
|
+
}
|
|
32300
|
+
);
|
|
32301
|
+
}
|
|
32132
32302
|
server.tool(
|
|
32133
32303
|
"update_work_order",
|
|
32134
32304
|
"Update content fields on a Work Order (title, description, size). To change its status use transition_work_order_status. There is no moving an Order between Tracks \u2014 an Order belongs to its Work Request, and a Track is attribution carried by the Work Items (set workOrderId on them via update_work_item). The Work Request it came from cannot be changed \u2014 that lineage is immutable.",
|
|
@@ -32136,7 +32306,7 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32136
32306
|
workOrderId: external_exports.string().describe("The Work Order ID"),
|
|
32137
32307
|
title: external_exports.string().min(1).max(300).optional().describe("New title"),
|
|
32138
32308
|
description: external_exports.string().max(1e4).nullable().optional().describe("New description. Pass null to clear; omit to leave untouched."),
|
|
32139
|
-
size: external_exports.enum(
|
|
32309
|
+
size: external_exports.enum(WORK_ORDER_SIZE_ENUM2).nullable().optional().describe("New t-shirt size. Pass null to return it to unsized; omit to leave untouched.")
|
|
32140
32310
|
},
|
|
32141
32311
|
async ({ workOrderId, title, description, size }) => {
|
|
32142
32312
|
if (title === void 0 && description === void 0 && size === void 0) {
|
|
@@ -32225,11 +32395,27 @@ function registerWorkOrderTools(server, _ctx, client) {
|
|
|
32225
32395
|
);
|
|
32226
32396
|
}
|
|
32227
32397
|
|
|
32398
|
+
// ../../libs/harmonica-services/src/tool-schemas/work-request.ts
|
|
32399
|
+
var WORK_REQUEST_ORIGIN_ENUM = ["client", "internal"];
|
|
32400
|
+
var create_work_request = {
|
|
32401
|
+
movementId: external_exports.string().describe("The Movement (engagement) this request is filed against"),
|
|
32402
|
+
title: external_exports.string().min(1).max(300).describe("Short title for the ask"),
|
|
32403
|
+
body: external_exports.string().max(1e4).optional().describe("The ask as written, in the requester's own words"),
|
|
32404
|
+
requesterName: external_exports.string().min(1).describe("Who is asking \u2014 display name"),
|
|
32405
|
+
requesterEmail: external_exports.string().email().describe("Who is asking \u2014 email"),
|
|
32406
|
+
origin: external_exports.enum(WORK_REQUEST_ORIGIN_ENUM).describe(
|
|
32407
|
+
'Where the ask came from: "client" or "internal". One entity covers both \u2014 only the entry point differs.'
|
|
32408
|
+
),
|
|
32409
|
+
createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
|
|
32410
|
+
};
|
|
32411
|
+
var get_work_request = {
|
|
32412
|
+
workRequestId: external_exports.string().describe("The Work Request ID (e.g., wr-abc123)")
|
|
32413
|
+
};
|
|
32414
|
+
var list_movement_work_requests = {
|
|
32415
|
+
movementId: external_exports.string().describe("The Movement (engagement) ID")
|
|
32416
|
+
};
|
|
32417
|
+
|
|
32228
32418
|
// ../../libs/harmonica-services/src/mcp/tools/work-request-tools.ts
|
|
32229
|
-
var WORK_REQUEST_ORIGIN_ENUM = [
|
|
32230
|
-
"client",
|
|
32231
|
-
"internal"
|
|
32232
|
-
];
|
|
32233
32419
|
function oneLine2(value) {
|
|
32234
32420
|
return value.replace(/[\r\n]+/g, " ");
|
|
32235
32421
|
}
|
|
@@ -32243,9 +32429,7 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32243
32429
|
server.tool(
|
|
32244
32430
|
"list_movement_work_requests",
|
|
32245
32431
|
`List the Work Requests filed against a Movement. A Work Request is the customer's ask in their own language, before it is sized or agreed. Recognition cues: "what has the client asked for", "list the requests on this engagement" \u2192 list_movement_work_requests.`,
|
|
32246
|
-
|
|
32247
|
-
movementId: external_exports.string().describe("The Movement (engagement) ID")
|
|
32248
|
-
},
|
|
32432
|
+
list_movement_work_requests,
|
|
32249
32433
|
async ({ movementId }) => {
|
|
32250
32434
|
const requests = await client.listMovementWorkRequests(movementId);
|
|
32251
32435
|
const text = requests.length === 0 ? "No work requests found." : requests.map((wr) => `[${wr.workRequestId}] ${oneLine2(wr.title)} \u2014 from ${oneLine2(wr.requester.name)} (${wr.origin})`).join("\n");
|
|
@@ -32255,9 +32439,7 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32255
32439
|
server.tool(
|
|
32256
32440
|
"get_work_request",
|
|
32257
32441
|
"Get a Work Request with the Work Orders it split into and their combined size composition. Note there is no request-level status: each Order carries its own, because a split whose halves sit in different states cannot be reduced to one word.",
|
|
32258
|
-
|
|
32259
|
-
workRequestId: external_exports.string().describe("The Work Request ID (e.g., wr-abc123)")
|
|
32260
|
-
},
|
|
32442
|
+
get_work_request,
|
|
32261
32443
|
async ({ workRequestId }) => {
|
|
32262
32444
|
const detail = await client.getWorkRequestDetail(workRequestId);
|
|
32263
32445
|
if (!detail) {
|
|
@@ -32267,10 +32449,9 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32267
32449
|
const orderLines = orders.length === 0 ? ["Orders: none yet \u2014 this request has not been split"] : [
|
|
32268
32450
|
"Orders:",
|
|
32269
32451
|
...orders.map((o) => {
|
|
32270
|
-
const track = o.trackTitle ? `${oneLine2(o.trackTitle)} (${o.trackId})` : o.trackId;
|
|
32271
32452
|
const reason = o.needsDecisionReason ? ` [${o.needsDecisionReason}]` : "";
|
|
32272
32453
|
const sized = o.size ?? "unsized";
|
|
32273
|
-
return ` [${o.workOrderId}] ${oneLine2(o.title)} \u2014 ${o.status}${reason}, ${sized}
|
|
32454
|
+
return ` [${o.workOrderId}] ${oneLine2(o.title)} \u2014 ${o.status}${reason}, ${sized}`;
|
|
32274
32455
|
})
|
|
32275
32456
|
];
|
|
32276
32457
|
const lines = [
|
|
@@ -32292,15 +32473,7 @@ function registerWorkRequestTools(server, _ctx, client) {
|
|
|
32292
32473
|
server.tool(
|
|
32293
32474
|
"create_work_request",
|
|
32294
32475
|
"File a Work Request against a Movement \u2014 how work arrives, in the requester's own language, before it is sized or agreed. Splitting it into Work Orders is a separate step (create_work_order).",
|
|
32295
|
-
|
|
32296
|
-
movementId: external_exports.string().describe("The Movement (engagement) this request is filed against"),
|
|
32297
|
-
title: external_exports.string().min(1).max(300).describe("Short title for the ask"),
|
|
32298
|
-
body: external_exports.string().max(1e4).optional().describe("The ask as written, in the requester's own words"),
|
|
32299
|
-
requesterName: external_exports.string().min(1).describe("Who is asking \u2014 display name"),
|
|
32300
|
-
requesterEmail: external_exports.string().email().describe("Who is asking \u2014 email"),
|
|
32301
|
-
origin: external_exports.enum(WORK_REQUEST_ORIGIN_ENUM).describe('Where the ask came from: "client" or "internal". One entity covers both \u2014 only the entry point differs.'),
|
|
32302
|
-
createdBy: external_exports.string().optional().describe("Optional creator identity for audit")
|
|
32303
|
-
},
|
|
32476
|
+
create_work_request,
|
|
32304
32477
|
async ({ movementId, title, body, requesterName, requesterEmail, origin, createdBy }) => {
|
|
32305
32478
|
const wr = await client.createWorkRequest({
|
|
32306
32479
|
movementId,
|
|
@@ -32858,6 +33031,7 @@ function registerAllTools(server, ctx, client, profile) {
|
|
|
32858
33031
|
registerNextActionsTools(server, ctx, client);
|
|
32859
33032
|
registerCheckTools(server, ctx, client);
|
|
32860
33033
|
registerActivityTools(server, ctx, client);
|
|
33034
|
+
registerProgressEvidenceTools(server, ctx, client);
|
|
32861
33035
|
registerAuditIntegrityTools(server, ctx, client);
|
|
32862
33036
|
registerAnalysisTools(server, ctx, client);
|
|
32863
33037
|
registerOrganizationTools(server, ctx, client);
|
|
@@ -33570,6 +33744,8 @@ function createHttpClient(config2) {
|
|
|
33570
33744
|
return { revision: result.revision, warnings: result.warnings ?? [] };
|
|
33571
33745
|
},
|
|
33572
33746
|
getRevision: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}`),
|
|
33747
|
+
// `request` maps 404 to undefined, which is what absence must be here.
|
|
33748
|
+
resolveRevisionPreview: (revisionId) => request("GET", `/api/revisions/${encodeURIComponent(revisionId)}/preview`),
|
|
33573
33749
|
listConceptRevisions: async (systemId, options) => {
|
|
33574
33750
|
const qs = options?.includeArchived ? "?includeArchived=true" : "";
|
|
33575
33751
|
const result = await request("GET", `/api/systems/${encodeURIComponent(systemId)}/revisions/pending${qs}`);
|
|
@@ -34523,10 +34699,13 @@ function createHttpClient(config2) {
|
|
|
34523
34699
|
const { teamspaceId, title, description, accountId } = input;
|
|
34524
34700
|
const body = {
|
|
34525
34701
|
title,
|
|
34526
|
-
...description !== void 0 && { description }
|
|
34527
|
-
...accountId !== void 0 && { accountId }
|
|
34702
|
+
...description !== void 0 && { description }
|
|
34528
34703
|
};
|
|
34529
|
-
const result = await request(
|
|
34704
|
+
const result = await request(
|
|
34705
|
+
"POST",
|
|
34706
|
+
`/api/teamspaces/${encodeURIComponent(teamspaceId)}/accounts/${encodeURIComponent(accountId)}/movements`,
|
|
34707
|
+
body
|
|
34708
|
+
);
|
|
34530
34709
|
if (result === void 0) throw new Error("Resource not found when creating Movement");
|
|
34531
34710
|
const parsed = MovementApiSchema.safeParse(result);
|
|
34532
34711
|
if (!parsed.success) throw new Error(`Unexpected Movement shape from API: ${parsed.error.message}`);
|
|
@@ -34936,6 +35115,11 @@ function createHttpClient(config2) {
|
|
|
34936
35115
|
const qs = asOf ? `?asOf=${encodeURIComponent(asOf)}` : "";
|
|
34937
35116
|
return request("GET", `/api/accounts/${encodeURIComponent(accountId)}/cadence-schedule${qs}`);
|
|
34938
35117
|
},
|
|
35118
|
+
getProgressEvidence: async (systemId, query) => {
|
|
35119
|
+
const qs = new URLSearchParams();
|
|
35120
|
+
for (const [key, value] of Object.entries(query)) if (value !== void 0) qs.set(key, String(value));
|
|
35121
|
+
return request("GET", `/api/systems/${encodeURIComponent(systemId)}/progress-evidence?${qs}`);
|
|
35122
|
+
},
|
|
34939
35123
|
getPulseReport: async (accountId, asOf) => {
|
|
34940
35124
|
const qs = asOf ? `?asOf=${encodeURIComponent(asOf)}` : "";
|
|
34941
35125
|
return request("GET", `/api/accounts/${encodeURIComponent(accountId)}/pulse-report${qs}`);
|
|
@@ -35369,7 +35553,7 @@ function loadConfig() {
|
|
|
35369
35553
|
};
|
|
35370
35554
|
}
|
|
35371
35555
|
async function main() {
|
|
35372
|
-
console.error(`[harmonica-mcp] v${"5.
|
|
35556
|
+
console.error(`[harmonica-mcp] v${"5.2.0"} starting\u2026`);
|
|
35373
35557
|
const config2 = loadConfig();
|
|
35374
35558
|
const client = createHttpClient({
|
|
35375
35559
|
apiBaseUrl: config2.apiBaseUrl,
|