targetprocess-mcp-server 2.6.1 → 2.6.2
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/README.md +4 -1
- package/build/handlers/create_formatted_feature.js +81 -0
- package/build/handlers/create_formatted_user_story.js +55 -0
- package/build/handlers/get_feature_content.js +38 -0
- package/build/handlers/update_feature.js +14 -0
- package/build/index.js +192 -69
- package/build/tp.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ Releases
|
|
|
42
42
|
- `get_release_open_user_stories` — Get only active/open user stories for a release (name, withDescription, optional results)
|
|
43
43
|
|
|
44
44
|
Features
|
|
45
|
+
- `get_feature_content` — Get a Targetprocess Feature by ID, including description, state, and progress (id)
|
|
45
46
|
- `get_feature_user_stories` — Get all user stories for a feature by its ID (id)
|
|
46
47
|
- `get_not_covered_user_stories_in_feature` — Get user stories in a feature not yet covered by tests, includes `covered` field based on "Test Automation" custom field (id)
|
|
47
48
|
|
|
@@ -91,7 +92,7 @@ Cards — Write
|
|
|
91
92
|
- `create_user_story` — Create a new user story (title, optional description, optional featureId, optional releaseId, optional projectId, optional teamId)
|
|
92
93
|
> [!NOTE]
|
|
93
94
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
94
|
-
- `create_formatted_user_story` — Create a new user story with a structured template description (title, header object with asA/iWant/soThat,
|
|
95
|
+
- `create_formatted_user_story` — Create a new user story with a structured template description in Header → Definitions → Scenarios → Examples Table → Edge Cases → Acceptance Criteria → References → Notes order (title, header object with asA/iWant/soThat, scenarios array with Gherkin steps, acceptanceCriteria array, optional definitions, examplesTable, edgeCases, references, notes, optional featureId, releaseId, projectId, teamId)
|
|
95
96
|
> [!NOTE]
|
|
96
97
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
97
98
|
- `format_existing_user_story` — Re-format the description of an existing user story using the structured template (id, header object with asA/iWant/soThat, acceptanceCriteria array, scenarios array with Gherkin steps, optional title, definitions, examplesTable, edgeCases, references, notes)
|
|
@@ -102,6 +103,8 @@ Cards — Write
|
|
|
102
103
|
- `create_feature` — Create a new feature (title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId)
|
|
103
104
|
> [!NOTE]
|
|
104
105
|
> `projectId` and `teamId` are optional — fall back to `TP_PROJECT_ID` and `TP_TEAM_ID` from config
|
|
106
|
+
- `create_formatted_feature` — Create a new feature with a feature-level TDRE template in Header → Definitions → Scope & Boundaries → Non-Functional Requirements → Cross-Cutting Scenarios → Child Stories → Open Questions/Risks → References → Notes order (title, header object with businessBackground, nonFunctionalRequirements array with area/requirement/status/storyOrOwner, optional definitions, scope, crossCuttingScenarios, childStories, openQuestions, references, notes, optional epicId, releaseId, projectId, teamId)
|
|
107
|
+
- `update_feature` — Update a feature card with data provided from user input; pass only the fields to change (id, optional title, optional description, optional epicId, optional releaseId, optional projectId, optional teamId, optional entityStateId, optional tags, optional teamIterationId)
|
|
105
108
|
- `create_test_plan` — Create a test plan linked to a UserStory, Bug, or Feature (title, resourceId, optional resourceType, optional description/startDate/endDate)
|
|
106
109
|
> [!NOTE]
|
|
107
110
|
> requires `TP_PROJECT_ID`,
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const gherkinBlock = (items) => items.map((s, indx) => `<div><strong>Scenario ${indx + 1} - ${s.name}:</strong></div><div>${s.steps.map(step => `<div>\t${step}</div>`).join('\n')}</div>`).join('<br>');
|
|
2
|
+
export async function handleCreateFormattedFeature(tp, params) {
|
|
3
|
+
const { title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId } = params;
|
|
4
|
+
const parts = ['<div>'];
|
|
5
|
+
parts.push('<h3>Header</h3>');
|
|
6
|
+
if (header.featureId)
|
|
7
|
+
parts.push(`<p><strong>Feature ID:</strong> ${header.featureId}</p>`);
|
|
8
|
+
parts.push(`<p><strong>Business Background:</strong> ${header.businessBackground}</p>`);
|
|
9
|
+
if (definitions && definitions.length > 0) {
|
|
10
|
+
parts.push('<h3>Definitions</h3>');
|
|
11
|
+
parts.push('<div>');
|
|
12
|
+
for (const def of definitions) {
|
|
13
|
+
parts.push(`<p><strong>${def.term}</strong> — ${def.description}</p>`);
|
|
14
|
+
}
|
|
15
|
+
parts.push('</div>');
|
|
16
|
+
}
|
|
17
|
+
if (scope && ((scope.includes?.length ?? 0) > 0 || (scope.excludes?.length ?? 0) > 0)) {
|
|
18
|
+
parts.push('<h3>Scope & Boundaries</h3>');
|
|
19
|
+
if (scope.includes && scope.includes.length > 0) {
|
|
20
|
+
parts.push('<p><strong>Includes:</strong></p>');
|
|
21
|
+
parts.push('<ul>');
|
|
22
|
+
for (const item of scope.includes)
|
|
23
|
+
parts.push(`<li>${item}</li>`);
|
|
24
|
+
parts.push('</ul>');
|
|
25
|
+
}
|
|
26
|
+
if (scope.excludes && scope.excludes.length > 0) {
|
|
27
|
+
parts.push('<p><strong>Excludes:</strong></p>');
|
|
28
|
+
parts.push('<ul>');
|
|
29
|
+
for (const item of scope.excludes)
|
|
30
|
+
parts.push(`<li>${item}</li>`);
|
|
31
|
+
parts.push('</ul>');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
parts.push('<h3>Non-Functional Requirements</h3>');
|
|
35
|
+
parts.push('<table><tr><th>NFR Area</th><th>Requirement</th><th>Status</th><th>Story / Owner</th></tr>');
|
|
36
|
+
for (const nfr of nonFunctionalRequirements) {
|
|
37
|
+
parts.push(`<tr><td>${nfr.area}</td><td>${nfr.requirement}</td><td>${nfr.status}</td><td>${nfr.storyOrOwner}</td></tr>`);
|
|
38
|
+
}
|
|
39
|
+
parts.push('</table>');
|
|
40
|
+
if (crossCuttingScenarios && crossCuttingScenarios.length > 0) {
|
|
41
|
+
parts.push('<h3>Cross-Cutting Scenarios</h3>');
|
|
42
|
+
parts.push(gherkinBlock(crossCuttingScenarios));
|
|
43
|
+
}
|
|
44
|
+
if (childStories && childStories.length > 0) {
|
|
45
|
+
parts.push('<h3>Child Stories</h3>');
|
|
46
|
+
parts.push('<ul>');
|
|
47
|
+
for (const story of childStories) {
|
|
48
|
+
parts.push(`<li>[${story.covered ? 'x' : ' '}] ${story.id} — ${story.name}${story.covered ? ' (covered)' : ' (not yet covered by tests)'}</li>`);
|
|
49
|
+
}
|
|
50
|
+
parts.push('</ul>');
|
|
51
|
+
}
|
|
52
|
+
if (openQuestions && openQuestions.length > 0) {
|
|
53
|
+
parts.push('<h3>Open Questions / Risks</h3>');
|
|
54
|
+
parts.push('<ul>');
|
|
55
|
+
for (const question of openQuestions)
|
|
56
|
+
parts.push(`<li>${question}</li>`);
|
|
57
|
+
parts.push('</ul>');
|
|
58
|
+
}
|
|
59
|
+
if (references) {
|
|
60
|
+
parts.push('<h3>References</h3>');
|
|
61
|
+
parts.push(`<p>${references}</p>`);
|
|
62
|
+
}
|
|
63
|
+
if (notes) {
|
|
64
|
+
parts.push('<h3>Notes</h3>');
|
|
65
|
+
parts.push(`<p>${notes}</p>`);
|
|
66
|
+
}
|
|
67
|
+
parts.push('</div>');
|
|
68
|
+
const description = parts.join('\n');
|
|
69
|
+
const featureResponse = await tp.createFeature({ title, description, epicId, releaseId, projectId, teamId });
|
|
70
|
+
if (!featureResponse) {
|
|
71
|
+
return {
|
|
72
|
+
content: [{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: `Failed to create formatted feature "${title}"\n JSON: ${JSON.stringify(featureResponse, null, 2)}`
|
|
75
|
+
}],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: 'text', text: JSON.stringify(featureResponse) }],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const gherkinBlock = (items) => items.map((s, indx) => `<div><strong>Scenario ${indx + 1} - ${s.name}:</strong></div><div>${s.steps.map(step => `<div>\t${step}</div>`).join('\n')}</div>`).join('<br>');
|
|
2
|
+
export async function handleCreateFormattedUserStory(tp, params) {
|
|
3
|
+
const { title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId } = params;
|
|
4
|
+
const parts = ['<div>'];
|
|
5
|
+
parts.push('<h3>Header</h3>');
|
|
6
|
+
if (header.storyId)
|
|
7
|
+
parts.push(`<p><strong>Story ID:</strong> ${header.storyId}</p>`);
|
|
8
|
+
parts.push(`<p>As a ${header.asA} <br> I want ${header.iWant} <br> so that ${header.soThat}</p>`);
|
|
9
|
+
if (definitions && definitions.length > 0) {
|
|
10
|
+
parts.push('<h3>Definitions</h3>');
|
|
11
|
+
parts.push('<div>');
|
|
12
|
+
for (const def of definitions) {
|
|
13
|
+
parts.push(`<p><strong>${def.term}</strong> — ${def.description}</p>`);
|
|
14
|
+
}
|
|
15
|
+
parts.push('</div>');
|
|
16
|
+
}
|
|
17
|
+
parts.push('<h3>Scenarios</h3>');
|
|
18
|
+
parts.push(gherkinBlock(scenarios));
|
|
19
|
+
if (examplesTable) {
|
|
20
|
+
parts.push('<h3>Examples Table</h3>');
|
|
21
|
+
parts.push(`<pre>${examplesTable}</pre>`);
|
|
22
|
+
}
|
|
23
|
+
if (edgeCases && edgeCases.length > 0) {
|
|
24
|
+
parts.push('<h3>Edge Cases</h3>');
|
|
25
|
+
parts.push(gherkinBlock(edgeCases));
|
|
26
|
+
}
|
|
27
|
+
parts.push('<h3>Acceptance Criteria</h3>');
|
|
28
|
+
parts.push('<ol>');
|
|
29
|
+
for (const criterion of acceptanceCriteria) {
|
|
30
|
+
parts.push(`<li>${criterion}</li>`);
|
|
31
|
+
}
|
|
32
|
+
parts.push('</ol>');
|
|
33
|
+
if (references) {
|
|
34
|
+
parts.push('<h3>References</h3>');
|
|
35
|
+
parts.push(`<p>${references}</p>`);
|
|
36
|
+
}
|
|
37
|
+
if (notes) {
|
|
38
|
+
parts.push('<h3>Notes</h3>');
|
|
39
|
+
parts.push(`<p>${notes}</p>`);
|
|
40
|
+
}
|
|
41
|
+
parts.push('</div>');
|
|
42
|
+
const description = parts.join('\n');
|
|
43
|
+
const userStoryResponse = await tp.createUserStory({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId });
|
|
44
|
+
if (!userStoryResponse) {
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: `Failed to create formatted user story "${title}"\n JSON: ${JSON.stringify(userStoryResponse, null, 2)}`
|
|
49
|
+
}],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: 'text', text: JSON.stringify(userStoryResponse) }],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { JSDOM } from 'jsdom';
|
|
2
|
+
export async function handleGetFeatureContent(tp, id) {
|
|
3
|
+
const feature = await tp.getFeature(id);
|
|
4
|
+
if (!feature) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{
|
|
7
|
+
type: 'text',
|
|
8
|
+
text: `Failed to get feature, id: ${id}\n JSON: ${JSON.stringify(feature, null, 2)}`
|
|
9
|
+
}],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const result = {
|
|
13
|
+
name: feature.Name,
|
|
14
|
+
id: feature.Id,
|
|
15
|
+
description: '',
|
|
16
|
+
entityState: feature.EntityState?.Name,
|
|
17
|
+
release: feature.Release?.Name,
|
|
18
|
+
epic: feature.Epic?.Name,
|
|
19
|
+
progress: feature.Progress,
|
|
20
|
+
effort: feature.Effort,
|
|
21
|
+
customFields: feature.CustomFields,
|
|
22
|
+
};
|
|
23
|
+
const description = feature.Description || '';
|
|
24
|
+
if (description) {
|
|
25
|
+
try {
|
|
26
|
+
const dom = new JSDOM(`<html><body><div id="content">${description}</div></body></html>`);
|
|
27
|
+
const text = dom.window.document.getElementById('content')?.textContent;
|
|
28
|
+
if (text)
|
|
29
|
+
result.description = text;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error('Error parsing feature description:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export async function handleUpdateFeature(tp, params) {
|
|
2
|
+
const response = await tp.updateFeature(params);
|
|
3
|
+
if (!response) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{
|
|
6
|
+
type: 'text',
|
|
7
|
+
text: `Failed to update feature id: ${params.id}\n JSON: ${JSON.stringify(response, null, 2)}`
|
|
8
|
+
}],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: 'text', text: JSON.stringify(response) }],
|
|
13
|
+
};
|
|
14
|
+
}
|
package/build/index.js
CHANGED
|
@@ -26,6 +26,9 @@ import { handleGetUserStoryComments } from "./handlers/get_user_story_comments.j
|
|
|
26
26
|
import { handleGetBugComments } from "./handlers/get_bug_comments.js";
|
|
27
27
|
import { handleCreateBug } from "./handlers/create_bug.js";
|
|
28
28
|
import { handleCreateUserStory } from "./handlers/create_user_story.js";
|
|
29
|
+
import { handleCreateFormattedUserStory } from "./handlers/create_formatted_user_story.js";
|
|
30
|
+
import { handleCreateFormattedFeature } from "./handlers/create_formatted_feature.js";
|
|
31
|
+
import { handleUpdateFeature } from "./handlers/update_feature.js";
|
|
29
32
|
import { handleCreateFeature } from "./handlers/create_feature.js";
|
|
30
33
|
import { handleCreateEpic } from "./handlers/create_epic.js";
|
|
31
34
|
import { handleGetEpicContent } from "./handlers/get_epic_content.js";
|
|
@@ -39,6 +42,7 @@ import { handleListMyBugs } from "./handlers/list_my_bugs.js";
|
|
|
39
42
|
import { handleLogTime } from "./handlers/log_time.js";
|
|
40
43
|
import { handleGetMyTimeLogs } from "./handlers/get_my_time_logs.js";
|
|
41
44
|
import { handleGetFeatureUserStories } from "./handlers/get_feature_user_stories.js";
|
|
45
|
+
import { handleGetFeatureContent } from "./handlers/get_feature_content.js";
|
|
42
46
|
import { handleGetUserStoryBugs } from "./handlers/get_user_story_bugs.js";
|
|
43
47
|
import { handleGetCardCurrentStatus } from "./handlers/get_card_current_status.js";
|
|
44
48
|
import { handleUpdateUserStorySubState } from "./handlers/update_user_story_sub_state.js";
|
|
@@ -613,8 +617,16 @@ server.registerTool('create_user_story', {
|
|
|
613
617
|
}, async ({ title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => handleCreateUserStory(tp, { title, description, featureId, releaseId, projectId, teamId, tags, teamIterationId }));
|
|
614
618
|
server.registerTool('create_formatted_user_story', {
|
|
615
619
|
title: 'Create a formatted user story',
|
|
616
|
-
description: `Create a new user story in Targetprocess with a structured, template-driven description.
|
|
617
|
-
|
|
620
|
+
description: `Create a new user story in Targetprocess with a structured, template-driven description, assembled from discrete sections and stored as HTML.
|
|
621
|
+
Fill in each field to this quality bar:
|
|
622
|
+
1) Header — "asA" and "iWant" must be filled in, and "soThat" MUST be a real business outcome, not a restatement of "iWant";
|
|
623
|
+
2) Definitions — check for jargon, feature flags, module names, and acronyms; if none apply, OMIT the "definitions" field entirely (do not send an empty section);
|
|
624
|
+
3) Scenarios (write these BEFORE acceptanceCriteria) — at least one Gherkin scenario per distinct behavior; each scenario has exactly ONE "Then" outcome (split it into two scenarios if it needs two); avoid vague verbs like "system validates input" — spell out exactly what "validates" means in the "Then" step;
|
|
625
|
+
4) Examples Table — if any behavior is described as "supports various formats/roles/states", convert it into a Scenario Outline (one of the "scenarios" entries) plus a matching "examplesTable" with real values; if no parameterized behavior exists, OMIT "examplesTable";
|
|
626
|
+
5) Edge Cases — MANDATORY (include at least one error-state scenario and one boundary-condition scenario in "edgeCases") if the story touches validation/input handling, permissions/roles, or external data/integrations; otherwise OMIT "edgeCases";
|
|
627
|
+
6) Acceptance Criteria — every bullet MUST be traceable to a specific scenario or edge case above; if a bullet isn't backed by a scenario, it isn't a criterion yet — put it in "notes" as a flagged gap instead;
|
|
628
|
+
7) References — put mockup/spec links here, not inline in prose;
|
|
629
|
+
8) Notes — capture open questions or known constraints here; don't let them hide inside acceptanceCriteria;
|
|
618
630
|
CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
|
|
619
631
|
1) IF the user specified a feature by name (not ID), call "get_feature_user_stories" or "search_tp_cards" to resolve the feature ID;
|
|
620
632
|
2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
|
|
@@ -633,7 +645,7 @@ server.registerTool('create_formatted_user_story', {
|
|
|
633
645
|
iWant: z.string()
|
|
634
646
|
.describe('Goal — the "I want ..." part'),
|
|
635
647
|
soThat: z.string()
|
|
636
|
-
.describe('Benefit — the "so that ..." part'),
|
|
648
|
+
.describe('Benefit — the "so that ..." part. MUST be a real business outcome, not a restatement of "iWant"'),
|
|
637
649
|
})
|
|
638
650
|
.describe('Story header following the As a / I want / so that format'),
|
|
639
651
|
definitions: z.array(z.object({
|
|
@@ -641,22 +653,21 @@ server.registerTool('create_formatted_user_story', {
|
|
|
641
653
|
.describe('The term, module name, or feature flag being defined'),
|
|
642
654
|
description: z.string()
|
|
643
655
|
.describe('Explanation of the term'),
|
|
644
|
-
}))
|
|
645
|
-
|
|
646
|
-
.
|
|
647
|
-
.describe('Bullet checklist items for quick review sign-off — each string is one criterion'),
|
|
656
|
+
}))
|
|
657
|
+
.optional()
|
|
658
|
+
.describe('Jargon, feature flags, module names, or acronyms that need defining. Omit entirely if none apply — do not send an empty section'),
|
|
648
659
|
scenarios: z.array(z.object({
|
|
649
660
|
name: z.string()
|
|
650
661
|
.describe('Scenario name'),
|
|
651
662
|
steps: z.array(z.string())
|
|
652
663
|
.min(1)
|
|
653
|
-
.describe('Gherkin steps — each string is a full step line, e.g. "Given I am on the login page"'),
|
|
664
|
+
.describe('Gherkin steps — each string is a full step line, e.g. "Given I am on the login page". Exactly one "Then" step per scenario; split into another scenario if a second outcome is needed. Avoid vague verbs ("validates") — spell out what happens'),
|
|
654
665
|
}))
|
|
655
666
|
.min(1)
|
|
656
|
-
.describe('Gherkin scenario blocks, one per behavior
|
|
667
|
+
.describe('Gherkin scenario blocks, one per distinct behavior. Write these before acceptanceCriteria'),
|
|
657
668
|
examplesTable: z.string()
|
|
658
669
|
.optional()
|
|
659
|
-
.describe('Examples table for parameterized
|
|
670
|
+
.describe('Examples: table with real values backing a Scenario Outline for parameterized/matrix behavior (e.g. "supports various formats/roles/states"). Omit if no parameterized behavior exists'),
|
|
660
671
|
edgeCases: z.array(z.object({
|
|
661
672
|
name: z.string()
|
|
662
673
|
.describe('Edge case scenario name'),
|
|
@@ -665,13 +676,16 @@ server.registerTool('create_formatted_user_story', {
|
|
|
665
676
|
.describe('Gherkin steps for this edge case'),
|
|
666
677
|
}))
|
|
667
678
|
.optional()
|
|
668
|
-
.describe('Explicit edge case or boundary condition scenarios'),
|
|
679
|
+
.describe('Explicit edge case or boundary condition scenarios. MANDATORY — at least one error-state and one boundary-condition scenario — if the story touches validation/input handling, permissions/roles, or external data/integrations; omit otherwise'),
|
|
680
|
+
acceptanceCriteria: z.array(z.string())
|
|
681
|
+
.min(1)
|
|
682
|
+
.describe('Bullet checklist items for quick review sign-off — each string is one criterion. Every bullet MUST be traceable to a specific scenario or edge case above'),
|
|
669
683
|
references: z.string()
|
|
670
684
|
.optional()
|
|
671
685
|
.describe('Links to Axure mockups or other external references (not inline in prose)'),
|
|
672
686
|
notes: z.string()
|
|
673
687
|
.optional()
|
|
674
|
-
.describe('
|
|
688
|
+
.describe('Open questions or known constraints that do not fit other sections — do not let these hide inside acceptanceCriteria'),
|
|
675
689
|
featureId: z.string()
|
|
676
690
|
.min(5)
|
|
677
691
|
.max(9)
|
|
@@ -695,63 +709,114 @@ server.registerTool('create_formatted_user_story', {
|
|
|
695
709
|
.optional()
|
|
696
710
|
.describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
|
|
697
711
|
},
|
|
698
|
-
}, async ({ title, header, definitions,
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
for
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
712
|
+
}, async ({ title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId }) => handleCreateFormattedUserStory(tp, { title, header, definitions, scenarios, examplesTable, edgeCases, acceptanceCriteria, references, notes, featureId, releaseId, projectId, teamId, tags, teamIterationId }));
|
|
713
|
+
server.registerTool('create_formatted_feature', {
|
|
714
|
+
title: 'Create a formatted feature',
|
|
715
|
+
description: `Create a new Feature in Targetprocess with a structured, template-driven description (feature-level TDRE), assembled from discrete sections and stored as HTML.
|
|
716
|
+
Features sit above user stories — this template isn't about writing Gherkin for individual behaviors (that belongs on child stories); it's about tracking cross-cutting constraints, risks, and open questions to a testable/decided state before they get lost across many separate stories.
|
|
717
|
+
Fill in each field to this quality bar:
|
|
718
|
+
1) Header — "businessBackground" is a 1-2 sentence value statement: who benefits and why;
|
|
719
|
+
2) Definitions — cross-cutting terms used across multiple child stories, so they aren't redefined at every story level; if none apply, OMIT "definitions" entirely (do not send an empty section);
|
|
720
|
+
3) Scope & Boundaries — what this feature explicitly includes/excludes, to stop child stories drifting into adjacent features; omit if genuinely trivial;
|
|
721
|
+
4) Non-Functional Requirements ("nonFunctionalRequirements") — every NFR category (Security, Compliance, Billing, Operational, etc.) MUST be converted from prose into one row with status "Covered" (link the child story/scenario that proves it in storyOrOwner), "Gap" (no story covers it yet — storyOrOwner names who should follow up), or "Decision needed" (genuinely still open, not testable until resolved). This is the core of the template — never leave an NFR as untested prose;
|
|
722
|
+
5) Cross-Cutting Scenarios — ONLY for behavior spanning multiple child stories that wouldn't naturally sit in any one of them (e.g. tenant isolation across all stories); do not duplicate per-story Gherkin here; omit if none apply;
|
|
723
|
+
6) Child Stories ("childStories") — pull this from "get_feature_user_stories" / "get_not_covered_user_stories_in_feature" rather than retyping it; keep it as a live pointer, not a duplicate spec; normally empty when first creating the feature;
|
|
724
|
+
7) Open Questions / Risks ("openQuestions") — anything raised at feature conception that hasn't been resolved into either a Covered NFR row or a child story; this is the section most likely to get silently dropped — treat it as the running "not done yet" list until each line is promoted to a Covered NFR row;
|
|
725
|
+
8) References — mockup/spec links here, not inline in prose;
|
|
726
|
+
9) Notes — anything else that helps understand context but doesn't fit other sections;
|
|
727
|
+
CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
|
|
728
|
+
1) IF the user specified an epic by name (not ID), resolve the epic ID first;
|
|
729
|
+
2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
|
|
730
|
+
3) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
|
|
731
|
+
4) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
|
|
732
|
+
5) IF this feature already has child stories, call "get_feature_user_stories" and "get_not_covered_user_stories_in_feature" to build "childStories" instead of guessing coverage;`,
|
|
733
|
+
inputSchema: {
|
|
734
|
+
title: z.string()
|
|
735
|
+
.describe('Feature title'),
|
|
736
|
+
header: z.object({
|
|
737
|
+
featureId: z.string()
|
|
738
|
+
.optional()
|
|
739
|
+
.describe('Feature ID if already known (e.g. TP-145636), omit for new features'),
|
|
740
|
+
businessBackground: z.string()
|
|
741
|
+
.describe('1-2 sentence value statement — who benefits and why'),
|
|
742
|
+
})
|
|
743
|
+
.describe('Feature header'),
|
|
744
|
+
definitions: z.array(z.object({
|
|
745
|
+
term: z.string()
|
|
746
|
+
.describe('The term, module name, or feature flag being defined'),
|
|
747
|
+
description: z.string()
|
|
748
|
+
.describe('Explanation of the term'),
|
|
749
|
+
}))
|
|
750
|
+
.optional()
|
|
751
|
+
.describe('Cross-cutting terms used across multiple child stories, avoiding re-defining the same term at every story level. Omit entirely if none apply'),
|
|
752
|
+
scope: z.object({
|
|
753
|
+
includes: z.array(z.string())
|
|
754
|
+
.optional()
|
|
755
|
+
.describe('What this feature explicitly includes'),
|
|
756
|
+
excludes: z.array(z.string())
|
|
757
|
+
.optional()
|
|
758
|
+
.describe('What this feature explicitly excludes — prevents child stories drifting into adjacent features'),
|
|
759
|
+
})
|
|
760
|
+
.optional()
|
|
761
|
+
.describe('Scope & Boundaries. Omit if genuinely trivial'),
|
|
762
|
+
nonFunctionalRequirements: z.array(z.object({
|
|
763
|
+
area: z.string()
|
|
764
|
+
.describe('NFR category, e.g. Security, Compliance, Billing, Operational'),
|
|
765
|
+
requirement: z.string()
|
|
766
|
+
.describe('The requirement, stated so it can be judged Covered/Gap/Decision needed'),
|
|
767
|
+
status: z.enum(["Covered", "Gap", "Decision needed"])
|
|
768
|
+
.describe('Covered = a child story/scenario proves it; Gap = no story covers it yet; Decision needed = still open — not testable until resolved'),
|
|
769
|
+
storyOrOwner: z.string()
|
|
770
|
+
.describe('If Covered, the child story ID/scenario that proves it; if Gap or Decision needed, who owns the follow-up (e.g. "Needs legal/BA follow-up")'),
|
|
771
|
+
}))
|
|
772
|
+
.min(1)
|
|
773
|
+
.describe('Every NFR category converted from prose into a testable/decided row — the core of this template. Do not leave requirements as untested prose'),
|
|
774
|
+
crossCuttingScenarios: z.array(z.object({
|
|
775
|
+
name: z.string()
|
|
776
|
+
.describe('Scenario name'),
|
|
777
|
+
steps: z.array(z.string())
|
|
778
|
+
.min(1)
|
|
779
|
+
.describe('Gherkin steps — each string is a full step line'),
|
|
780
|
+
}))
|
|
781
|
+
.optional()
|
|
782
|
+
.describe('Only for behavior spanning multiple child stories that would not naturally sit in any single one of them. Do not duplicate per-story Gherkin here; omit if none apply'),
|
|
783
|
+
childStories: z.array(z.object({
|
|
784
|
+
id: z.string()
|
|
785
|
+
.describe('Child story ID (e.g. 145789)'),
|
|
786
|
+
name: z.string()
|
|
787
|
+
.describe('Child story title'),
|
|
788
|
+
covered: z.boolean()
|
|
789
|
+
.describe('Whether this story is covered by tests'),
|
|
790
|
+
}))
|
|
791
|
+
.optional()
|
|
792
|
+
.describe('Pull this from "get_feature_user_stories" / "get_not_covered_user_stories_in_feature" rather than retyping it — a live pointer, not a duplicate spec. Normally empty when first creating the feature'),
|
|
793
|
+
openQuestions: z.array(z.string())
|
|
794
|
+
.optional()
|
|
795
|
+
.describe('Anything raised at feature conception not yet resolved into a Covered NFR row or a child story — the running "not done yet" list'),
|
|
796
|
+
references: z.string()
|
|
797
|
+
.optional()
|
|
798
|
+
.describe('Links to specs/mockups (not inline in prose)'),
|
|
799
|
+
notes: z.string()
|
|
800
|
+
.optional()
|
|
801
|
+
.describe('Anything else that helps understand context but does not fit other sections'),
|
|
802
|
+
epicId: z.string()
|
|
803
|
+
.min(5)
|
|
804
|
+
.max(9)
|
|
805
|
+
.optional()
|
|
806
|
+
.describe('Optional Epic ID to link this feature to (e.g. 145636)'),
|
|
807
|
+
releaseId: z.string()
|
|
808
|
+
.min(5)
|
|
809
|
+
.max(9)
|
|
810
|
+
.optional()
|
|
811
|
+
.describe('Optional Release ID to link this feature to (e.g. 145200)'),
|
|
812
|
+
projectId: z.string()
|
|
813
|
+
.optional()
|
|
814
|
+
.describe('Optional Project ID — defaults to TP_PROJECT_ID from config'),
|
|
815
|
+
teamId: z.string()
|
|
816
|
+
.optional()
|
|
817
|
+
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
818
|
+
},
|
|
819
|
+
}, async ({ title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId }) => handleCreateFormattedFeature(tp, { title, header, definitions, scope, nonFunctionalRequirements, crossCuttingScenarios, childStories, openQuestions, references, notes, epicId, releaseId, projectId, teamId }));
|
|
755
820
|
server.registerTool('create_feature', {
|
|
756
821
|
title: 'Create a new feature',
|
|
757
822
|
description: `Create a new Feature in Targetprocess.`,
|
|
@@ -779,6 +844,54 @@ server.registerTool('create_feature', {
|
|
|
779
844
|
.describe('Optional Team ID — defaults to TP_TEAM_ID from config'),
|
|
780
845
|
},
|
|
781
846
|
}, async ({ title, description, epicId, releaseId, projectId, teamId }) => handleCreateFeature(tp, { title, description, epicId, releaseId, projectId, teamId }));
|
|
847
|
+
server.registerTool('update_feature', {
|
|
848
|
+
title: 'Update a feature card',
|
|
849
|
+
description: `Update a feature card with data provided from user input.
|
|
850
|
+
NOTE: pass only the fields that user wants to update.
|
|
851
|
+
CRITICAL WORKFLOW: Before calling this tool, you MUST follow these steps:
|
|
852
|
+
1) IF the user specified an epic by name (not ID), resolve the epic ID first;
|
|
853
|
+
2) IF the user specified a release by name (not ID), call "get_current_releases" to resolve the release ID;
|
|
854
|
+
3) IF the user specified a team by name (not ID), call "get_teams" to find the matching team and use its ID as teamId;
|
|
855
|
+
4) IF the user specified a project by name (not ID), call "get_projects" to find the matching project and use its ID as projectId;
|
|
856
|
+
5) IF the user specified a sprint/iteration by name, call "get_team_iterations" to find the matching iteration and use its ID as teamIterationId;`,
|
|
857
|
+
inputSchema: {
|
|
858
|
+
id: z.string()
|
|
859
|
+
.min(5)
|
|
860
|
+
.max(9)
|
|
861
|
+
.describe('Feature card ID (e.g. 145636)'),
|
|
862
|
+
title: z.string()
|
|
863
|
+
.optional()
|
|
864
|
+
.describe('Updated feature title'),
|
|
865
|
+
description: z.string()
|
|
866
|
+
.optional()
|
|
867
|
+
.describe('Updated feature description (format as HTML)'),
|
|
868
|
+
epicId: z.string()
|
|
869
|
+
.min(5)
|
|
870
|
+
.max(9)
|
|
871
|
+
.optional()
|
|
872
|
+
.describe('Optional Epic ID — moves this feature to the specified epic'),
|
|
873
|
+
releaseId: z.string()
|
|
874
|
+
.min(5)
|
|
875
|
+
.max(9)
|
|
876
|
+
.optional()
|
|
877
|
+
.describe('Optional Release ID to link this feature to'),
|
|
878
|
+
projectId: z.string()
|
|
879
|
+
.optional()
|
|
880
|
+
.describe('Optional Project ID — if user gave a project name, resolve it via "get_projects" first'),
|
|
881
|
+
teamId: z.string()
|
|
882
|
+
.optional()
|
|
883
|
+
.describe('Optional Team ID — if user gave a team name, resolve it via "get_teams" first'),
|
|
884
|
+
entityStateId: z.string()
|
|
885
|
+
.optional()
|
|
886
|
+
.describe('Optional Entity State ID — ask the user for the exact ID if given a state name; no dedicated feature-workflow lookup tool exists yet'),
|
|
887
|
+
tags: z.string()
|
|
888
|
+
.optional()
|
|
889
|
+
.describe('Optional comma-separated tags to apply, e.g. "regression, mobile"'),
|
|
890
|
+
teamIterationId: z.string()
|
|
891
|
+
.optional()
|
|
892
|
+
.describe('Optional Team Iteration (sprint) ID — resolve it via "get_team_iterations" first'),
|
|
893
|
+
},
|
|
894
|
+
}, async ({ id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }) => handleUpdateFeature(tp, { id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }));
|
|
782
895
|
server.registerTool('create_epic', {
|
|
783
896
|
title: 'Create a new epic',
|
|
784
897
|
description: `Create a new Epic in Targetprocess.`,
|
|
@@ -976,6 +1089,16 @@ server.registerTool('get_feature_user_stories', {
|
|
|
976
1089
|
.describe('TP feature ID (e.g. 145636)'),
|
|
977
1090
|
},
|
|
978
1091
|
}, async ({ id }) => handleGetFeatureUserStories(tp, id));
|
|
1092
|
+
server.registerTool('get_feature_content', {
|
|
1093
|
+
title: 'Get TP feature content',
|
|
1094
|
+
description: 'Get a Targetprocess Feature by ID, including description, state, and progress',
|
|
1095
|
+
inputSchema: {
|
|
1096
|
+
id: z.string()
|
|
1097
|
+
.min(5)
|
|
1098
|
+
.max(9)
|
|
1099
|
+
.describe('TP feature ID (e.g. 145636)'),
|
|
1100
|
+
},
|
|
1101
|
+
}, async ({ id }) => handleGetFeatureContent(tp, id));
|
|
979
1102
|
server.registerTool('get_user_story_bugs', {
|
|
980
1103
|
title: 'Get user story bugs',
|
|
981
1104
|
description: 'Get bugs linked to a TP user story by its ID',
|
package/build/tp.js
CHANGED
|
@@ -409,6 +409,31 @@ export class TpClient {
|
|
|
409
409
|
param: { "format": "json" },
|
|
410
410
|
}, feature);
|
|
411
411
|
}
|
|
412
|
+
async updateFeature({ id, title, description, epicId, releaseId, projectId, teamId, entityStateId, tags, teamIterationId }) {
|
|
413
|
+
const feature = { "Id": id };
|
|
414
|
+
if (title)
|
|
415
|
+
feature["Name"] = title;
|
|
416
|
+
if (description)
|
|
417
|
+
feature["Description"] = description;
|
|
418
|
+
if (epicId)
|
|
419
|
+
feature["Epic"] = { "Id": epicId };
|
|
420
|
+
if (releaseId)
|
|
421
|
+
feature["Release"] = { "Id": releaseId };
|
|
422
|
+
if (projectId)
|
|
423
|
+
feature["Project"] = { "Id": projectId };
|
|
424
|
+
if (teamId)
|
|
425
|
+
feature["assignedTeams"] = [{ "team": { "id": teamId } }];
|
|
426
|
+
if (entityStateId)
|
|
427
|
+
feature["EntityState"] = { "Id": entityStateId };
|
|
428
|
+
if (tags)
|
|
429
|
+
feature["Tags"] = tags;
|
|
430
|
+
if (teamIterationId)
|
|
431
|
+
feature["TeamIteration"] = { "Id": teamIterationId };
|
|
432
|
+
return this.post({
|
|
433
|
+
pathParam: ["Features"],
|
|
434
|
+
param: { "format": "json" },
|
|
435
|
+
}, feature);
|
|
436
|
+
}
|
|
412
437
|
async createBugBasedOnUserStory(title, userStoryId, bugContent) {
|
|
413
438
|
const bug = {
|
|
414
439
|
"Name": title,
|