wolfpack-mcp 1.0.61 → 1.0.63
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/client.js +11 -0
- package/dist/index.js +109 -30
- package/dist/workItemReminders.js +41 -0
- package/dist/workItemReminders.test.js +52 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -60,6 +60,17 @@ export class WolfpackClient {
|
|
|
60
60
|
const response = await this.api.get('/teams');
|
|
61
61
|
return response.teams;
|
|
62
62
|
}
|
|
63
|
+
// Search methods
|
|
64
|
+
async search(options) {
|
|
65
|
+
const params = new URLSearchParams({ q: options.query });
|
|
66
|
+
if (options.teamSlug)
|
|
67
|
+
params.append('teamSlug', options.teamSlug);
|
|
68
|
+
if (options.includeInactive)
|
|
69
|
+
params.append('includeInactive', 'true');
|
|
70
|
+
if (options.exactOnly)
|
|
71
|
+
params.append('exactOnly', 'true');
|
|
72
|
+
return this.api.get(`/search?${params.toString()}`);
|
|
73
|
+
}
|
|
63
74
|
// Team Member methods
|
|
64
75
|
async listTeamMembers(teamSlug) {
|
|
65
76
|
const params = new URLSearchParams();
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createRequire } from 'module';
|
|
|
7
7
|
import { readFile, stat } from 'fs/promises';
|
|
8
8
|
import { basename, extname, resolve } from 'path';
|
|
9
9
|
import { WolfpackClient } from './client.js';
|
|
10
|
+
import { allTasksChecked, getWorkItemReminders } from './workItemReminders.js';
|
|
10
11
|
import { validateConfig, config } from './config.js';
|
|
11
12
|
import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
12
13
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
@@ -220,7 +221,14 @@ const CreateRadarItemSchema = z.object({
|
|
|
220
221
|
.optional()
|
|
221
222
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
222
223
|
title: z.string().describe('Title of the radar/initiative item'),
|
|
223
|
-
description: z
|
|
224
|
+
description: z
|
|
225
|
+
.string()
|
|
226
|
+
.optional()
|
|
227
|
+
.describe('Short summary shown on the roadmap card (1-2 sentences). Put extended detail in notes, not here.'),
|
|
228
|
+
notes: z
|
|
229
|
+
.string()
|
|
230
|
+
.optional()
|
|
231
|
+
.describe("Extended details shown on the initiative's Notes page (markdown). Use this for long-form content."),
|
|
224
232
|
stage: z
|
|
225
233
|
.enum(['pending', 'now', 'next', 'later', 'completed'])
|
|
226
234
|
.describe('Stage: "pending" (not started), "now" (current sprint), "next" (upcoming), "later" (future), "completed"'),
|
|
@@ -228,8 +236,11 @@ const CreateRadarItemSchema = z.object({
|
|
|
228
236
|
const UpdateRadarItemSchema = z.object({
|
|
229
237
|
item_id: z.string().describe('The refId (number) of the radar item to update'),
|
|
230
238
|
title: z.string().optional().describe('Updated title'),
|
|
231
|
-
description: z
|
|
232
|
-
|
|
239
|
+
description: z
|
|
240
|
+
.string()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe('Updated short summary (1-2 sentences; extended detail belongs in notes)'),
|
|
243
|
+
notes: z.string().optional().describe('Updated Notes page content (markdown)'),
|
|
233
244
|
stage: z
|
|
234
245
|
.enum(['pending', 'now', 'next', 'later', 'completed'])
|
|
235
246
|
.optional()
|
|
@@ -346,6 +357,22 @@ const UpdateIssueSchema = z.object({
|
|
|
346
357
|
.optional()
|
|
347
358
|
.describe('Project slug (required for multi-project users, use list_projects to get slugs)'),
|
|
348
359
|
});
|
|
360
|
+
// Search schema
|
|
361
|
+
const SearchSchema = z.object({
|
|
362
|
+
query: z.string().describe('Search query (websearch syntax: quoted phrases, -exclusions)'),
|
|
363
|
+
project_slug: z
|
|
364
|
+
.string()
|
|
365
|
+
.optional()
|
|
366
|
+
.describe('Project slug; omit to search all accessible projects'),
|
|
367
|
+
include_inactive: z
|
|
368
|
+
.boolean()
|
|
369
|
+
.optional()
|
|
370
|
+
.describe('Include finished/archived items (default false)'),
|
|
371
|
+
exact_only: z
|
|
372
|
+
.boolean()
|
|
373
|
+
.optional()
|
|
374
|
+
.describe('Only exact full-text matches, skipping semantic results (default false)'),
|
|
375
|
+
});
|
|
349
376
|
// Wiki Page schemas
|
|
350
377
|
const ListWikiPagesSchema = z.object({
|
|
351
378
|
project_slug: z.string().optional().describe('Project slug to filter wiki pages'),
|
|
@@ -628,18 +655,6 @@ function stripUuids(obj) {
|
|
|
628
655
|
}
|
|
629
656
|
return result;
|
|
630
657
|
}
|
|
631
|
-
// Helper to detect if a work item description contains a plan
|
|
632
|
-
function hasPlan(description) {
|
|
633
|
-
if (!description)
|
|
634
|
-
return false;
|
|
635
|
-
// Check for markdown checklist items (plan indicators)
|
|
636
|
-
if (/- \[[ x]\]/.test(description))
|
|
637
|
-
return true;
|
|
638
|
-
// Check for --- separator with content after (agent-added section)
|
|
639
|
-
if (/\n---\n/.test(description))
|
|
640
|
-
return true;
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
658
|
class WolfpackMCPServer {
|
|
644
659
|
server;
|
|
645
660
|
client;
|
|
@@ -681,6 +696,36 @@ class WolfpackMCPServer {
|
|
|
681
696
|
properties: {},
|
|
682
697
|
},
|
|
683
698
|
},
|
|
699
|
+
// Search tool
|
|
700
|
+
{
|
|
701
|
+
name: 'search',
|
|
702
|
+
description: 'Ranked full-text search across project content: wiki pages, work items, cases, journal entries and issues. ' +
|
|
703
|
+
'Title matches outrank body matches; results include a snippet, entity type, and refId/slug for follow-up calls ' +
|
|
704
|
+
'(get_work_item, get_issue, get_wiki_page, ...). ' +
|
|
705
|
+
'Use this to find existing content before creating new items, or when the user asks to "find" or "look up" something without saying where it lives.',
|
|
706
|
+
inputSchema: {
|
|
707
|
+
type: 'object',
|
|
708
|
+
properties: {
|
|
709
|
+
query: {
|
|
710
|
+
type: 'string',
|
|
711
|
+
description: 'Search query (websearch syntax: quoted phrases, -exclusions)',
|
|
712
|
+
},
|
|
713
|
+
project_slug: {
|
|
714
|
+
type: 'string',
|
|
715
|
+
description: 'Project slug; omit to search all accessible projects',
|
|
716
|
+
},
|
|
717
|
+
include_inactive: {
|
|
718
|
+
type: 'boolean',
|
|
719
|
+
description: 'Include finished/archived items (default false)',
|
|
720
|
+
},
|
|
721
|
+
exact_only: {
|
|
722
|
+
type: 'boolean',
|
|
723
|
+
description: 'Only exact full-text matches, skipping semantic results (default false)',
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
required: ['query'],
|
|
727
|
+
},
|
|
728
|
+
},
|
|
684
729
|
// Work Item tools
|
|
685
730
|
{
|
|
686
731
|
name: 'list_work_items',
|
|
@@ -777,6 +822,7 @@ class WolfpackMCPServer {
|
|
|
777
822
|
{
|
|
778
823
|
name: 'update_work_progress',
|
|
779
824
|
description: 'Update work item description/notes with your progress. WORKFLOW: 1) get_work_item to read current notes 2) Modify the markdown description with new findings/progress 3) Call this with the complete updated description. The full description replaces the existing one. ' +
|
|
825
|
+
'STATUS: Items in "new" are auto-advanced to "doing" (updating progress means work has started). Completing the work is NOT auto-detected: when done, set status to "review" via update_work_item and add a completion comment. ' +
|
|
780
826
|
'CRITICAL: NEVER overwrite or remove the original description text. Preserve all existing content exactly as-is. Append your plan below a "---" separator. You may only modify content you previously added (your plan section). ' +
|
|
781
827
|
'BEST PRACTICE: Append a plan with markdown checkboxes (- [ ] task). Check off completed tasks (- [x] task) as you progress. Include sections for: Plan (checklist), Approach (strategy), and Notes (discoveries/decisions). ' +
|
|
782
828
|
CONTENT_LINKING_HELP,
|
|
@@ -982,6 +1028,7 @@ class WolfpackMCPServer {
|
|
|
982
1028
|
{
|
|
983
1029
|
name: 'create_radar_item',
|
|
984
1030
|
description: 'Create a new radar/initiative item in your current project. Requires mcp:radar:create permission. ' +
|
|
1031
|
+
'Keep "description" to a short summary; put extended detail in "notes". ' +
|
|
985
1032
|
CONTENT_LINKING_HELP,
|
|
986
1033
|
inputSchema: {
|
|
987
1034
|
type: 'object',
|
|
@@ -993,7 +1040,11 @@ class WolfpackMCPServer {
|
|
|
993
1040
|
title: { type: 'string', description: 'Title of the radar/initiative item' },
|
|
994
1041
|
description: {
|
|
995
1042
|
type: 'string',
|
|
996
|
-
description: '
|
|
1043
|
+
description: 'Short summary shown on the roadmap card (1-2 sentences). Put extended detail in notes, not here.',
|
|
1044
|
+
},
|
|
1045
|
+
notes: {
|
|
1046
|
+
type: 'string',
|
|
1047
|
+
description: "Extended details shown on the initiative's Notes page (markdown). Use this for long-form content.",
|
|
997
1048
|
},
|
|
998
1049
|
stage: {
|
|
999
1050
|
type: 'string',
|
|
@@ -1016,8 +1067,11 @@ class WolfpackMCPServer {
|
|
|
1016
1067
|
description: 'The refId (number) of the radar item to update',
|
|
1017
1068
|
},
|
|
1018
1069
|
title: { type: 'string', description: 'Updated title' },
|
|
1019
|
-
description: {
|
|
1020
|
-
|
|
1070
|
+
description: {
|
|
1071
|
+
type: 'string',
|
|
1072
|
+
description: 'Updated short summary (1-2 sentences; extended detail belongs in notes)',
|
|
1073
|
+
},
|
|
1074
|
+
notes: { type: 'string', description: 'Updated Notes page content (markdown)' },
|
|
1021
1075
|
stage: {
|
|
1022
1076
|
type: 'string',
|
|
1023
1077
|
enum: ['pending', 'now', 'next', 'later', 'completed'],
|
|
@@ -1954,6 +2008,22 @@ class WolfpackMCPServer {
|
|
|
1954
2008
|
],
|
|
1955
2009
|
};
|
|
1956
2010
|
}
|
|
2011
|
+
// Search handler
|
|
2012
|
+
case 'search': {
|
|
2013
|
+
const parsed = SearchSchema.parse(args);
|
|
2014
|
+
const results = await this.client.search({
|
|
2015
|
+
query: parsed.query,
|
|
2016
|
+
teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
|
|
2017
|
+
includeInactive: parsed.include_inactive,
|
|
2018
|
+
exactOnly: parsed.exact_only,
|
|
2019
|
+
});
|
|
2020
|
+
const text = results.length === 0
|
|
2021
|
+
? 'No results found.'
|
|
2022
|
+
: JSON.stringify(stripUuids(results), null, 2);
|
|
2023
|
+
return {
|
|
2024
|
+
content: [{ type: 'text', text }],
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
1957
2027
|
// Work Item handlers
|
|
1958
2028
|
case 'list_work_items': {
|
|
1959
2029
|
const parsed = ListWorkItemsSchema.parse(args);
|
|
@@ -1992,11 +2062,9 @@ class WolfpackMCPServer {
|
|
|
1992
2062
|
const workItem = await this.client.getWorkItem(parsed.work_item_id, parsed.project_slug || this.client.getProjectSlug() || undefined);
|
|
1993
2063
|
if (workItem) {
|
|
1994
2064
|
let text = JSON.stringify(stripUuids(workItem), null, 2);
|
|
1995
|
-
|
|
1996
|
-
if (
|
|
1997
|
-
text =
|
|
1998
|
-
'REMINDER: This work item has no plan. Before starting work, use update_work_progress to add a plan below a "---" separator. Preserve the original description and append your plan with markdown checkboxes (- [ ] task).\n\n' +
|
|
1999
|
-
text;
|
|
2065
|
+
const reminders = getWorkItemReminders(workItem.status, workItem.description);
|
|
2066
|
+
if (reminders.length > 0) {
|
|
2067
|
+
text = `${reminders.join('\n\n')}\n\n${text}`;
|
|
2000
2068
|
}
|
|
2001
2069
|
return {
|
|
2002
2070
|
content: [{ type: 'text', text }],
|
|
@@ -2009,15 +2077,25 @@ class WolfpackMCPServer {
|
|
|
2009
2077
|
case 'update_work_progress': {
|
|
2010
2078
|
const parsed = UpdateWorkProgressSchema.parse(args);
|
|
2011
2079
|
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2012
|
-
|
|
2080
|
+
let workItem = await this.client.updateWorkProgress(parsed.work_item_id, parsed.description, teamSlug);
|
|
2013
2081
|
if (workItem) {
|
|
2082
|
+
let summary = `Updated description on: ${workItem.title}`;
|
|
2083
|
+
// Progress on a "new" item means work has started — advance it to "doing"
|
|
2084
|
+
if (workItem.status === 'new') {
|
|
2085
|
+
const advanced = await this.client.updateWorkItem(parsed.work_item_id, { status: 'doing' }, teamSlug);
|
|
2086
|
+
if (advanced) {
|
|
2087
|
+
workItem = advanced;
|
|
2088
|
+
summary += ' (status auto-advanced new → doing)';
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
let text = `${summary}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`;
|
|
2092
|
+
if (workItem.status === 'doing' && allTasksChecked(workItem.description)) {
|
|
2093
|
+
text =
|
|
2094
|
+
'REMINDER: All plan tasks are checked off. If the work is complete, set status to "review" NOW (update_work_item) and add a completion comment.\n\n' +
|
|
2095
|
+
text;
|
|
2096
|
+
}
|
|
2014
2097
|
return {
|
|
2015
|
-
content: [
|
|
2016
|
-
{
|
|
2017
|
-
type: 'text',
|
|
2018
|
-
text: `Updated description on: ${workItem.title}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
|
|
2019
|
-
},
|
|
2020
|
-
],
|
|
2098
|
+
content: [{ type: 'text', text }],
|
|
2021
2099
|
};
|
|
2022
2100
|
}
|
|
2023
2101
|
return {
|
|
@@ -2193,6 +2271,7 @@ class WolfpackMCPServer {
|
|
|
2193
2271
|
const radarItem = await this.client.createRadarItem({
|
|
2194
2272
|
title: parsed.title,
|
|
2195
2273
|
description: parsed.description,
|
|
2274
|
+
notes: parsed.notes,
|
|
2196
2275
|
stage: parsed.stage,
|
|
2197
2276
|
teamSlug: parsed.project_slug || this.client.getProjectSlug() || undefined,
|
|
2198
2277
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Workflow reminders injected into work-item tool responses. Tool descriptions document
|
|
2
|
+
// the status workflow, but agents skim them — a reminder in the response at the moment of
|
|
3
|
+
// action is what reliably changes behaviour. Reminders only fire when actionable.
|
|
4
|
+
// Detect if a work item description contains a plan
|
|
5
|
+
export function hasPlan(description) {
|
|
6
|
+
if (!description)
|
|
7
|
+
return false;
|
|
8
|
+
// Check for markdown checklist items (plan indicators)
|
|
9
|
+
if (/- \[[ x]\]/.test(description))
|
|
10
|
+
return true;
|
|
11
|
+
// Check for --- separator with content after (agent-added section)
|
|
12
|
+
if (/\n---\n/.test(description))
|
|
13
|
+
return true;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
// True when the description has a checklist and every task is checked off.
|
|
17
|
+
export function allTasksChecked(description) {
|
|
18
|
+
if (!description)
|
|
19
|
+
return false;
|
|
20
|
+
return /- \[x\]/.test(description) && !/- \[ \]/.test(description);
|
|
21
|
+
}
|
|
22
|
+
const STATUS_REMINDERS = {
|
|
23
|
+
pending: 'REMINDER: This work item is in the backlog ("pending"). Claim it with pull_work_item before starting work.',
|
|
24
|
+
new: 'REMINDER: This work item is in "new". If you are picking it up, set status to "doing" (update_work_item) ' +
|
|
25
|
+
'BEFORE starting work.',
|
|
26
|
+
doing: 'REMINDER: This work item is in "doing". The moment the work is complete, set status to "review" ' +
|
|
27
|
+
'(update_work_item) and add a completion comment — do not leave it sitting in "doing".',
|
|
28
|
+
};
|
|
29
|
+
// Reminders to prepend to a get_work_item response, most urgent first.
|
|
30
|
+
export function getWorkItemReminders(status, description) {
|
|
31
|
+
const reminders = [];
|
|
32
|
+
const statusReminder = STATUS_REMINDERS[status];
|
|
33
|
+
if (statusReminder)
|
|
34
|
+
reminders.push(statusReminder);
|
|
35
|
+
if (!hasPlan(description)) {
|
|
36
|
+
reminders.push('REMINDER: This work item has no plan. Before starting work, use update_work_progress to add a plan ' +
|
|
37
|
+
'below a "---" separator. Preserve the original description and append your plan with markdown ' +
|
|
38
|
+
'checkboxes (- [ ] task).');
|
|
39
|
+
}
|
|
40
|
+
return reminders;
|
|
41
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { hasPlan, allTasksChecked, getWorkItemReminders } from './workItemReminders.js';
|
|
3
|
+
const PLAN = 'Original text\n\n---\n\n## Plan\n\n- [x] first task\n- [ ] second task';
|
|
4
|
+
const DONE_PLAN = 'Original text\n\n---\n\n## Plan\n\n- [x] first task\n- [x] second task';
|
|
5
|
+
describe('hasPlan', () => {
|
|
6
|
+
it('detects markdown checklists', () => {
|
|
7
|
+
expect(hasPlan(PLAN)).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
it('detects a --- separator section', () => {
|
|
10
|
+
expect(hasPlan('Original text\n---\nAgent notes')).toBe(true);
|
|
11
|
+
});
|
|
12
|
+
it.each([null, undefined, '', 'Just a plain description'])('rejects %j', (description) => {
|
|
13
|
+
expect(hasPlan(description)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('allTasksChecked', () => {
|
|
17
|
+
it('is true when every task is checked', () => {
|
|
18
|
+
expect(allTasksChecked(DONE_PLAN)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
it('is false while unchecked tasks remain', () => {
|
|
21
|
+
expect(allTasksChecked(PLAN)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it.each([null, undefined, '', 'No checklist here'])('is false without a checklist (%j)', (description) => {
|
|
24
|
+
expect(allTasksChecked(description)).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe('getWorkItemReminders', () => {
|
|
28
|
+
it('tells agents to pull pending items', () => {
|
|
29
|
+
expect(getWorkItemReminders('pending', PLAN)).toEqual([
|
|
30
|
+
expect.stringContaining('pull_work_item'),
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
it('tells agents to move new items to doing', () => {
|
|
34
|
+
expect(getWorkItemReminders('new', PLAN)).toEqual([
|
|
35
|
+
expect.stringContaining('set status to "doing"'),
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
it('tells agents to move doing items to review when complete', () => {
|
|
39
|
+
expect(getWorkItemReminders('doing', PLAN)).toEqual([
|
|
40
|
+
expect.stringContaining('set status to "review"'),
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
it('adds the no-plan reminder when the description has no plan', () => {
|
|
44
|
+
const reminders = getWorkItemReminders('new', 'Just a description');
|
|
45
|
+
expect(reminders).toHaveLength(2);
|
|
46
|
+
expect(reminders[1]).toContain('no plan');
|
|
47
|
+
});
|
|
48
|
+
it('stays quiet for statuses with no required action', () => {
|
|
49
|
+
expect(getWorkItemReminders('review', PLAN)).toEqual([]);
|
|
50
|
+
expect(getWorkItemReminders('completed', PLAN)).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
});
|