@damper/mcp 0.3.22 β 0.4.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/formatters.d.ts +1 -0
- package/dist/formatters.js +9 -1
- package/dist/index.js +40 -7
- package/package.json +1 -1
- package/dist/formatters.test.d.ts +0 -4
- package/dist/formatters.test.js +0 -126
package/dist/formatters.d.ts
CHANGED
package/dist/formatters.js
CHANGED
|
@@ -16,6 +16,14 @@ export function formatStartTaskResponse(result) {
|
|
|
16
16
|
lines.push(`β’ ${rule}`);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
// Completion checklist - shown after critical rules so agent knows upfront
|
|
20
|
+
if (result.completionChecklist && result.completionChecklist.length > 0) {
|
|
21
|
+
lines.push('\nπ **COMPLETION CHECKLIST (required for complete_task):**');
|
|
22
|
+
lines.push('You MUST verify each item and pass them as `confirmations` when calling `complete_task`:');
|
|
23
|
+
for (const item of result.completionChecklist) {
|
|
24
|
+
lines.push(` β‘ ${item}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
19
27
|
// Context section - emphatic about reading it first
|
|
20
28
|
if (result.context) {
|
|
21
29
|
if (result.context.isEmpty) {
|
|
@@ -43,7 +51,7 @@ export function formatStartTaskResponse(result) {
|
|
|
43
51
|
lines.push('1. `add_note`: "Session started: <goal>"');
|
|
44
52
|
lines.push('2. Work: use `add_commit` for commits, `add_note` for decisions');
|
|
45
53
|
lines.push('3. `add_note`: "Session end: <summary>"');
|
|
46
|
-
lines.push('4. `complete_task` or `abandon_task`');
|
|
54
|
+
lines.push('4. `complete_task` (with `confirmations` if checklist exists) or `abandon_task`');
|
|
47
55
|
return lines.join('\n');
|
|
48
56
|
}
|
|
49
57
|
/**
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,12 @@ async function api(method, path, body) {
|
|
|
28
28
|
lockErr.lockInfo = err;
|
|
29
29
|
throw lockErr;
|
|
30
30
|
}
|
|
31
|
+
// Preserve checklist info for 400 checklist failures
|
|
32
|
+
if (res.status === 400 && err.missingItems) {
|
|
33
|
+
const checklistErr = new Error(err.error || `HTTP ${res.status}`);
|
|
34
|
+
checklistErr.checklistInfo = err;
|
|
35
|
+
throw checklistErr;
|
|
36
|
+
}
|
|
31
37
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
32
38
|
}
|
|
33
39
|
return res.json();
|
|
@@ -35,7 +41,7 @@ async function api(method, path, body) {
|
|
|
35
41
|
// Server
|
|
36
42
|
const server = new McpServer({
|
|
37
43
|
name: 'damper',
|
|
38
|
-
version: '0.
|
|
44
|
+
version: '0.4.0',
|
|
39
45
|
});
|
|
40
46
|
// Output schemas
|
|
41
47
|
const SubtaskProgressSchema = z.object({
|
|
@@ -403,6 +409,7 @@ server.registerTool('start_task', {
|
|
|
403
409
|
lockedBy: z.string().optional(),
|
|
404
410
|
lockedAt: z.string().optional(),
|
|
405
411
|
context: ContextIndexSchema.optional(),
|
|
412
|
+
completionChecklist: z.array(z.string()).optional(),
|
|
406
413
|
}),
|
|
407
414
|
annotations: {
|
|
408
415
|
readOnlyHint: false,
|
|
@@ -587,6 +594,9 @@ server.registerTool('complete_task', {
|
|
|
587
594
|
'**Before calling:**\n' +
|
|
588
595
|
'1. Push all commits\n' +
|
|
589
596
|
'2. Check if project context docs need updating\n\n' +
|
|
597
|
+
'**Completion checklist:** If the project has a completion checklist (shown in `start_task` response), ' +
|
|
598
|
+
'you MUST pass `confirmations` β an array echoing back each checklist item you\'ve verified. ' +
|
|
599
|
+
'The server will reject completion if any items are missing.\n\n' +
|
|
590
600
|
'**Commits:** Pass commits array to log them at completion (convenience for final commits).\n\n' +
|
|
591
601
|
'Returns documentation update suggestions.',
|
|
592
602
|
inputSchema: z.object({
|
|
@@ -596,6 +606,7 @@ server.registerTool('complete_task', {
|
|
|
596
606
|
hash: z.string().describe('Commit hash (short or full)'),
|
|
597
607
|
message: z.string().describe('Commit message'),
|
|
598
608
|
})).optional().describe('Optional: commits to log at completion'),
|
|
609
|
+
confirmations: z.array(z.string()).optional().describe('Completion checklist confirmations β echo back each item from the checklist shown in start_task'),
|
|
599
610
|
}),
|
|
600
611
|
outputSchema: z.object({
|
|
601
612
|
id: z.string(),
|
|
@@ -608,12 +619,33 @@ server.registerTool('complete_task', {
|
|
|
608
619
|
idempotentHint: true,
|
|
609
620
|
openWorldHint: false,
|
|
610
621
|
},
|
|
611
|
-
}, async ({ taskId, summary, commits }) => {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
622
|
+
}, async ({ taskId, summary, commits, confirmations }) => {
|
|
623
|
+
try {
|
|
624
|
+
const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary, commits, confirmations });
|
|
625
|
+
return {
|
|
626
|
+
content: [{ type: 'text', text: formatCompleteTaskResponse(result) }],
|
|
627
|
+
structuredContent: result,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
catch (err) {
|
|
631
|
+
const error = err;
|
|
632
|
+
if (error.checklistInfo) {
|
|
633
|
+
const { missingItems, checklist } = error.checklistInfo;
|
|
634
|
+
const lines = [
|
|
635
|
+
'β Completion blocked β checklist not fully confirmed.',
|
|
636
|
+
'',
|
|
637
|
+
`**Missing ${missingItems.length} of ${checklist.length} items:**`,
|
|
638
|
+
...missingItems.map(item => ` β’ ${item}`),
|
|
639
|
+
'',
|
|
640
|
+
'Pass ALL checklist items in `confirmations` to complete the task.',
|
|
641
|
+
];
|
|
642
|
+
return {
|
|
643
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
644
|
+
isError: true,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
throw error;
|
|
648
|
+
}
|
|
617
649
|
});
|
|
618
650
|
// Tool: Abandon task
|
|
619
651
|
server.registerTool('abandon_task', {
|
|
@@ -724,6 +756,7 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
|
|
|
724
756
|
### At Session End (MANDATORY)
|
|
725
757
|
- ALWAYS call \`complete_task\` (if done) or \`abandon_task\` (if stopping early)
|
|
726
758
|
- NEVER leave a started task without completing or abandoning it
|
|
759
|
+
- If the project has a **completion checklist** (shown in \`start_task\` response), you MUST pass all items as \`confirmations\` when calling \`complete_task\`
|
|
727
760
|
- If you learned something about the codebase, consider updating project context
|
|
728
761
|
|
|
729
762
|
### Why This Matters
|
package/package.json
CHANGED
package/dist/formatters.test.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for MCP response formatters
|
|
3
|
-
*/
|
|
4
|
-
import { describe, it, expect } from 'bun:test';
|
|
5
|
-
import { formatStartTaskResponse, formatCompleteTaskResponse, formatAbandonTaskResponse, } from './formatters';
|
|
6
|
-
describe('formatStartTaskResponse', () => {
|
|
7
|
-
it('includes workflow steps', () => {
|
|
8
|
-
const result = formatStartTaskResponse({
|
|
9
|
-
id: 'TASK-1',
|
|
10
|
-
status: 'in_progress',
|
|
11
|
-
message: 'Task started',
|
|
12
|
-
});
|
|
13
|
-
expect(result).toContain('**π Workflow:**');
|
|
14
|
-
expect(result).toContain('`add_note`: "Session started: <goal>"');
|
|
15
|
-
expect(result).toContain('`add_note`: "Session end: <summary, next steps>"');
|
|
16
|
-
expect(result).toContain('`complete_task` or `abandon_task`');
|
|
17
|
-
});
|
|
18
|
-
it('shows empty context hint when context is empty', () => {
|
|
19
|
-
const result = formatStartTaskResponse({
|
|
20
|
-
id: 'TASK-1',
|
|
21
|
-
status: 'in_progress',
|
|
22
|
-
message: 'Task started',
|
|
23
|
-
context: {
|
|
24
|
-
isEmpty: true,
|
|
25
|
-
index: [],
|
|
26
|
-
hint: 'No docs available yet',
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
expect(result).toContain('π No docs available yet');
|
|
30
|
-
});
|
|
31
|
-
it('lists context sections with star for relevant ones', () => {
|
|
32
|
-
const result = formatStartTaskResponse({
|
|
33
|
-
id: 'TASK-1',
|
|
34
|
-
status: 'in_progress',
|
|
35
|
-
message: 'Task started',
|
|
36
|
-
context: {
|
|
37
|
-
isEmpty: false,
|
|
38
|
-
index: [
|
|
39
|
-
{ section: 'overview', preview: 'Project overview', updatedAt: '2024-01-01' },
|
|
40
|
-
{ section: 'api', preview: 'API docs', updatedAt: '2024-01-01' },
|
|
41
|
-
{ section: 'testing', preview: 'Testing guide', updatedAt: '2024-01-01' },
|
|
42
|
-
],
|
|
43
|
-
relevantSections: ['api', 'testing'],
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
expect(result).toContain('**Project context available:**');
|
|
47
|
-
expect(result).toContain('β’ overview');
|
|
48
|
-
expect(result).toContain('β’ api β');
|
|
49
|
-
expect(result).toContain('β’ testing β');
|
|
50
|
-
expect(result).toContain('Relevant for this task: api, testing');
|
|
51
|
-
expect(result).toContain('Use get_context_section to fetch full content.');
|
|
52
|
-
});
|
|
53
|
-
it('shows basic message and task ID', () => {
|
|
54
|
-
const result = formatStartTaskResponse({
|
|
55
|
-
id: 'TASK-123',
|
|
56
|
-
status: 'in_progress',
|
|
57
|
-
message: 'Locked successfully',
|
|
58
|
-
});
|
|
59
|
-
expect(result).toContain('Started TASK-123: Locked successfully');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
describe('formatCompleteTaskResponse', () => {
|
|
63
|
-
it('shows completion message', () => {
|
|
64
|
-
const result = formatCompleteTaskResponse({
|
|
65
|
-
id: 'TASK-1',
|
|
66
|
-
status: 'done',
|
|
67
|
-
});
|
|
68
|
-
expect(result).toContain('β
Completed TASK-1');
|
|
69
|
-
});
|
|
70
|
-
it('shows documentation section with affected sections', () => {
|
|
71
|
-
const result = formatCompleteTaskResponse({
|
|
72
|
-
id: 'TASK-1',
|
|
73
|
-
status: 'done',
|
|
74
|
-
documentation: {
|
|
75
|
-
hasContext: true,
|
|
76
|
-
affectedSections: ['api', 'overview'],
|
|
77
|
-
reminder: 'Please review and update docs',
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
expect(result).toContain('**π Documentation:**');
|
|
81
|
-
expect(result).toContain('May need updates: api, overview');
|
|
82
|
-
expect(result).toContain('Please review and update docs');
|
|
83
|
-
});
|
|
84
|
-
it('skips affected sections line when empty', () => {
|
|
85
|
-
const result = formatCompleteTaskResponse({
|
|
86
|
-
id: 'TASK-1',
|
|
87
|
-
status: 'done',
|
|
88
|
-
documentation: {
|
|
89
|
-
hasContext: true,
|
|
90
|
-
affectedSections: [],
|
|
91
|
-
reminder: 'No changes needed',
|
|
92
|
-
},
|
|
93
|
-
});
|
|
94
|
-
expect(result).toContain('**π Documentation:**');
|
|
95
|
-
expect(result).not.toContain('May need updates:');
|
|
96
|
-
expect(result).toContain('No changes needed');
|
|
97
|
-
});
|
|
98
|
-
it('handles missing documentation gracefully', () => {
|
|
99
|
-
const result = formatCompleteTaskResponse({
|
|
100
|
-
id: 'TASK-1',
|
|
101
|
-
status: 'done',
|
|
102
|
-
});
|
|
103
|
-
expect(result).not.toContain('**π Documentation:**');
|
|
104
|
-
expect(result).toBe('β
Completed TASK-1');
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
describe('formatAbandonTaskResponse', () => {
|
|
108
|
-
it('shows tip when no summary provided', () => {
|
|
109
|
-
const result = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Task abandoned' }, false);
|
|
110
|
-
expect(result).toContain('βΈοΈ Abandoned TASK-1: Task abandoned');
|
|
111
|
-
expect(result).toContain('β οΈ Tip: Include a summary next time for better handoff.');
|
|
112
|
-
expect(result).toContain('π‘ Task returned to "planned". Notes preserved for next agent.');
|
|
113
|
-
});
|
|
114
|
-
it('does not show tip when summary provided', () => {
|
|
115
|
-
const result = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Task abandoned with handoff' }, true);
|
|
116
|
-
expect(result).toContain('βΈοΈ Abandoned TASK-1');
|
|
117
|
-
expect(result).not.toContain('β οΈ Tip:');
|
|
118
|
-
expect(result).toContain('π‘ Task returned to "planned"');
|
|
119
|
-
});
|
|
120
|
-
it('always shows notes preserved message', () => {
|
|
121
|
-
const withSummary = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Done' }, true);
|
|
122
|
-
const withoutSummary = formatAbandonTaskResponse({ id: 'TASK-1', status: 'planned', message: 'Done' }, false);
|
|
123
|
-
expect(withSummary).toContain('Notes preserved for next agent');
|
|
124
|
-
expect(withoutSummary).toContain('Notes preserved for next agent');
|
|
125
|
-
});
|
|
126
|
-
});
|