@damper/mcp 0.8.7 β†’ 0.9.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.
@@ -35,6 +35,11 @@ export interface CompleteTaskResult {
35
35
  reminder: string;
36
36
  };
37
37
  }
38
+ export interface ReviewTaskResult {
39
+ id: string;
40
+ status: string;
41
+ completionChecklist?: string[];
42
+ }
38
43
  export interface AbandonTaskResult {
39
44
  id: string;
40
45
  status: string;
@@ -49,11 +54,16 @@ export declare function formatStartTaskResponse(result: StartTaskResult): string
49
54
  * Guides the agent to propose improvements to the user with rationale.
50
55
  */
51
56
  export declare function formatSelfAssessmentActions(assessment: SelfAssessment): string;
57
+ /**
58
+ * Format review_task response.
59
+ * Shows self-assessment follow-up actions and reminds about next steps.
60
+ */
61
+ export declare function formatReviewTaskResponse(result: ReviewTaskResult, assessment: SelfAssessment): string;
52
62
  /**
53
63
  * Format complete_task response
54
64
  */
55
- export declare function formatCompleteTaskResponse(result: CompleteTaskResult, assessment?: SelfAssessment): string;
65
+ export declare function formatCompleteTaskResponse(result: CompleteTaskResult): string;
56
66
  /**
57
67
  * Format abandon_task response
58
68
  */
59
- export declare function formatAbandonTaskResponse(result: AbandonTaskResult, hasSummary: boolean, assessment?: SelfAssessment): string;
69
+ export declare function formatAbandonTaskResponse(result: AbandonTaskResult, hasSummary: boolean): string;
@@ -52,7 +52,8 @@ export function formatStartTaskResponse(result) {
52
52
  lines.push('1. `add_note`: "Session started: <goal>"');
53
53
  lines.push('2. Work: use `add_commit` for commits, `add_note` for decisions');
54
54
  lines.push('3. `add_note`: "Session end: <summary>"');
55
- lines.push('4. `complete_task` (with `confirmations` and `selfAssessment`) or `abandon_task`');
55
+ lines.push('4. `review_task` (with `selfAssessment`) β€” required self-review before completion');
56
+ lines.push('5. `complete_task` (with `confirmations`) or `abandon_task`');
56
57
  return lines.join('\n');
57
58
  }
58
59
  /**
@@ -91,31 +92,38 @@ export function formatSelfAssessmentActions(assessment) {
91
92
  }
92
93
  return lines.join('\n');
93
94
  }
95
+ /**
96
+ * Format review_task response.
97
+ * Shows self-assessment follow-up actions and reminds about next steps.
98
+ */
99
+ export function formatReviewTaskResponse(result, assessment) {
100
+ const lines = [`πŸ” Review recorded for ${result.id}.`];
101
+ // Self-assessment follow-up actions
102
+ const actions = formatSelfAssessmentActions(assessment);
103
+ if (actions) {
104
+ lines.push(actions);
105
+ }
106
+ if (!actions) {
107
+ lines.push('\nNo friction points reported β€” nice!');
108
+ }
109
+ // Remind about completion checklist
110
+ if (result.completionChecklist && result.completionChecklist.length > 0) {
111
+ lines.push('\nπŸ“‹ **Completion checklist (pass as `confirmations` in `complete_task`):**');
112
+ for (const item of result.completionChecklist) {
113
+ lines.push(` β–‘ ${item}`);
114
+ }
115
+ }
116
+ lines.push('\n**Next:** Push commits, then call `complete_task` with summary.');
117
+ return lines.join('\n');
118
+ }
94
119
  /**
95
120
  * Format complete_task response
96
121
  */
97
- export function formatCompleteTaskResponse(result, assessment) {
122
+ export function formatCompleteTaskResponse(result) {
98
123
  const statusMessage = result.status === 'done'
99
124
  ? `βœ… Task ${result.id} marked as done`
100
125
  : `βœ… Task ${result.id} moved to review`;
101
126
  const lines = [statusMessage];
102
- // Self-assessment follow-up actions (shown first if provided)
103
- if (assessment) {
104
- const actions = formatSelfAssessmentActions(assessment);
105
- if (actions) {
106
- lines.push(actions);
107
- }
108
- }
109
- if (!assessment) {
110
- // No assessment provided β€” nudge the agent to reflect
111
- lines.push('\n---');
112
- lines.push('**πŸ” Before you finish β€” quick self-assessment:**');
113
- lines.push('Reflect on your session. If you encountered any of these, propose improvements to the user:');
114
- lines.push('β€’ Missing project docs β†’ offer to call `update_context_section`');
115
- lines.push('β€’ Confusing tools β†’ offer to call `report_issue`');
116
- lines.push('β€’ Spec/process gaps β†’ offer to update docs or report');
117
- lines.push('Explain briefly why each improvement would help future tasks.');
118
- }
119
127
  if (result.documentation) {
120
128
  if (result.documentation.affectedSections?.length > 0) {
121
129
  lines.push(`\nSections that may need updates: ${result.documentation.affectedSections.join(', ')}`);
@@ -129,26 +137,11 @@ export function formatCompleteTaskResponse(result, assessment) {
129
137
  /**
130
138
  * Format abandon_task response
131
139
  */
132
- export function formatAbandonTaskResponse(result, hasSummary, assessment) {
140
+ export function formatAbandonTaskResponse(result, hasSummary) {
133
141
  const lines = [`⏸️ Abandoned ${result.id}: ${result.message}`];
134
142
  if (!hasSummary) {
135
143
  lines.push('\n⚠️ Tip: Include a summary next time for better handoff.');
136
144
  }
137
145
  lines.push('\nπŸ’‘ Task returned to "planned" (from in_progress or in_review). Notes preserved for next agent.');
138
- // Self-assessment follow-up actions
139
- if (assessment) {
140
- const actions = formatSelfAssessmentActions(assessment);
141
- if (actions) {
142
- lines.push(actions);
143
- }
144
- }
145
- if (!assessment) {
146
- lines.push('\n---');
147
- lines.push('**πŸ” Before you finish β€” quick self-assessment:**');
148
- lines.push('Something caused this task to be abandoned. Propose improvements to the user:');
149
- lines.push('β€’ Missing project docs that would have helped β†’ offer to call `update_context_section`');
150
- lines.push('β€’ Tool issues that blocked progress β†’ offer to call `report_issue`');
151
- lines.push('Explain briefly why each fix would help the next agent pick this up smoothly.');
152
- }
153
146
  return lines.join('\n');
154
147
  }
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { z } from 'zod';
5
- import { formatStartTaskResponse, formatCompleteTaskResponse, formatAbandonTaskResponse, } from './formatters.js';
5
+ import { formatStartTaskResponse, formatReviewTaskResponse, formatCompleteTaskResponse, formatAbandonTaskResponse, } from './formatters.js';
6
6
  // Config
7
7
  const API_KEY = process.env.DAMPER_API_KEY;
8
8
  const API_URL = process.env.DAMPER_API_URL || 'https://api.usedamper.com';
@@ -28,6 +28,12 @@ async function api(method, path, body) {
28
28
  lockErr.lockInfo = err;
29
29
  throw lockErr;
30
30
  }
31
+ // Preserve review-required info for 400 review gate
32
+ if (res.status === 400 && err.reviewRequired) {
33
+ const reviewErr = new Error(err.error || `HTTP ${res.status}`);
34
+ reviewErr.reviewRequired = true;
35
+ throw reviewErr;
36
+ }
31
37
  // Preserve checklist info for 400 checklist failures
32
38
  if (res.status === 400 && (err.missingItems || err.invalidEvidence)) {
33
39
  const checklistErr = new Error(err.error || `HTTP ${res.status}`);
@@ -101,6 +107,7 @@ const FeedbackSummarySchema = z.object({
101
107
  title: z.string(),
102
108
  type: z.string(),
103
109
  voterCount: z.number(),
110
+ isPublic: z.boolean(),
104
111
  linkedTaskId: z.string().nullish(),
105
112
  });
106
113
  const FeedbackDetailSchema = z.object({
@@ -110,6 +117,7 @@ const FeedbackDetailSchema = z.object({
110
117
  type: z.string(),
111
118
  status: z.string(),
112
119
  voteScore: z.number(),
120
+ isPublic: z.boolean(),
113
121
  metadata: z.unknown().optional(),
114
122
  linkedTaskId: z.string().nullish(),
115
123
  voters: z.array(z.object({ email: z.string(), plan: z.string() })),
@@ -600,6 +608,38 @@ const SelfAssessmentSchema = z.object({
600
608
  toolIssues: z.array(z.string()).optional().describe('MCP tools or workflow steps that were confusing, broken, or could be improved'),
601
609
  processGaps: z.array(z.string()).optional().describe('Gaps in specs, checklists, or conventions that caused friction or mistakes'),
602
610
  });
611
+ // Tool: Review task (self-assessment gate before completion)
612
+ server.registerTool('review_task', {
613
+ title: 'Review Task',
614
+ description: 'Self-review gate β€” MUST be called before `complete_task`.\n\n' +
615
+ 'Reflect on your session: what project knowledge was missing, what tools were confusing, ' +
616
+ 'what process gaps caused friction. This drives project improvements.\n\n' +
617
+ '**Flow:** `start_task` β†’ work β†’ `review_task` β†’ `complete_task`\n\n' +
618
+ 'If you have findings, propose improvements to the user after this call ' +
619
+ '(e.g., `update_context_section` for missing docs, `report_issue` for tool problems).',
620
+ inputSchema: z.object({
621
+ taskId: z.string(),
622
+ selfAssessment: SelfAssessmentSchema.describe('Reflect on your session: what went well, what knowledge was missing, what caused friction. ' +
623
+ 'Be honest β€” this drives project improvements. Pass empty object if nothing to report.'),
624
+ }),
625
+ outputSchema: z.object({
626
+ id: z.string(),
627
+ status: z.string(),
628
+ completionChecklist: z.array(z.string()).optional(),
629
+ }),
630
+ annotations: {
631
+ readOnlyHint: false,
632
+ destructiveHint: false,
633
+ idempotentHint: true,
634
+ openWorldHint: false,
635
+ },
636
+ }, async ({ taskId, selfAssessment }) => {
637
+ const result = await api('POST', `/api/agent/tasks/${taskId}/review`, { selfAssessment });
638
+ return {
639
+ content: [{ type: 'text', text: formatReviewTaskResponse(result, selfAssessment) }],
640
+ structuredContent: result,
641
+ };
642
+ });
603
643
  // Documentation reminder schema for complete_task response
604
644
  const DocumentationSchema = z.object({
605
645
  hasContext: z.boolean(),
@@ -611,12 +651,12 @@ server.registerTool('complete_task', {
611
651
  title: 'Complete Task',
612
652
  description: 'Mark task as ready for review (in_review). A human will verify and move it to done. ' +
613
653
  'Pass `skipReview: true` to mark as done immediately (use when the human explicitly asks).\n\n' +
654
+ '**Requires `review_task` first** β€” completion will be rejected if you haven\'t called `review_task`.\n\n' +
614
655
  '**Before calling:**\n' +
615
- '1. Push all commits\n' +
616
- '2. Check if project context docs need updating\n\n' +
617
- '**Self-assessment:** Reflect on your session β€” what project knowledge was missing, what tools were confusing, ' +
618
- 'what process gaps caused friction. If you identify improvements, propose them to the user with a brief explanation.\n\n' +
619
- '**Completion checklist:** If the project has a completion checklist (shown in `start_task` response), ' +
656
+ '1. Call `review_task` (self-assessment)\n' +
657
+ '2. Push all commits\n' +
658
+ '3. Check if project context docs need updating\n\n' +
659
+ '**Completion checklist:** If the project has a completion checklist (shown in `start_task` and `review_task` responses), ' +
620
660
  'you MUST pass `confirmations` β€” an array of `{item, evidence}` objects. Each confirmation needs the checklist item text ' +
621
661
  'and concrete evidence proving you verified it (e.g., test output, build log snippet). ' +
622
662
  'Empty evidence or evidence identical to the item text will be rejected.\n\n' +
@@ -625,8 +665,6 @@ server.registerTool('complete_task', {
625
665
  inputSchema: z.object({
626
666
  taskId: z.string(),
627
667
  summary: z.string().describe('Brief one-line summary of what was done, written for end users. Focus on the outcome, not the implementation.'),
628
- selfAssessment: SelfAssessmentSchema.optional().describe('Reflect on your session: what went well, what knowledge was missing, what caused friction. ' +
629
- 'Be honest β€” this drives project improvements.'),
630
668
  commits: z.array(z.object({
631
669
  hash: z.string().describe('Commit hash (short or full)'),
632
670
  message: z.string().describe('Commit message'),
@@ -648,43 +686,22 @@ server.registerTool('complete_task', {
648
686
  idempotentHint: true,
649
687
  openWorldHint: false,
650
688
  },
651
- }, async ({ taskId, summary, selfAssessment, commits, confirmations, skipReview }) => {
689
+ }, async ({ taskId, summary, commits, confirmations, skipReview }) => {
652
690
  try {
653
691
  const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary, commits, confirmations, skipReview });
654
- // Log self-assessment as a structured note if provided
655
- if (selfAssessment) {
656
- const assessmentLines = ['Self-Assessment:'];
657
- if (selfAssessment.missingContext?.length) {
658
- assessmentLines.push('Missing context:');
659
- for (const item of selfAssessment.missingContext)
660
- assessmentLines.push(` - ${item}`);
661
- }
662
- if (selfAssessment.toolIssues?.length) {
663
- assessmentLines.push('Tool issues:');
664
- for (const item of selfAssessment.toolIssues)
665
- assessmentLines.push(` - ${item}`);
666
- }
667
- if (selfAssessment.processGaps?.length) {
668
- assessmentLines.push('Process gaps:');
669
- for (const item of selfAssessment.processGaps)
670
- assessmentLines.push(` - ${item}`);
671
- }
672
- if (assessmentLines.length > 1) {
673
- try {
674
- await api('POST', `/api/agent/tasks/${taskId}/notes`, { note: assessmentLines.join('\n') });
675
- }
676
- catch {
677
- // Don't fail completion if note logging fails
678
- }
679
- }
680
- }
681
692
  return {
682
- content: [{ type: 'text', text: formatCompleteTaskResponse(result, selfAssessment) }],
693
+ content: [{ type: 'text', text: formatCompleteTaskResponse(result) }],
683
694
  structuredContent: result,
684
695
  };
685
696
  }
686
697
  catch (err) {
687
698
  const error = err;
699
+ if (error.reviewRequired) {
700
+ return {
701
+ content: [{ type: 'text', text: '❌ Completion blocked β€” self-review required.\n\nCall `review_task` before `complete_task`. This is a required step for reflecting on your session.' }],
702
+ isError: true,
703
+ };
704
+ }
688
705
  if (error.checklistInfo) {
689
706
  const { missingItems, invalidEvidence, checklist } = error.checklistInfo;
690
707
  const lines = [
@@ -719,14 +736,10 @@ server.registerTool('abandon_task', {
719
736
  description: 'Release lock and return task to planned status. Works for in_progress and in_review tasks.\n\n' +
720
737
  '**Before calling:**\n' +
721
738
  '1. Push any WIP commits\n\n' +
722
- '**Summary parameter:** What was done, what remains, blockers. Helps the next agent.\n\n' +
723
- '**Self-assessment:** Especially important when abandoning β€” reflect on what blocked you ' +
724
- 'or what knowledge was missing so the next agent has a smoother experience.',
739
+ '**Summary parameter:** What was done, what remains, blockers. Helps the next agent.',
725
740
  inputSchema: z.object({
726
741
  taskId: z.string(),
727
742
  summary: z.string().optional().describe('Handoff summary: what was done, what remains, any blockers'),
728
- selfAssessment: SelfAssessmentSchema.optional().describe('Reflect on your session: what went well, what knowledge was missing, what caused friction. ' +
729
- 'Be honest β€” this drives project improvements.'),
730
743
  }),
731
744
  outputSchema: z.object({
732
745
  id: z.string(),
@@ -739,37 +752,10 @@ server.registerTool('abandon_task', {
739
752
  idempotentHint: true,
740
753
  openWorldHint: false,
741
754
  },
742
- }, async ({ taskId, summary, selfAssessment }) => {
755
+ }, async ({ taskId, summary }) => {
743
756
  const result = await api('POST', `/api/agent/tasks/${taskId}/abandon`, summary ? { summary } : undefined);
744
- // Log self-assessment as a structured note if provided
745
- if (selfAssessment) {
746
- const assessmentLines = ['Self-Assessment (abandoned):'];
747
- if (selfAssessment.missingContext?.length) {
748
- assessmentLines.push('Missing context:');
749
- for (const item of selfAssessment.missingContext)
750
- assessmentLines.push(` - ${item}`);
751
- }
752
- if (selfAssessment.toolIssues?.length) {
753
- assessmentLines.push('Tool issues:');
754
- for (const item of selfAssessment.toolIssues)
755
- assessmentLines.push(` - ${item}`);
756
- }
757
- if (selfAssessment.processGaps?.length) {
758
- assessmentLines.push('Process gaps:');
759
- for (const item of selfAssessment.processGaps)
760
- assessmentLines.push(` - ${item}`);
761
- }
762
- if (assessmentLines.length > 1) {
763
- try {
764
- await api('POST', `/api/agent/tasks/${taskId}/notes`, { note: assessmentLines.join('\n') });
765
- }
766
- catch {
767
- // Don't fail abandonment if note logging fails
768
- }
769
- }
770
- }
771
757
  return {
772
- content: [{ type: 'text', text: formatAbandonTaskResponse(result, !!summary, selfAssessment) }],
758
+ content: [{ type: 'text', text: formatAbandonTaskResponse(result, !!summary) }],
773
759
  structuredContent: result,
774
760
  };
775
761
  });
@@ -862,7 +848,7 @@ This project uses Damper MCP for task tracking. **You MUST follow this workflow.
862
848
  2. **Abandon and hand off** - Call \`abandon_task\` with a detailed handoff summary for the next agent.
863
849
  - NEVER leave a started task without completing or abandoning it
864
850
  - If the project has a **completion checklist** (shown in \`start_task\` response), you MUST pass all items as \`confirmations\` when calling \`complete_task\`
865
- - **Self-assessment:** Include \`selfAssessment\` when calling \`complete_task\` or \`abandon_task\`. Reflect honestly on:
851
+ - **Self-review (required):** Call \`review_task\` before \`complete_task\`. Pass \`selfAssessment\` reflecting on:
866
852
  - \`missingContext\` β€” What project knowledge was missing or wrong?
867
853
  - \`toolIssues\` β€” Which tools or workflow steps were confusing or broken?
868
854
  - \`processGaps\` β€” What specs or conventions were missing?
@@ -1335,7 +1321,8 @@ server.registerTool('list_feedback', {
1335
1321
  }
1336
1322
  const lines = data.feedback.map((f) => {
1337
1323
  const link = f.linkedTaskId ? ` β†’ ${f.linkedTaskId}` : '';
1338
- return `β€’ ${f.id}: ${f.title} [${f.type}] (${f.voterCount} votes)${link}`;
1324
+ const hidden = !f.isPublic ? ' [hidden]' : '';
1325
+ return `β€’ ${f.id}: ${f.title} [${f.type}] (${f.voterCount} votes)${hidden}${link}`;
1339
1326
  });
1340
1327
  return {
1341
1328
  content: [{ type: 'text', text: `Feedback:\n${lines.join('\n')}` }],
@@ -1412,12 +1399,14 @@ server.registerTool('get_feedback', {
1412
1399
  // Tool: Update feedback
1413
1400
  server.registerTool('update_feedback', {
1414
1401
  title: 'Update Feedback',
1415
- description: 'Update feedback status. Use to triage, close, or mark feedback as done.',
1402
+ description: 'Update feedback status or visibility. Use to triage, close, mark as done, or hide from public page.',
1416
1403
  inputSchema: z.object({
1417
1404
  feedbackId: z.string().describe('Feedback ID'),
1418
1405
  status: z
1419
1406
  .enum(['new', 'under_review', 'planned', 'in_progress', 'done', 'closed'])
1407
+ .optional()
1420
1408
  .describe('New status'),
1409
+ isPublic: z.boolean().optional().describe('Whether feedback is visible on public page'),
1421
1410
  }),
1422
1411
  outputSchema: z.object({
1423
1412
  id: z.string(),
@@ -1431,8 +1420,13 @@ server.registerTool('update_feedback', {
1431
1420
  idempotentHint: true,
1432
1421
  openWorldHint: false,
1433
1422
  },
1434
- }, async ({ feedbackId, status }) => {
1435
- const result = await api('PATCH', `/api/agent/feedback/${feedbackId}`, { status });
1423
+ }, async ({ feedbackId, status, isPublic }) => {
1424
+ const body = {};
1425
+ if (status)
1426
+ body.status = status;
1427
+ if (isPublic !== undefined)
1428
+ body.isPublic = isPublic;
1429
+ const result = await api('PATCH', `/api/agent/feedback/${feedbackId}`, body);
1436
1430
  return {
1437
1431
  content: [
1438
1432
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@damper/mcp",
3
- "version": "0.8.7",
3
+ "version": "0.9.0",
4
4
  "description": "MCP server for Damper task management",
5
5
  "author": "Damper <hello@usedamper.com>",
6
6
  "repository": {