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 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.string().optional().describe('Description of the initiative (markdown)'),
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.string().optional().describe('Updated description (markdown)'),
232
- notes: z.string().optional().describe('Updated internal notes (markdown)'),
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: 'Description of the initiative (markdown)',
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: { type: 'string', description: 'Updated description (markdown)' },
1020
- notes: { type: 'string', description: 'Updated internal notes (markdown)' },
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
- // Add hint if no plan detected in description
1996
- if (!hasPlan(workItem.description)) {
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
- const workItem = await this.client.updateWorkProgress(parsed.work_item_id, parsed.description, teamSlug);
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolfpack-mcp",
3
- "version": "1.0.61",
3
+ "version": "1.0.63",
4
4
  "description": "MCP server for Wolfpack AI-enhanced software delivery tools",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",