Skip to content

Commit 68b5584

Browse files
committed
feat(reminder): add memory reminder in tool responses
- Add wrapWithReminder() to inject reminder into tool results - Check PendingMemoryTracker after each tool execution - Append [MEMORY REMINDER] with pending file list - Skip reminder for record_context and verify_work_recorded - Show up to 5 pending files with count indicator
1 parent a4006cc commit 68b5584

File tree

3 files changed

+124
-40
lines changed

3 files changed

+124
-40
lines changed

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.2.7] - 2025-11-28
9+
10+
### Added
11+
12+
- **Memory Reminder in Tool Responses**: Auto-appends reminder when unrecorded file changes detected
13+
- Checks PendingMemoryTracker after each tool execution
14+
- Appends `[MEMORY REMINDER]` with pending file list to tool response
15+
- Skips reminder for `record_context` and `verify_work_recorded` tools
16+
- Shows up to 5 pending files with count indicator
17+
18+
### Technical Details
19+
20+
- **wrapWithReminder()**: New method wraps tool results with memory reminder
21+
- **executeAndWrap()**: Helper function for consistent reminder injection
22+
- **NO_REMINDER_TOOLS**: Set of tools excluded from reminder (prevents loops)
23+
24+
---
25+
826
## [2.2.6] - 2025-11-28
927

1028
### Optimized

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "devmind-mcp",
3-
"version": "2.2.6",
3+
"version": "2.2.7",
44
"description": "DevMind MCP - AI Assistant Memory System - Pure MCP Tool",
55
"main": "dist/index.js",
66
"type": "module",

src/mcp-server.ts

Lines changed: 105 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ export class AiMemoryMcpServer {
7272
// === Pending Memory Tracker (v2.2.6) ===
7373
private pendingMemoryTracker: PendingMemoryTracker;
7474

75+
// Tools that should NOT trigger memory reminder (to avoid infinite loops)
76+
private readonly NO_REMINDER_TOOLS = new Set([
77+
"record_context",
78+
"verify_work_recorded",
79+
]);
80+
7581
// 真实日期记录函数
7682
private getCurrentRealDate(): string {
7783
return new Date().toISOString();
@@ -195,6 +201,62 @@ export class AiMemoryMcpServer {
195201
this.setupHandlers();
196202
}
197203

204+
/**
205+
* Generate memory reminder if there are unrecorded files
206+
* Returns null if no reminder needed
207+
*/
208+
private generateMemoryReminder(): {
209+
pending_count: number;
210+
pending_files: string[];
211+
action: string;
212+
} | null {
213+
const unrecordedFiles = this.pendingMemoryTracker.getUnrecorded();
214+
215+
if (unrecordedFiles.length === 0) {
216+
return null;
217+
}
218+
219+
return {
220+
pending_count: unrecordedFiles.length,
221+
pending_files: unrecordedFiles.slice(0, 5), // Show max 5 files
222+
action: "Call record_context NOW to save your work before responding to user.",
223+
};
224+
}
225+
226+
/**
227+
* Wrap tool result with memory reminder if needed
228+
*/
229+
private wrapWithReminder(
230+
toolName: string,
231+
result: { content: Array<{ type: string; text: string }> }
232+
): { content: Array<{ type: string; text: string }> } {
233+
// Skip reminder for certain tools
234+
if (this.NO_REMINDER_TOOLS.has(toolName)) {
235+
return result;
236+
}
237+
238+
const reminder = this.generateMemoryReminder();
239+
if (!reminder) {
240+
return result;
241+
}
242+
243+
// Add reminder to the result
244+
const reminderText = `\n\n---\n[MEMORY REMINDER] ${reminder.pending_count} file(s) edited but not recorded: ${reminder.pending_files.join(", ")}${reminder.pending_count > 5 ? ` (+${reminder.pending_count - 5} more)` : ""}. ${reminder.action}`;
245+
246+
// Append reminder to the last text content
247+
const newContent = [...result.content];
248+
if (newContent.length > 0 && newContent[newContent.length - 1].type === "text") {
249+
newContent[newContent.length - 1] = {
250+
...newContent[newContent.length - 1],
251+
text: newContent[newContent.length - 1].text + reminderText,
252+
};
253+
} else {
254+
newContent.push({ type: "text", text: reminderText });
255+
}
256+
257+
return { content: newContent };
258+
}
259+
198260
private initializeDatabase(): void {
199261
const dbDir = dirname(this.config.database_path!);
200262
if (!existsSync(dbDir)) {
@@ -697,100 +759,104 @@ export class AiMemoryMcpServer {
697759
// Ensure args is at least an empty object to prevent destructuring errors
698760
const safeArgs = args || {};
699761

762+
// Execute tool and wrap result with memory reminder
763+
const executeAndWrap = async (
764+
handler: () => Promise<{ content: Array<{ type: string; text: string }> }>
765+
) => {
766+
const result = await handler();
767+
return this.wrapWithReminder(name, result);
768+
};
769+
700770
switch (name) {
701771
case "create_session":
702-
return await this.handleCreateSession(
703-
safeArgs as unknown as SessionCreateParams
772+
return executeAndWrap(() =>
773+
this.handleCreateSession(safeArgs as unknown as SessionCreateParams)
704774
);
705775
case "record_context":
706-
return await this.handleRecordContext(
776+
return this.handleRecordContext(
707777
safeArgs as unknown as RecordContextParams
708778
);
709779
case "verify_work_recorded":
710-
return await this.handleVerifyWorkRecorded(
780+
return this.handleVerifyWorkRecorded(
711781
safeArgs as { work_summary: string; project_path?: string }
712782
);
713783
case "end_session":
714-
return await this.handleEndSession(
715-
safeArgs as { session_id: string }
784+
return executeAndWrap(() =>
785+
this.handleEndSession(safeArgs as { session_id: string })
716786
);
717787
case "get_current_session":
718-
return await this.handleGetCurrentSession(
719-
safeArgs as { project_path: string }
788+
return executeAndWrap(() =>
789+
this.handleGetCurrentSession(safeArgs as { project_path: string })
720790
);
721791
case "list_projects":
722-
return await this.handleListProjects(
723-
safeArgs as { include_stats?: boolean; limit?: number }
792+
return executeAndWrap(() =>
793+
this.handleListProjects(safeArgs as { include_stats?: boolean; limit?: number })
724794
);
725-
// extract_file_context removed
726795
case "get_context":
727-
return await this.handleGetContext(
728-
safeArgs as { context_ids: string | string[]; relation_type?: string }
796+
return executeAndWrap(() =>
797+
this.handleGetContext(
798+
safeArgs as { context_ids: string | string[]; relation_type?: string }
799+
)
729800
);
730801
case "semantic_search":
731-
return await this.handleSemanticSearch(
732-
safeArgs as {
802+
return executeAndWrap(() =>
803+
this.handleSemanticSearch(safeArgs as {
733804
query: string;
734805
project_path?: string;
735806
session_id?: string;
736807
limit?: number;
737808
similarity_threshold?: number;
738809
hybrid_weight?: number;
739-
}
810+
})
740811
);
741-
// generate_embeddings removed
742812
case "list_contexts":
743-
return await this.handleListContexts(
744-
safeArgs as {
813+
return executeAndWrap(() =>
814+
this.handleListContexts(safeArgs as {
745815
session_id?: string;
746816
project_path?: string;
747817
limit?: number;
748-
}
818+
})
749819
);
750820
case "delete_context":
751-
return await this.handleDeleteContext(
752-
safeArgs as { context_id: string }
821+
return executeAndWrap(() =>
822+
this.handleDeleteContext(safeArgs as { context_id: string })
753823
);
754824
case "update_context":
755-
return await this.handleUpdateContext(
756-
safeArgs as {
825+
return executeAndWrap(() =>
826+
this.handleUpdateContext(safeArgs as {
757827
context_id: string;
758828
content?: string;
759829
tags?: string[];
760830
quality_score?: number;
761831
metadata?: object;
762-
}
832+
})
763833
);
764834
case "delete_session":
765-
return await this.handleDeleteSession(
766-
safeArgs as { session_id?: string; project_id?: string }
835+
return executeAndWrap(() =>
836+
this.handleDeleteSession(safeArgs as { session_id?: string; project_id?: string })
767837
);
768838
case "project_analysis_engineer":
769-
return await this.handleProjectAnalysisEngineerTool(
770-
safeArgs as {
839+
return executeAndWrap(() =>
840+
this.handleProjectAnalysisEngineerTool(safeArgs as {
771841
project_path: string;
772842
analysis_focus?: string;
773843
doc_style?: string;
774844
auto_save?: boolean;
775845
language?: string;
776-
}
846+
})
777847
);
778-
// optimize_project_memory removed
779-
// update_quality_scores removed
780848
case "export_memory_graph":
781-
return await this.handleExportMemoryGraph(
782-
safeArgs as {
849+
return executeAndWrap(() =>
850+
this.handleExportMemoryGraph(safeArgs as {
783851
project_id: string;
784852
max_nodes?: number;
785853
focus_type?: string;
786854
output_path?: string;
787-
}
855+
})
788856
);
789857
case "get_memory_status":
790-
return await this.handleGetMemoryStatus(
791-
safeArgs as {
792-
project_path?: string;
793-
}
858+
return executeAndWrap(() =>
859+
this.handleGetMemoryStatus(safeArgs as { project_path?: string })
794860
);
795861
default:
796862
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);

0 commit comments

Comments
 (0)