@meltstudio/meltctl 4.154.1 → 4.155.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 +331 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var CLI_VERSION;
|
|
|
14
14
|
var init_version = __esm({
|
|
15
15
|
"src/utils/version.ts"() {
|
|
16
16
|
"use strict";
|
|
17
|
-
CLI_VERSION = "4.
|
|
17
|
+
CLI_VERSION = "4.155.0";
|
|
18
18
|
}
|
|
19
19
|
});
|
|
20
20
|
|
|
@@ -663,6 +663,8 @@ function createProjectsResource(config) {
|
|
|
663
663
|
const params = new URLSearchParams();
|
|
664
664
|
if (opts?.activeOnly)
|
|
665
665
|
params.set("activeOnly", "true");
|
|
666
|
+
if (opts?.includeInternal)
|
|
667
|
+
params.set("includeInternal", "true");
|
|
666
668
|
const path9 = `/projects${params.toString() ? `?${params}` : ""}`;
|
|
667
669
|
const { data, status } = await apiFetch(config, path9);
|
|
668
670
|
if (status === 403)
|
|
@@ -942,8 +944,19 @@ function unwrap(label, response, okStatus = 200) {
|
|
|
942
944
|
function createPmResource(config) {
|
|
943
945
|
return {
|
|
944
946
|
// ─── Features ─────────────────────────────────────────────────────────
|
|
945
|
-
async listFeatures(projectId) {
|
|
946
|
-
const
|
|
947
|
+
async listFeatures(projectId, filters) {
|
|
948
|
+
const params = new URLSearchParams({ projectId: String(projectId) });
|
|
949
|
+
if (filters?.phaseId)
|
|
950
|
+
params.set("phaseId", filters.phaseId);
|
|
951
|
+
if (filters?.status)
|
|
952
|
+
params.set("status", filters.status);
|
|
953
|
+
if (filters?.category)
|
|
954
|
+
params.set("category", filters.category);
|
|
955
|
+
if (filters?.currentStage)
|
|
956
|
+
params.set("currentStage", filters.currentStage);
|
|
957
|
+
if (filters?.targetStage)
|
|
958
|
+
params.set("targetStage", filters.targetStage);
|
|
959
|
+
const res = await apiFetch(config, `/pm/features?${params}`);
|
|
947
960
|
return unwrap("list features", res);
|
|
948
961
|
},
|
|
949
962
|
async getFeature(id) {
|
|
@@ -953,10 +966,28 @@ function createPmResource(config) {
|
|
|
953
966
|
async createFeature(input3) {
|
|
954
967
|
const res = await apiFetch(config, `/pm/features`, {
|
|
955
968
|
method: "POST",
|
|
956
|
-
body: JSON.stringify(input3)
|
|
969
|
+
body: JSON.stringify(normalizeCreateFeatureInput(input3))
|
|
957
970
|
});
|
|
958
971
|
return unwrap("create feature", res, 201);
|
|
959
972
|
},
|
|
973
|
+
/**
|
|
974
|
+
* Bulk feature create — accepts an array of inputs and returns one
|
|
975
|
+
* result per item with `success: true | false` so partial failures
|
|
976
|
+
* don't sink the whole batch. Each item is wrapped in its own
|
|
977
|
+
* transaction (feature + phase memberships if any). Built for
|
|
978
|
+
* one-shot migrations of an existing roadmap into meltctl.
|
|
979
|
+
*
|
|
980
|
+
* Status code: 201 when every item succeeded; 207 (multi-status)
|
|
981
|
+
* when at least one failed. The unwrap layer accepts both.
|
|
982
|
+
*/
|
|
983
|
+
async createFeaturesBatch(inputs) {
|
|
984
|
+
const res = await apiFetch(config, `/pm/features/batch`, {
|
|
985
|
+
method: "POST",
|
|
986
|
+
body: JSON.stringify({ features: inputs.map(normalizeCreateFeatureInput) })
|
|
987
|
+
});
|
|
988
|
+
const body = unwrap("create features batch", res, [201, 207]);
|
|
989
|
+
return body.results;
|
|
990
|
+
},
|
|
960
991
|
async updateFeature(id, input3) {
|
|
961
992
|
const res = await apiFetch(config, `/pm/features/${id}`, {
|
|
962
993
|
method: "PATCH",
|
|
@@ -985,14 +1016,23 @@ function createPmResource(config) {
|
|
|
985
1016
|
async createPhase(input3) {
|
|
986
1017
|
const res = await apiFetch(config, `/pm/phases`, {
|
|
987
1018
|
method: "POST",
|
|
988
|
-
body: JSON.stringify(input3)
|
|
1019
|
+
body: JSON.stringify(normalizeCreatePhaseInput(input3))
|
|
989
1020
|
});
|
|
990
1021
|
return unwrap("create phase", res, 201);
|
|
991
1022
|
},
|
|
1023
|
+
/** Bulk phase create — same shape as createFeaturesBatch. */
|
|
1024
|
+
async createPhasesBatch(inputs) {
|
|
1025
|
+
const res = await apiFetch(config, `/pm/phases/batch`, {
|
|
1026
|
+
method: "POST",
|
|
1027
|
+
body: JSON.stringify({ phases: inputs.map(normalizeCreatePhaseInput) })
|
|
1028
|
+
});
|
|
1029
|
+
const body = unwrap("create phases batch", res, [201, 207]);
|
|
1030
|
+
return body.results;
|
|
1031
|
+
},
|
|
992
1032
|
async updatePhase(id, input3) {
|
|
993
1033
|
const res = await apiFetch(config, `/pm/phases/${id}`, {
|
|
994
1034
|
method: "PATCH",
|
|
995
|
-
body: JSON.stringify(input3)
|
|
1035
|
+
body: JSON.stringify(normalizeUpdatePhaseInput(input3))
|
|
996
1036
|
});
|
|
997
1037
|
return unwrap("update phase", res);
|
|
998
1038
|
},
|
|
@@ -1132,6 +1172,31 @@ function createPmResource(config) {
|
|
|
1132
1172
|
}
|
|
1133
1173
|
};
|
|
1134
1174
|
}
|
|
1175
|
+
function toIsoOrNull(value) {
|
|
1176
|
+
if (value === void 0)
|
|
1177
|
+
return void 0;
|
|
1178
|
+
if (value === null)
|
|
1179
|
+
return null;
|
|
1180
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
1181
|
+
}
|
|
1182
|
+
function normalizeCreateFeatureInput(input3) {
|
|
1183
|
+
if (input3.createdAt === void 0)
|
|
1184
|
+
return input3;
|
|
1185
|
+
return { ...input3, createdAt: toIsoOrNull(input3.createdAt) ?? void 0 };
|
|
1186
|
+
}
|
|
1187
|
+
function normalizeCreatePhaseInput(input3) {
|
|
1188
|
+
const out = { ...input3 };
|
|
1189
|
+
if (input3.createdAt !== void 0)
|
|
1190
|
+
out.createdAt = toIsoOrNull(input3.createdAt) ?? void 0;
|
|
1191
|
+
if (input3.closedAt !== void 0)
|
|
1192
|
+
out.closedAt = toIsoOrNull(input3.closedAt);
|
|
1193
|
+
return out;
|
|
1194
|
+
}
|
|
1195
|
+
function normalizeUpdatePhaseInput(input3) {
|
|
1196
|
+
if (input3.closedAt === void 0)
|
|
1197
|
+
return input3;
|
|
1198
|
+
return { ...input3, closedAt: toIsoOrNull(input3.closedAt) };
|
|
1199
|
+
}
|
|
1135
1200
|
|
|
1136
1201
|
// ../sdk/dist/resources/slack.js
|
|
1137
1202
|
function createSlackResource(config) {
|
|
@@ -3535,6 +3600,8 @@ function createProjectsResource2(config) {
|
|
|
3535
3600
|
const params = new URLSearchParams();
|
|
3536
3601
|
if (opts?.activeOnly)
|
|
3537
3602
|
params.set("activeOnly", "true");
|
|
3603
|
+
if (opts?.includeInternal)
|
|
3604
|
+
params.set("includeInternal", "true");
|
|
3538
3605
|
const path22 = `/projects${params.toString() ? `?${params}` : ""}`;
|
|
3539
3606
|
const { data, status } = await apiFetch2(config, path22);
|
|
3540
3607
|
if (status === 403)
|
|
@@ -3806,8 +3873,19 @@ function unwrap2(label, response, okStatus = 200) {
|
|
|
3806
3873
|
function createPmResource2(config) {
|
|
3807
3874
|
return {
|
|
3808
3875
|
// ─── Features ─────────────────────────────────────────────────────────
|
|
3809
|
-
async listFeatures(projectId) {
|
|
3810
|
-
const
|
|
3876
|
+
async listFeatures(projectId, filters) {
|
|
3877
|
+
const params = new URLSearchParams({ projectId: String(projectId) });
|
|
3878
|
+
if (filters?.phaseId)
|
|
3879
|
+
params.set("phaseId", filters.phaseId);
|
|
3880
|
+
if (filters?.status)
|
|
3881
|
+
params.set("status", filters.status);
|
|
3882
|
+
if (filters?.category)
|
|
3883
|
+
params.set("category", filters.category);
|
|
3884
|
+
if (filters?.currentStage)
|
|
3885
|
+
params.set("currentStage", filters.currentStage);
|
|
3886
|
+
if (filters?.targetStage)
|
|
3887
|
+
params.set("targetStage", filters.targetStage);
|
|
3888
|
+
const res = await apiFetch2(config, `/pm/features?${params}`);
|
|
3811
3889
|
return unwrap2("list features", res);
|
|
3812
3890
|
},
|
|
3813
3891
|
async getFeature(id) {
|
|
@@ -3817,10 +3895,28 @@ function createPmResource2(config) {
|
|
|
3817
3895
|
async createFeature(input3) {
|
|
3818
3896
|
const res = await apiFetch2(config, `/pm/features`, {
|
|
3819
3897
|
method: "POST",
|
|
3820
|
-
body: JSON.stringify(input3)
|
|
3898
|
+
body: JSON.stringify(normalizeCreateFeatureInput2(input3))
|
|
3821
3899
|
});
|
|
3822
3900
|
return unwrap2("create feature", res, 201);
|
|
3823
3901
|
},
|
|
3902
|
+
/**
|
|
3903
|
+
* Bulk feature create — accepts an array of inputs and returns one
|
|
3904
|
+
* result per item with `success: true | false` so partial failures
|
|
3905
|
+
* don't sink the whole batch. Each item is wrapped in its own
|
|
3906
|
+
* transaction (feature + phase memberships if any). Built for
|
|
3907
|
+
* one-shot migrations of an existing roadmap into meltctl.
|
|
3908
|
+
*
|
|
3909
|
+
* Status code: 201 when every item succeeded; 207 (multi-status)
|
|
3910
|
+
* when at least one failed. The unwrap layer accepts both.
|
|
3911
|
+
*/
|
|
3912
|
+
async createFeaturesBatch(inputs) {
|
|
3913
|
+
const res = await apiFetch2(config, `/pm/features/batch`, {
|
|
3914
|
+
method: "POST",
|
|
3915
|
+
body: JSON.stringify({ features: inputs.map(normalizeCreateFeatureInput2) })
|
|
3916
|
+
});
|
|
3917
|
+
const body = unwrap2("create features batch", res, [201, 207]);
|
|
3918
|
+
return body.results;
|
|
3919
|
+
},
|
|
3824
3920
|
async updateFeature(id, input3) {
|
|
3825
3921
|
const res = await apiFetch2(config, `/pm/features/${id}`, {
|
|
3826
3922
|
method: "PATCH",
|
|
@@ -3849,14 +3945,23 @@ function createPmResource2(config) {
|
|
|
3849
3945
|
async createPhase(input3) {
|
|
3850
3946
|
const res = await apiFetch2(config, `/pm/phases`, {
|
|
3851
3947
|
method: "POST",
|
|
3852
|
-
body: JSON.stringify(input3)
|
|
3948
|
+
body: JSON.stringify(normalizeCreatePhaseInput2(input3))
|
|
3853
3949
|
});
|
|
3854
3950
|
return unwrap2("create phase", res, 201);
|
|
3855
3951
|
},
|
|
3952
|
+
/** Bulk phase create — same shape as createFeaturesBatch. */
|
|
3953
|
+
async createPhasesBatch(inputs) {
|
|
3954
|
+
const res = await apiFetch2(config, `/pm/phases/batch`, {
|
|
3955
|
+
method: "POST",
|
|
3956
|
+
body: JSON.stringify({ phases: inputs.map(normalizeCreatePhaseInput2) })
|
|
3957
|
+
});
|
|
3958
|
+
const body = unwrap2("create phases batch", res, [201, 207]);
|
|
3959
|
+
return body.results;
|
|
3960
|
+
},
|
|
3856
3961
|
async updatePhase(id, input3) {
|
|
3857
3962
|
const res = await apiFetch2(config, `/pm/phases/${id}`, {
|
|
3858
3963
|
method: "PATCH",
|
|
3859
|
-
body: JSON.stringify(input3)
|
|
3964
|
+
body: JSON.stringify(normalizeUpdatePhaseInput2(input3))
|
|
3860
3965
|
});
|
|
3861
3966
|
return unwrap2("update phase", res);
|
|
3862
3967
|
},
|
|
@@ -3996,6 +4101,31 @@ function createPmResource2(config) {
|
|
|
3996
4101
|
}
|
|
3997
4102
|
};
|
|
3998
4103
|
}
|
|
4104
|
+
function toIsoOrNull2(value) {
|
|
4105
|
+
if (value === void 0)
|
|
4106
|
+
return void 0;
|
|
4107
|
+
if (value === null)
|
|
4108
|
+
return null;
|
|
4109
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
4110
|
+
}
|
|
4111
|
+
function normalizeCreateFeatureInput2(input3) {
|
|
4112
|
+
if (input3.createdAt === void 0)
|
|
4113
|
+
return input3;
|
|
4114
|
+
return { ...input3, createdAt: toIsoOrNull2(input3.createdAt) ?? void 0 };
|
|
4115
|
+
}
|
|
4116
|
+
function normalizeCreatePhaseInput2(input3) {
|
|
4117
|
+
const out = { ...input3 };
|
|
4118
|
+
if (input3.createdAt !== void 0)
|
|
4119
|
+
out.createdAt = toIsoOrNull2(input3.createdAt) ?? void 0;
|
|
4120
|
+
if (input3.closedAt !== void 0)
|
|
4121
|
+
out.closedAt = toIsoOrNull2(input3.closedAt);
|
|
4122
|
+
return out;
|
|
4123
|
+
}
|
|
4124
|
+
function normalizeUpdatePhaseInput2(input3) {
|
|
4125
|
+
if (input3.closedAt === void 0)
|
|
4126
|
+
return input3;
|
|
4127
|
+
return { ...input3, closedAt: toIsoOrNull2(input3.closedAt) };
|
|
4128
|
+
}
|
|
3999
4129
|
function createSlackResource2(config) {
|
|
4000
4130
|
return {
|
|
4001
4131
|
async listChannels() {
|
|
@@ -4560,36 +4690,88 @@ function withClientArgs(getClient2, handler) {
|
|
|
4560
4690
|
}
|
|
4561
4691
|
};
|
|
4562
4692
|
}
|
|
4563
|
-
async function listProjects(client) {
|
|
4564
|
-
|
|
4693
|
+
async function listProjects(client, input3) {
|
|
4694
|
+
const hasPM = input3.hasPM ?? true;
|
|
4695
|
+
const includeInternal = input3.includeInternal ?? false;
|
|
4696
|
+
return safe(() => client.projects.list({ activeOnly: hasPM, includeInternal }));
|
|
4565
4697
|
}
|
|
4566
4698
|
async function getProjectSettings(client, input3) {
|
|
4567
4699
|
return safe(() => client.pm.getProjectSettings(input3.projectId));
|
|
4568
4700
|
}
|
|
4701
|
+
async function updateProjectSettings(client, input3) {
|
|
4702
|
+
const { projectId, ...patch } = input3;
|
|
4703
|
+
return safe(
|
|
4704
|
+
() => client.pm.updateProjectSettings(projectId, patch)
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4569
4707
|
async function getProjectContext(client, input3) {
|
|
4570
4708
|
return safe(() => client.pm.getProjectContext(input3.projectId));
|
|
4571
4709
|
}
|
|
4710
|
+
var PROJECT_STAGE_VALUES = ["prototype", "early_revenue", "growth", "scale"];
|
|
4572
4711
|
function registerProjectTools(server, getClient2) {
|
|
4573
4712
|
server.registerTool(
|
|
4574
4713
|
"list_projects",
|
|
4575
4714
|
{
|
|
4576
4715
|
title: "List projects",
|
|
4577
|
-
description: "Lists
|
|
4578
|
-
inputSchema: {
|
|
4716
|
+
description: "Lists Melt projects the authenticated user can see. Defaults: hasPM=true (only actively-managed projects), includeInternal=false (Melt's own internal projects hidden). Use this to resolve a project's name to its numeric `projectId` before calling any other tool. Returns id, name, client, project manager, and repo count per project.",
|
|
4717
|
+
inputSchema: {
|
|
4718
|
+
hasPM: z22.boolean().optional().describe(
|
|
4719
|
+
"Default true. When true, only projects with a project manager assigned are returned (recommended for AI agent flows). Set false to include unmanaged projects."
|
|
4720
|
+
),
|
|
4721
|
+
includeInternal: z22.boolean().optional().describe(
|
|
4722
|
+
"Default false. When false, Melt's own internal projects are hidden. Set true to include them."
|
|
4723
|
+
)
|
|
4724
|
+
}
|
|
4579
4725
|
},
|
|
4580
|
-
|
|
4726
|
+
withClientArgs(getClient2, listProjects)
|
|
4581
4727
|
);
|
|
4582
4728
|
server.registerTool(
|
|
4583
4729
|
"get_project_settings",
|
|
4584
4730
|
{
|
|
4585
4731
|
title: "Get project settings",
|
|
4586
|
-
description:
|
|
4732
|
+
description: 'Returns the project\'s PM-skills settings: cadence (weekly/biweekly/monthly), start_day, demo_day, team_size, velocity_override, Linear workspace, Notion URLs, code repo path, tech-support contact, default language, project glossary, project stage. Includes a `warnings` array flagging settings whose absence breaks downstream behavior (e.g., "calculator: missing teamSize"). Returns sensible defaults if no settings row exists yet (cadence=weekly, start_day=Monday, demo_day=Friday, defaultLanguage=English).',
|
|
4587
4733
|
inputSchema: {
|
|
4588
4734
|
projectId: z22.number().int().positive().describe("Strapi project id from list_projects.")
|
|
4589
4735
|
}
|
|
4590
4736
|
},
|
|
4591
4737
|
withClientArgs(getClient2, getProjectSettings)
|
|
4592
4738
|
);
|
|
4739
|
+
server.registerTool(
|
|
4740
|
+
"update_project_settings",
|
|
4741
|
+
{
|
|
4742
|
+
title: "Update project settings",
|
|
4743
|
+
description: "Patches PM-skills settings for a project. Pass only the fields you want to change. Use cases: setting teamSize after a roadmap migration so the maturity-jumps calculator renders, setting projectStage so audit calibration uses the right baseline, wiring notionProjectProfileUrl so risks/feedback route to the right Notion page. The PM should review the proposed change before this is called \u2014 this is a write operation. Pass null to clear a nullable field.",
|
|
4744
|
+
inputSchema: {
|
|
4745
|
+
projectId: z22.number().int().positive().describe("Strapi project id from list_projects."),
|
|
4746
|
+
cadence: z22.enum(["weekly", "biweekly", "monthly"]).optional().describe("Sprint cadence."),
|
|
4747
|
+
startDay: z22.string().min(1).optional().describe('Sprint start day, e.g. "Monday".'),
|
|
4748
|
+
demoDay: z22.string().min(1).optional().describe('Demo day, e.g. "Friday".'),
|
|
4749
|
+
teamSize: z22.number().int().nonnegative().nullable().optional().describe(
|
|
4750
|
+
"Active developer count for the maturity-jumps calculator. Pass null to clear (calculator falls back to 1)."
|
|
4751
|
+
),
|
|
4752
|
+
velocityOverride: z22.number().positive().nullable().optional().describe(
|
|
4753
|
+
"Project-specific velocity override (stories/week/dev). Wins over the company baseline when set. Pass null to clear."
|
|
4754
|
+
),
|
|
4755
|
+
linearWorkspace: z22.string().nullable().optional().describe('Linear workspace slug, e.g. "meltstudio". Pass null to clear.'),
|
|
4756
|
+
notionCallLogUrl: z22.string().url().nullable().optional().describe("Notion URL for the team's call log. Pass null to clear."),
|
|
4757
|
+
notionProjectProfileUrl: z22.string().url().nullable().optional().describe(
|
|
4758
|
+
"Notion URL for the project profile page (Risks + Feedback sections live there). Required for intake routing of risks/feedback. Pass null to clear."
|
|
4759
|
+
),
|
|
4760
|
+
codeRepoPath: z22.string().nullable().optional().describe(
|
|
4761
|
+
"Local path to the project's code repo. When set, intake skills can investigate code inline; when null, they create tech-investigation requests instead."
|
|
4762
|
+
),
|
|
4763
|
+
techSupportContact: z22.string().nullable().optional().describe("Email or handle of the project's tech-support contact. Pass null to clear."),
|
|
4764
|
+
defaultLanguage: z22.string().min(1).optional().describe("Default language for client-facing artifacts (recap emails, etc)."),
|
|
4765
|
+
projectGlossary: z22.string().nullable().optional().describe(
|
|
4766
|
+
"Free-text project glossary surfaced to skills as context. Pass null to clear."
|
|
4767
|
+
),
|
|
4768
|
+
projectStage: z22.enum(PROJECT_STAGE_VALUES).nullable().optional().describe(
|
|
4769
|
+
"Canonical PM-owned project stage that drives audit calibration: prototype | early_revenue | growth | scale. The server stamps projectStageSetBy + projectStageSetAt automatically. Pass null to clear."
|
|
4770
|
+
)
|
|
4771
|
+
}
|
|
4772
|
+
},
|
|
4773
|
+
withClientArgs(getClient2, updateProjectSettings)
|
|
4774
|
+
);
|
|
4593
4775
|
server.registerTool(
|
|
4594
4776
|
"get_project_context",
|
|
4595
4777
|
{
|
|
@@ -4602,19 +4784,26 @@ function registerProjectTools(server, getClient2) {
|
|
|
4602
4784
|
withClientArgs(getClient2, getProjectContext)
|
|
4603
4785
|
);
|
|
4604
4786
|
}
|
|
4787
|
+
var SHAPE_VALUES = ["crawling", "walking", "running"];
|
|
4605
4788
|
var SHAPE_DESC = "crawling | walking | running. Target shape is intent (PM sets when creating); current shape is computed from feature stages.";
|
|
4606
4789
|
async function listPhases(client, input3) {
|
|
4607
4790
|
return safe(() => client.pm.listPhases(input3.projectId, input3.includeClosed ?? false));
|
|
4608
4791
|
}
|
|
4609
4792
|
async function createPhase(client, input3) {
|
|
4610
|
-
return safe(
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
);
|
|
4793
|
+
return safe(() => client.pm.createPhase(input3));
|
|
4794
|
+
}
|
|
4795
|
+
async function createPhasesBatch(client, input3) {
|
|
4796
|
+
return safe(() => client.pm.createPhasesBatch(input3.phases));
|
|
4797
|
+
}
|
|
4798
|
+
async function updatePhase(client, input3) {
|
|
4799
|
+
const { id, ...patch } = input3;
|
|
4800
|
+
return safe(() => client.pm.updatePhase(id, patch));
|
|
4801
|
+
}
|
|
4802
|
+
async function deletePhase(client, input3) {
|
|
4803
|
+
return safe(async () => {
|
|
4804
|
+
await client.pm.deletePhase(input3.id);
|
|
4805
|
+
return { ok: true, id: input3.id };
|
|
4806
|
+
});
|
|
4618
4807
|
}
|
|
4619
4808
|
async function assignFeatureToPhase(client, input3) {
|
|
4620
4809
|
return safe(() => client.pm.assignFeature(input3.phaseId, input3.featureId));
|
|
@@ -4625,8 +4814,10 @@ async function unassignFeatureFromPhase(client, input3) {
|
|
|
4625
4814
|
var createPhaseInputSchema = z3.object({
|
|
4626
4815
|
projectId: z3.number().int().positive(),
|
|
4627
4816
|
name: z3.string().min(1),
|
|
4628
|
-
targetShape: z3.enum(
|
|
4629
|
-
isPrimary: z3.boolean().optional()
|
|
4817
|
+
targetShape: z3.enum(SHAPE_VALUES).default("walking"),
|
|
4818
|
+
isPrimary: z3.boolean().optional(),
|
|
4819
|
+
createdAt: z3.string().datetime().optional(),
|
|
4820
|
+
closedAt: z3.string().datetime().nullable().optional()
|
|
4630
4821
|
});
|
|
4631
4822
|
function registerPhaseTools(server, getClient2) {
|
|
4632
4823
|
server.registerTool(
|
|
@@ -4645,18 +4836,74 @@ function registerPhaseTools(server, getClient2) {
|
|
|
4645
4836
|
"create_phase",
|
|
4646
4837
|
{
|
|
4647
4838
|
title: "Create phase",
|
|
4648
|
-
description: "Creates a new phase on the project. The PM should review the proposed name + target shape before this is called \u2014 this is a write operation. If isPrimary=true, any existing primary phase on the project is automatically demoted (atomic). Use isPrimary sparingly; multi-active phases without changing primary is the safer default.",
|
|
4839
|
+
description: "Creates a new phase on the project. The PM should review the proposed name + target shape before this is called \u2014 this is a write operation. If isPrimary=true, any existing primary phase on the project is automatically demoted (atomic). Use isPrimary sparingly; multi-active phases without changing primary is the safer default. For roadmap migrations preserving historical timelines, pass createdAt and/or closedAt \u2014 a non-null closedAt also marks the phase isActive=false + isPrimary=false.",
|
|
4649
4840
|
inputSchema: {
|
|
4650
4841
|
projectId: z3.number().int().positive(),
|
|
4651
4842
|
name: z3.string().min(1).describe('e.g. "MVP launch", "V2 features".'),
|
|
4652
|
-
targetShape: z3.enum(
|
|
4843
|
+
targetShape: z3.enum(SHAPE_VALUES).default("walking").describe(SHAPE_DESC),
|
|
4653
4844
|
isPrimary: z3.boolean().optional().describe(
|
|
4654
4845
|
"Default false. Setting true demotes any existing primary phase. Only one primary per project."
|
|
4846
|
+
),
|
|
4847
|
+
createdAt: z3.string().datetime().optional().describe(
|
|
4848
|
+
"ISO 8601 timestamp. Backdates createdAt \u2014 only set this when migrating an existing phase that has a real historical creation date."
|
|
4849
|
+
),
|
|
4850
|
+
closedAt: z3.string().datetime().nullable().optional().describe(
|
|
4851
|
+
"ISO 8601 timestamp. When set, the phase is created as historical (isActive=false, isPrimary=false). Use to backfill phases that closed months ago."
|
|
4655
4852
|
)
|
|
4656
4853
|
}
|
|
4657
4854
|
},
|
|
4658
4855
|
withClientArgs(getClient2, createPhase)
|
|
4659
4856
|
);
|
|
4857
|
+
server.registerTool(
|
|
4858
|
+
"create_phases_batch",
|
|
4859
|
+
{
|
|
4860
|
+
title: "Create phases batch",
|
|
4861
|
+
description: "Bulk phase create \u2014 accepts an array of inputs (max 100) and returns one result per input with success or error. Each item is wrapped in its own transaction. Built for one-shot migrations of an existing roadmap.",
|
|
4862
|
+
inputSchema: {
|
|
4863
|
+
phases: z3.array(createPhaseInputSchema).min(1).max(100).describe("Up to 100 phase inputs. Each accepts the same fields as create_phase.")
|
|
4864
|
+
}
|
|
4865
|
+
},
|
|
4866
|
+
withClientArgs(getClient2, createPhasesBatch)
|
|
4867
|
+
);
|
|
4868
|
+
server.registerTool(
|
|
4869
|
+
"update_phase",
|
|
4870
|
+
{
|
|
4871
|
+
title: "Update phase",
|
|
4872
|
+
description: "Patches fields on an existing phase. Pass only the fields you want to change. Common uses: rename, switch target shape, override or clear the computed current shape, set or clear primary, mark a phase closed/reopened. The PM should review the proposed change before this is called \u2014 this is a write operation. Setting closedAt non-null forces isActive=false + isPrimary=false; setting isActive=false also clears isPrimary.",
|
|
4873
|
+
inputSchema: {
|
|
4874
|
+
id: z3.string().uuid().describe("Phase id from list_phases or create_phase."),
|
|
4875
|
+
name: z3.string().min(1).optional(),
|
|
4876
|
+
targetShape: z3.enum(SHAPE_VALUES).optional().describe(SHAPE_DESC),
|
|
4877
|
+
currentShapeOverride: z3.enum(SHAPE_VALUES).nullable().optional().describe(
|
|
4878
|
+
"Force the phase's current shape regardless of feature distribution. Pass null to clear and resume computed-shape behavior."
|
|
4879
|
+
),
|
|
4880
|
+
overrideReason: z3.string().nullable().optional().describe("Free-text reason for the shape override. Pass null to clear."),
|
|
4881
|
+
isPrimary: z3.boolean().optional().describe(
|
|
4882
|
+
"Promote this phase to primary. Server demotes any existing primary atomically."
|
|
4883
|
+
),
|
|
4884
|
+
isActive: z3.boolean().optional().describe(
|
|
4885
|
+
"Set to false to deactivate (also clears isPrimary). Set to true to reopen a closed phase (closedAt stays unless you also clear it)."
|
|
4886
|
+
),
|
|
4887
|
+
closedAt: z3.string().datetime().nullable().optional().describe(
|
|
4888
|
+
"ISO 8601 timestamp or null. When set non-null, forces isActive=false + isPrimary=false. When null, clears the closed-at stamp."
|
|
4889
|
+
)
|
|
4890
|
+
}
|
|
4891
|
+
},
|
|
4892
|
+
withClientArgs(getClient2, updatePhase)
|
|
4893
|
+
);
|
|
4894
|
+
server.registerTool(
|
|
4895
|
+
"delete_phase",
|
|
4896
|
+
{
|
|
4897
|
+
title: "Delete phase (DESTRUCTIVE \u2014 hard delete, no undo)",
|
|
4898
|
+
description: `\u26A0 DESTRUCTIVE. Hard-deletes the phase row from postgres. Not a soft archive \u2014 the row is gone and there is no recovery short of restoring the database from backup. The API refuses the delete if the phase still has features assigned (move or unassign the features first); this safety net prevents accidentally cascading away every phase membership in one call. Use ONLY for rolling back a bad insert during a migration or removing a phase created in error.
|
|
4899
|
+
|
|
4900
|
+
Mandatory caller behavior: present the phase's name + id + active/closed status + the count of any current feature memberships to the PM, get explicit confirmation ("yes, delete <name>"), then call. Never call without that confirmation. If you have any doubt, prefer closing the phase via update_phase (closedAt + isActive=false) instead \u2014 that preserves history.`,
|
|
4901
|
+
inputSchema: {
|
|
4902
|
+
id: z3.string().uuid().describe("Phase id from list_phases or create_phase.")
|
|
4903
|
+
}
|
|
4904
|
+
},
|
|
4905
|
+
withClientArgs(getClient2, deletePhase)
|
|
4906
|
+
);
|
|
4660
4907
|
server.registerTool(
|
|
4661
4908
|
"assign_feature_to_phase",
|
|
4662
4909
|
{
|
|
@@ -4686,11 +4933,21 @@ var STAGE_VALUES = ["idea", "poc", "pt", "mv", "mk", "ma", "mbi"];
|
|
|
4686
4933
|
var STAGE_DESC = "idea | poc | pt | mv | mk | ma | mbi. The 7 maturity stages from the workflow doc \u2014 pick the one that matches where the work *looks like* it is right now (or where it should land for target).";
|
|
4687
4934
|
var PRD_PATH_DESC = "http(s) URL pointing to the PRD (Notion, Google Doc, etc.). Omit if there is no PRD yet \u2014 relative paths or plain text are rejected.";
|
|
4688
4935
|
async function listFeatures(client, input3) {
|
|
4689
|
-
|
|
4936
|
+
const { projectId, ...filters } = input3;
|
|
4937
|
+
return safe(() => client.pm.listFeatures(projectId, filters));
|
|
4690
4938
|
}
|
|
4691
4939
|
async function createFeature(client, input3) {
|
|
4692
4940
|
return safe(() => client.pm.createFeature(input3));
|
|
4693
4941
|
}
|
|
4942
|
+
async function createFeaturesBatch(client, input3) {
|
|
4943
|
+
return safe(() => client.pm.createFeaturesBatch(input3.features));
|
|
4944
|
+
}
|
|
4945
|
+
async function deleteFeature(client, input3) {
|
|
4946
|
+
return safe(async () => {
|
|
4947
|
+
await client.pm.deleteFeature(input3.id);
|
|
4948
|
+
return { ok: true, id: input3.id };
|
|
4949
|
+
});
|
|
4950
|
+
}
|
|
4694
4951
|
async function updateFeature(client, input3) {
|
|
4695
4952
|
const { id, ...patch } = input3;
|
|
4696
4953
|
return safe(() => client.pm.updateFeature(id, patch));
|
|
@@ -4704,7 +4961,10 @@ var createFeatureInputSchema = z4.object({
|
|
|
4704
4961
|
currentStage: z4.enum(STAGE_VALUES).optional(),
|
|
4705
4962
|
targetStage: z4.enum(STAGE_VALUES).optional(),
|
|
4706
4963
|
status: z4.string().optional(),
|
|
4707
|
-
clientDependencies: z4.string().optional()
|
|
4964
|
+
clientDependencies: z4.string().optional(),
|
|
4965
|
+
createdAt: z4.string().datetime().optional(),
|
|
4966
|
+
phaseId: z4.string().uuid().optional(),
|
|
4967
|
+
phaseIds: z4.array(z4.string().uuid()).optional()
|
|
4708
4968
|
});
|
|
4709
4969
|
var updateFeatureInputSchema = z4.object({
|
|
4710
4970
|
id: z4.string().uuid(),
|
|
@@ -4723,9 +4983,14 @@ function registerFeatureTools(server, getClient2) {
|
|
|
4723
4983
|
"list_features",
|
|
4724
4984
|
{
|
|
4725
4985
|
title: "List features",
|
|
4726
|
-
description: "Lists
|
|
4986
|
+
description: "Lists features on the project. Each entry has id, name, category, description, current_stage, target_stage, status, prd_path, client_dependencies, and Linear epic url if synced. Optional filters narrow server-side: phaseId returns only features assigned to that phase; status / category / currentStage / targetStage filter on the matching column.",
|
|
4727
4987
|
inputSchema: {
|
|
4728
|
-
projectId: z4.number().int().positive()
|
|
4988
|
+
projectId: z4.number().int().positive(),
|
|
4989
|
+
phaseId: z4.string().uuid().optional().describe("Limit to features assigned to this phase (phase_features join)."),
|
|
4990
|
+
status: z4.string().optional().describe("Filter by status \u2014 typically not_started / in_progress / done."),
|
|
4991
|
+
category: z4.string().optional().describe("Filter by roadmap category, exact match."),
|
|
4992
|
+
currentStage: z4.enum(STAGE_VALUES).optional().describe("Filter by current_stage."),
|
|
4993
|
+
targetStage: z4.enum(STAGE_VALUES).optional().describe("Filter by target_stage.")
|
|
4729
4994
|
}
|
|
4730
4995
|
},
|
|
4731
4996
|
withClientArgs(getClient2, listFeatures)
|
|
@@ -4734,7 +4999,7 @@ function registerFeatureTools(server, getClient2) {
|
|
|
4734
4999
|
"create_feature",
|
|
4735
5000
|
{
|
|
4736
5001
|
title: "Create feature",
|
|
4737
|
-
description: "Creates a new feature on the project. The PM should review the proposed fields before this is called \u2014 this is a write operation.
|
|
5002
|
+
description: "Creates a new feature on the project. The PM should review the proposed fields before this is called \u2014 this is a write operation. Optionally pre-assigns the new feature to one or more phases via phaseId / phaseIds (atomic \u2014 feature + memberships land together or neither does). For roadmap migrations from older sources, pass createdAt to preserve the historical creation timestamp.",
|
|
4738
5003
|
inputSchema: {
|
|
4739
5004
|
projectId: z4.number().int().positive(),
|
|
4740
5005
|
name: z4.string().min(1),
|
|
@@ -4744,11 +5009,42 @@ function registerFeatureTools(server, getClient2) {
|
|
|
4744
5009
|
currentStage: z4.enum(STAGE_VALUES).optional().describe(`Default idea. ${STAGE_DESC}`),
|
|
4745
5010
|
targetStage: z4.enum(STAGE_VALUES).optional().describe(`Default mv. ${STAGE_DESC}`),
|
|
4746
5011
|
status: z4.string().optional().describe("Default not_started. Common values: not_started, in_progress, done."),
|
|
4747
|
-
clientDependencies: z4.string().optional().describe("Free-text. What blockers from the client side, if any.")
|
|
5012
|
+
clientDependencies: z4.string().optional().describe("Free-text. What blockers from the client side, if any."),
|
|
5013
|
+
createdAt: z4.string().datetime().optional().describe(
|
|
5014
|
+
"ISO 8601 timestamp. Backdates the row's createdAt \u2014 only set this when migrating an existing roadmap that has a real historical creation date. Otherwise omit and the server stamps now()."
|
|
5015
|
+
),
|
|
5016
|
+
phaseId: z4.string().uuid().optional().describe("Atomically assign the new feature to this phase. Same project required."),
|
|
5017
|
+
phaseIds: z4.array(z4.string().uuid()).optional().describe(
|
|
5018
|
+
"Atomically assign the new feature to multiple phases. Same project required for every phase."
|
|
5019
|
+
)
|
|
4748
5020
|
}
|
|
4749
5021
|
},
|
|
4750
5022
|
withClientArgs(getClient2, createFeature)
|
|
4751
5023
|
);
|
|
5024
|
+
server.registerTool(
|
|
5025
|
+
"create_features_batch",
|
|
5026
|
+
{
|
|
5027
|
+
title: "Create features batch",
|
|
5028
|
+
description: `Bulk feature create \u2014 accepts an array of inputs (max 200) and returns one result per input with success or error. Each item is wrapped in its own transaction (feature + phase memberships if any), so partial failures don't sink the whole batch. Built for one-shot migrations of an existing roadmap (Excel "Features List" sheets etc).`,
|
|
5029
|
+
inputSchema: {
|
|
5030
|
+
features: z4.array(createFeatureInputSchema).min(1).max(200).describe("Up to 200 feature inputs. Each accepts the same fields as create_feature.")
|
|
5031
|
+
}
|
|
5032
|
+
},
|
|
5033
|
+
withClientArgs(getClient2, createFeaturesBatch)
|
|
5034
|
+
);
|
|
5035
|
+
server.registerTool(
|
|
5036
|
+
"delete_feature",
|
|
5037
|
+
{
|
|
5038
|
+
title: "Delete feature (DESTRUCTIVE \u2014 hard delete, no undo)",
|
|
5039
|
+
description: `\u26A0 DESTRUCTIVE. Hard-deletes the feature row from postgres. Not a soft archive \u2014 the row is gone, the DB cascades all phase_features memberships so the feature disappears from every phase it was attached to, and there is no recovery short of restoring the database from backup. Use ONLY for rolling back a bad insert during a migration or removing a feature created in error.
|
|
5040
|
+
|
|
5041
|
+
Mandatory caller behavior: present the feature's name + id + every phase it currently belongs to to the PM, get explicit confirmation ("yes, delete <name>"), then call. Never call without that confirmation. If you have any doubt, prefer marking the feature's status to something like "cancelled" via update_feature instead \u2014 that preserves history.`,
|
|
5042
|
+
inputSchema: {
|
|
5043
|
+
id: z4.string().uuid().describe("Feature id from list_features or create_feature.")
|
|
5044
|
+
}
|
|
5045
|
+
},
|
|
5046
|
+
withClientArgs(getClient2, deleteFeature)
|
|
5047
|
+
);
|
|
4752
5048
|
server.registerTool(
|
|
4753
5049
|
"update_feature",
|
|
4754
5050
|
{
|
package/package.json
CHANGED