wolfpack-mcp 1.0.62 → 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/index.js +22 -24
- package/dist/workItemReminders.js +41 -0
- package/dist/workItemReminders.test.js +52 -0
- package/package.json +1 -1
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';
|
|
@@ -654,18 +655,6 @@ function stripUuids(obj) {
|
|
|
654
655
|
}
|
|
655
656
|
return result;
|
|
656
657
|
}
|
|
657
|
-
// Helper to detect if a work item description contains a plan
|
|
658
|
-
function hasPlan(description) {
|
|
659
|
-
if (!description)
|
|
660
|
-
return false;
|
|
661
|
-
// Check for markdown checklist items (plan indicators)
|
|
662
|
-
if (/- \[[ x]\]/.test(description))
|
|
663
|
-
return true;
|
|
664
|
-
// Check for --- separator with content after (agent-added section)
|
|
665
|
-
if (/\n---\n/.test(description))
|
|
666
|
-
return true;
|
|
667
|
-
return false;
|
|
668
|
-
}
|
|
669
658
|
class WolfpackMCPServer {
|
|
670
659
|
server;
|
|
671
660
|
client;
|
|
@@ -833,6 +822,7 @@ class WolfpackMCPServer {
|
|
|
833
822
|
{
|
|
834
823
|
name: 'update_work_progress',
|
|
835
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. ' +
|
|
836
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). ' +
|
|
837
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). ' +
|
|
838
828
|
CONTENT_LINKING_HELP,
|
|
@@ -2072,11 +2062,9 @@ class WolfpackMCPServer {
|
|
|
2072
2062
|
const workItem = await this.client.getWorkItem(parsed.work_item_id, parsed.project_slug || this.client.getProjectSlug() || undefined);
|
|
2073
2063
|
if (workItem) {
|
|
2074
2064
|
let text = JSON.stringify(stripUuids(workItem), null, 2);
|
|
2075
|
-
|
|
2076
|
-
if (
|
|
2077
|
-
text =
|
|
2078
|
-
'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' +
|
|
2079
|
-
text;
|
|
2065
|
+
const reminders = getWorkItemReminders(workItem.status, workItem.description);
|
|
2066
|
+
if (reminders.length > 0) {
|
|
2067
|
+
text = `${reminders.join('\n\n')}\n\n${text}`;
|
|
2080
2068
|
}
|
|
2081
2069
|
return {
|
|
2082
2070
|
content: [{ type: 'text', text }],
|
|
@@ -2089,15 +2077,25 @@ class WolfpackMCPServer {
|
|
|
2089
2077
|
case 'update_work_progress': {
|
|
2090
2078
|
const parsed = UpdateWorkProgressSchema.parse(args);
|
|
2091
2079
|
const teamSlug = parsed.project_slug || this.client.getProjectSlug() || undefined;
|
|
2092
|
-
|
|
2080
|
+
let workItem = await this.client.updateWorkProgress(parsed.work_item_id, parsed.description, teamSlug);
|
|
2093
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
|
+
}
|
|
2094
2097
|
return {
|
|
2095
|
-
content: [
|
|
2096
|
-
{
|
|
2097
|
-
type: 'text',
|
|
2098
|
-
text: `Updated description on: ${workItem.title}\n\n${JSON.stringify(stripUuids(workItem), null, 2)}`,
|
|
2099
|
-
},
|
|
2100
|
-
],
|
|
2098
|
+
content: [{ type: 'text', text }],
|
|
2101
2099
|
};
|
|
2102
2100
|
}
|
|
2103
2101
|
return {
|
|
@@ -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
|
+
});
|