@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.
@@ -8,6 +8,7 @@ export interface StartTaskResult {
8
8
  id: string;
9
9
  status: string;
10
10
  message: string;
11
+ completionChecklist?: string[];
11
12
  context?: {
12
13
  isEmpty: boolean;
13
14
  index: Array<{
@@ -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.3.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
- const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary, commits });
613
- return {
614
- content: [{ type: 'text', text: formatCompleteTaskResponse(result) }],
615
- structuredContent: result,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/mcp",
3
- "version": "0.3.22",
3
+ "version": "0.4.0",
4
4
  "description": "MCP server for Damper task management",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {
@@ -1,4 +0,0 @@
1
- /**
2
- * Tests for MCP response formatters
3
- */
4
- export {};
@@ -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
- });