@mycontxt/mcp 0.1.1
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/.turbo/turbo-build.log +15 -0
- package/dist/server.js +988 -0
- package/dist/server.js.map +1 -0
- package/package.json +24 -0
- package/src/server.ts +433 -0
- package/src/tools/auto-capture.ts +377 -0
- package/src/tools/index.ts +330 -0
- package/src/tools/suggest-context.ts +76 -0
- package/src/utils/project.ts +19 -0
- package/src/utils/usage-gate.ts +149 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-capture tools — AI-driven passive context capture
|
|
3
|
+
*
|
|
4
|
+
* These tools are called by AI agents (Claude Code, etc.) during a conversation
|
|
5
|
+
* to silently capture decisions and patterns the developer makes.
|
|
6
|
+
* All entries are saved as drafts for human review.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SQLiteDatabase } from '@mycontxt/adapters/sqlite';
|
|
10
|
+
import { getDbPath } from '../utils/project.js';
|
|
11
|
+
import { checkEntryAllowed } from '../utils/usage-gate.js';
|
|
12
|
+
|
|
13
|
+
interface AutoCaptureDecisionArgs {
|
|
14
|
+
decision: string;
|
|
15
|
+
rationale: string;
|
|
16
|
+
category?: string;
|
|
17
|
+
alternatives?: string;
|
|
18
|
+
status?: string;
|
|
19
|
+
conversationId?: string;
|
|
20
|
+
projectPath?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface AutoCapturePatternArgs {
|
|
24
|
+
pattern: string;
|
|
25
|
+
description: string;
|
|
26
|
+
category?: string;
|
|
27
|
+
when?: string;
|
|
28
|
+
conversationId?: string;
|
|
29
|
+
projectPath?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CaptureDiscussionArgs {
|
|
33
|
+
type: 'decision' | 'pattern' | 'context';
|
|
34
|
+
title: string;
|
|
35
|
+
summary: string;
|
|
36
|
+
context: string;
|
|
37
|
+
rejected?: string;
|
|
38
|
+
conversationId?: string;
|
|
39
|
+
projectPath?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface UpdateSessionArgs {
|
|
43
|
+
summary: string;
|
|
44
|
+
filesChanged?: string[];
|
|
45
|
+
decisions?: string[];
|
|
46
|
+
conversationId?: string;
|
|
47
|
+
projectPath?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface GetDraftsArgs {
|
|
51
|
+
source?: string;
|
|
52
|
+
projectPath?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ConfirmDraftArgs {
|
|
56
|
+
id: string;
|
|
57
|
+
projectPath?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Auto-capture a decision from conversation.
|
|
62
|
+
* Called by AI when it detects the developer made an architectural decision.
|
|
63
|
+
*/
|
|
64
|
+
export async function autoCaptureDecision(args: AutoCaptureDecisionArgs): Promise<string> {
|
|
65
|
+
const projectPath = args.projectPath || process.cwd();
|
|
66
|
+
const dbPath = getDbPath(projectPath);
|
|
67
|
+
|
|
68
|
+
const db = new SQLiteDatabase(dbPath);
|
|
69
|
+
await db.initialize();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const project = await db.getProjectByPath(projectPath);
|
|
73
|
+
if (!project) {
|
|
74
|
+
return JSON.stringify({ status: 'error', message: 'No Contxt project found.' });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check usage limits (graceful degradation - never block the AI)
|
|
78
|
+
const gateCheck = await checkEntryAllowed(db, project.id);
|
|
79
|
+
if (!gateCheck.allowed) {
|
|
80
|
+
return JSON.stringify({
|
|
81
|
+
status: 'limit_reached',
|
|
82
|
+
message: `Decision noted (not saved - usage limit reached).${gateCheck.message}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const entry = await db.createEntry({
|
|
87
|
+
projectId: project.id,
|
|
88
|
+
type: 'decision',
|
|
89
|
+
title: args.decision,
|
|
90
|
+
content: args.rationale,
|
|
91
|
+
metadata: {
|
|
92
|
+
source: 'mcp:auto',
|
|
93
|
+
category: args.category,
|
|
94
|
+
alternatives: args.alternatives,
|
|
95
|
+
decisionStatus: args.status || 'active',
|
|
96
|
+
conversationId: args.conversationId,
|
|
97
|
+
capturedAt: new Date().toISOString(),
|
|
98
|
+
},
|
|
99
|
+
status: 'draft',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return JSON.stringify({
|
|
103
|
+
status: 'captured',
|
|
104
|
+
id: entry.id,
|
|
105
|
+
message: `Decision captured: ${args.decision} (draft — run \`contxt review\` to confirm)`,
|
|
106
|
+
});
|
|
107
|
+
} finally {
|
|
108
|
+
await db.close();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Auto-capture a pattern from conversation.
|
|
114
|
+
* Called by AI when it identifies a reusable code pattern.
|
|
115
|
+
*/
|
|
116
|
+
export async function autoCapturePattern(args: AutoCapturePatternArgs): Promise<string> {
|
|
117
|
+
const projectPath = args.projectPath || process.cwd();
|
|
118
|
+
const dbPath = getDbPath(projectPath);
|
|
119
|
+
|
|
120
|
+
const db = new SQLiteDatabase(dbPath);
|
|
121
|
+
await db.initialize();
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const project = await db.getProjectByPath(projectPath);
|
|
125
|
+
if (!project) {
|
|
126
|
+
return JSON.stringify({ status: 'error', message: 'No Contxt project found.' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check usage limits (graceful degradation - never block the AI)
|
|
130
|
+
const gateCheck = await checkEntryAllowed(db, project.id);
|
|
131
|
+
if (!gateCheck.allowed) {
|
|
132
|
+
return JSON.stringify({
|
|
133
|
+
status: 'limit_reached',
|
|
134
|
+
message: `Pattern noted (not saved - usage limit reached).${gateCheck.message}`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const entry = await db.createEntry({
|
|
139
|
+
projectId: project.id,
|
|
140
|
+
type: 'pattern',
|
|
141
|
+
title: args.pattern,
|
|
142
|
+
content: args.description,
|
|
143
|
+
metadata: {
|
|
144
|
+
source: 'mcp:auto',
|
|
145
|
+
category: args.category,
|
|
146
|
+
when: args.when,
|
|
147
|
+
conversationId: args.conversationId,
|
|
148
|
+
capturedAt: new Date().toISOString(),
|
|
149
|
+
},
|
|
150
|
+
status: 'draft',
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return JSON.stringify({
|
|
154
|
+
status: 'captured',
|
|
155
|
+
id: entry.id,
|
|
156
|
+
message: `Pattern captured: ${args.pattern} (draft — run \`contxt review\` to confirm)`,
|
|
157
|
+
});
|
|
158
|
+
} finally {
|
|
159
|
+
await db.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Capture a discussion outcome — richer alternative to autoCaptureDecision/Pattern.
|
|
165
|
+
* Use when a decision emerged from back-and-forth; captures context, what was
|
|
166
|
+
* rejected, and why, in natural language rather than rigid fields.
|
|
167
|
+
*/
|
|
168
|
+
export async function captureDiscussion(args: CaptureDiscussionArgs): Promise<string> {
|
|
169
|
+
const projectPath = args.projectPath || process.cwd();
|
|
170
|
+
const dbPath = getDbPath(projectPath);
|
|
171
|
+
|
|
172
|
+
const db = new SQLiteDatabase(dbPath);
|
|
173
|
+
await db.initialize();
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const project = await db.getProjectByPath(projectPath);
|
|
177
|
+
if (!project) {
|
|
178
|
+
return JSON.stringify({ status: 'error', message: 'No Contxt project found.' });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check usage limits (graceful degradation - never block the AI)
|
|
182
|
+
const gateCheck = await checkEntryAllowed(db, project.id);
|
|
183
|
+
if (!gateCheck.allowed) {
|
|
184
|
+
return JSON.stringify({
|
|
185
|
+
status: 'limit_reached',
|
|
186
|
+
message: `Discussion noted (not saved - usage limit reached).${gateCheck.message}`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const content = [
|
|
191
|
+
args.summary,
|
|
192
|
+
'',
|
|
193
|
+
`**Context:** ${args.context}`,
|
|
194
|
+
args.rejected ? `**Rejected:** ${args.rejected}` : '',
|
|
195
|
+
]
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join('\n');
|
|
198
|
+
|
|
199
|
+
const entry = await db.createEntry({
|
|
200
|
+
projectId: project.id,
|
|
201
|
+
type: args.type,
|
|
202
|
+
title: args.title,
|
|
203
|
+
content,
|
|
204
|
+
metadata: {
|
|
205
|
+
source: 'mcp:discussion',
|
|
206
|
+
context: args.context,
|
|
207
|
+
rejected: args.rejected,
|
|
208
|
+
conversationId: args.conversationId,
|
|
209
|
+
capturedAt: new Date().toISOString(),
|
|
210
|
+
},
|
|
211
|
+
status: 'draft',
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return JSON.stringify({
|
|
215
|
+
status: 'captured',
|
|
216
|
+
id: entry.id,
|
|
217
|
+
message: `Discussion captured: ${args.title} (draft — run \`contxt review\` to confirm)`,
|
|
218
|
+
});
|
|
219
|
+
} finally {
|
|
220
|
+
await db.close();
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Update the active session summary.
|
|
226
|
+
* Called by AI at the end of a conversation to log what was accomplished.
|
|
227
|
+
*/
|
|
228
|
+
export async function updateSession(args: UpdateSessionArgs): Promise<string> {
|
|
229
|
+
const projectPath = args.projectPath || process.cwd();
|
|
230
|
+
const dbPath = getDbPath(projectPath);
|
|
231
|
+
|
|
232
|
+
const db = new SQLiteDatabase(dbPath);
|
|
233
|
+
await db.initialize();
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
const project = await db.getProjectByPath(projectPath);
|
|
237
|
+
if (!project) {
|
|
238
|
+
return JSON.stringify({ status: 'error', message: 'No Contxt project found.' });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const branch = await db.getActiveBranch(project.id);
|
|
242
|
+
const entries = await db.listEntries({
|
|
243
|
+
projectId: project.id,
|
|
244
|
+
branch,
|
|
245
|
+
type: 'context',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const activeContext = entries.find((e) => e.status === 'active');
|
|
249
|
+
|
|
250
|
+
if (activeContext) {
|
|
251
|
+
await db.updateEntry(activeContext.id, {
|
|
252
|
+
metadata: {
|
|
253
|
+
...activeContext.metadata,
|
|
254
|
+
lastSessionSummary: args.summary,
|
|
255
|
+
lastSessionFiles: args.filesChanged,
|
|
256
|
+
lastSessionDecisions: args.decisions,
|
|
257
|
+
lastSessionAt: new Date().toISOString(),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
} else {
|
|
261
|
+
// Check usage limits before creating new session entry
|
|
262
|
+
const gateCheck = await checkEntryAllowed(db, project.id);
|
|
263
|
+
if (!gateCheck.allowed) {
|
|
264
|
+
return JSON.stringify({
|
|
265
|
+
status: 'limit_reached',
|
|
266
|
+
message: `Session noted (not saved - usage limit reached).${gateCheck.message}`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Create a session entry as draft — consistent with all other auto-captures
|
|
271
|
+
await db.createEntry({
|
|
272
|
+
projectId: project.id,
|
|
273
|
+
type: 'session',
|
|
274
|
+
title: `Session: ${new Date().toLocaleDateString()}`,
|
|
275
|
+
content: args.summary,
|
|
276
|
+
metadata: {
|
|
277
|
+
source: 'mcp:auto',
|
|
278
|
+
filesChanged: args.filesChanged,
|
|
279
|
+
decisions: args.decisions,
|
|
280
|
+
conversationId: args.conversationId,
|
|
281
|
+
startedAt: new Date().toISOString(),
|
|
282
|
+
},
|
|
283
|
+
status: 'draft',
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return JSON.stringify({
|
|
288
|
+
status: 'updated',
|
|
289
|
+
message: 'Session summary updated.',
|
|
290
|
+
});
|
|
291
|
+
} finally {
|
|
292
|
+
await db.close();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get all draft entries pending review.
|
|
298
|
+
* Called by AI to surface pending drafts for the developer.
|
|
299
|
+
*/
|
|
300
|
+
export async function getDrafts(args: GetDraftsArgs): Promise<string> {
|
|
301
|
+
const projectPath = args.projectPath || process.cwd();
|
|
302
|
+
const dbPath = getDbPath(projectPath);
|
|
303
|
+
|
|
304
|
+
const db = new SQLiteDatabase(dbPath);
|
|
305
|
+
await db.initialize();
|
|
306
|
+
|
|
307
|
+
try {
|
|
308
|
+
const project = await db.getProjectByPath(projectPath);
|
|
309
|
+
if (!project) {
|
|
310
|
+
return JSON.stringify({ status: 'error', message: 'No Contxt project found.' });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const branch = await db.getActiveBranch(project.id);
|
|
314
|
+
const allEntries = await db.listEntries({ projectId: project.id, branch });
|
|
315
|
+
let drafts = allEntries.filter((e) => e.status === 'draft');
|
|
316
|
+
|
|
317
|
+
if (args.source) {
|
|
318
|
+
const sourceFilter = `mcp:${args.source}`;
|
|
319
|
+
drafts = drafts.filter((e) => e.metadata.source?.includes(sourceFilter));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (drafts.length === 0) {
|
|
323
|
+
return JSON.stringify({ status: 'ok', count: 0, message: 'No drafts pending review.' });
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const summary = drafts.map((d) => ({
|
|
327
|
+
id: d.id,
|
|
328
|
+
type: d.type,
|
|
329
|
+
title: d.title,
|
|
330
|
+
source: d.metadata.source,
|
|
331
|
+
conversationId: d.metadata.conversationId,
|
|
332
|
+
capturedAt: d.metadata.capturedAt || d.createdAt,
|
|
333
|
+
}));
|
|
334
|
+
|
|
335
|
+
return JSON.stringify({
|
|
336
|
+
status: 'ok',
|
|
337
|
+
count: drafts.length,
|
|
338
|
+
drafts: summary,
|
|
339
|
+
message: `${drafts.length} draft${drafts.length !== 1 ? 's' : ''} pending. Run \`contxt review\` to confirm them.`,
|
|
340
|
+
});
|
|
341
|
+
} finally {
|
|
342
|
+
await db.close();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Confirm a draft entry.
|
|
348
|
+
* Called by AI when the developer explicitly approves a captured draft.
|
|
349
|
+
*/
|
|
350
|
+
export async function confirmDraft(args: ConfirmDraftArgs): Promise<string> {
|
|
351
|
+
const projectPath = args.projectPath || process.cwd();
|
|
352
|
+
const dbPath = getDbPath(projectPath);
|
|
353
|
+
|
|
354
|
+
const db = new SQLiteDatabase(dbPath);
|
|
355
|
+
await db.initialize();
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const entry = await db.getEntry(args.id);
|
|
359
|
+
if (!entry) {
|
|
360
|
+
return JSON.stringify({ status: 'error', message: `Draft ${args.id} not found.` });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (entry.status !== 'draft') {
|
|
364
|
+
return JSON.stringify({ status: 'error', message: `Entry ${args.id} is not a draft.` });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
await db.updateEntry(args.id, { status: 'active' });
|
|
368
|
+
|
|
369
|
+
return JSON.stringify({
|
|
370
|
+
status: 'confirmed',
|
|
371
|
+
id: args.id,
|
|
372
|
+
message: `"${entry.title}" confirmed and added to memory.`,
|
|
373
|
+
});
|
|
374
|
+
} finally {
|
|
375
|
+
await db.close();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools - All tool implementations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { SQLiteDatabase } from '@mycontxt/adapters/sqlite';
|
|
6
|
+
import { MemoryEngine } from '@mycontxt/core';
|
|
7
|
+
import type { DecisionInput, PatternInput, ContextInput } from '@mycontxt/core';
|
|
8
|
+
import { getDbPath } from '../utils/project.js';
|
|
9
|
+
import { suggestContext } from './suggest-context.js';
|
|
10
|
+
import {
|
|
11
|
+
autoCaptureDecision,
|
|
12
|
+
autoCapturePattern,
|
|
13
|
+
captureDiscussion,
|
|
14
|
+
updateSession,
|
|
15
|
+
getDrafts,
|
|
16
|
+
confirmDraft,
|
|
17
|
+
} from './auto-capture.js';
|
|
18
|
+
|
|
19
|
+
// Export suggest-context
|
|
20
|
+
export { suggestContext };
|
|
21
|
+
|
|
22
|
+
// Export auto-capture tools
|
|
23
|
+
export { autoCaptureDecision, autoCapturePattern, captureDiscussion, updateSession, getDrafts, confirmDraft };
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get project context - Overview of project
|
|
27
|
+
*/
|
|
28
|
+
export async function getProjectContext(args: { projectPath?: string }): Promise<string> {
|
|
29
|
+
const projectPath = args.projectPath || process.cwd();
|
|
30
|
+
const dbPath = getDbPath(projectPath);
|
|
31
|
+
|
|
32
|
+
const db = new SQLiteDatabase(dbPath);
|
|
33
|
+
await db.initialize();
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const project = await db.getProjectByPath(projectPath);
|
|
37
|
+
if (!project) {
|
|
38
|
+
return 'No MemoCore project found.';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const engine = new MemoryEngine(db);
|
|
42
|
+
const activeBranch = await db.getActiveBranch(project.id);
|
|
43
|
+
const context = await engine.getContext(project.id);
|
|
44
|
+
const decisions = await engine.listDecisions(project.id);
|
|
45
|
+
const patterns = await engine.listPatterns(project.id);
|
|
46
|
+
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
lines.push(`# Project: ${project.name}`);
|
|
49
|
+
lines.push('');
|
|
50
|
+
lines.push(`**Branch:** ${activeBranch}`);
|
|
51
|
+
lines.push(`**Stack:** ${project.stack?.join(', ') || 'Not specified'}`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
|
|
54
|
+
if (context) {
|
|
55
|
+
lines.push('## Current Context');
|
|
56
|
+
if (context.metadata.feature) {
|
|
57
|
+
lines.push(`**Feature:** ${context.metadata.feature}`);
|
|
58
|
+
}
|
|
59
|
+
if (context.metadata.blockers?.length > 0) {
|
|
60
|
+
lines.push(`**Blockers:** ${context.metadata.blockers.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
lines.push(`## Summary`);
|
|
66
|
+
lines.push(`- ${decisions.length} decisions`);
|
|
67
|
+
lines.push(`- ${patterns.length} patterns`);
|
|
68
|
+
|
|
69
|
+
return lines.join('\n');
|
|
70
|
+
} finally {
|
|
71
|
+
await db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get decisions
|
|
77
|
+
*/
|
|
78
|
+
export async function getDecisions(args: {
|
|
79
|
+
projectPath?: string;
|
|
80
|
+
limit?: number;
|
|
81
|
+
}): Promise<string> {
|
|
82
|
+
const projectPath = args.projectPath || process.cwd();
|
|
83
|
+
const dbPath = getDbPath(projectPath);
|
|
84
|
+
|
|
85
|
+
const db = new SQLiteDatabase(dbPath);
|
|
86
|
+
await db.initialize();
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const project = await db.getProjectByPath(projectPath);
|
|
90
|
+
if (!project) {
|
|
91
|
+
return 'No MemoCore project found.';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const engine = new MemoryEngine(db);
|
|
95
|
+
const decisions = await engine.listDecisions(project.id);
|
|
96
|
+
|
|
97
|
+
const limited = args.limit ? decisions.slice(0, args.limit) : decisions;
|
|
98
|
+
|
|
99
|
+
if (limited.length === 0) {
|
|
100
|
+
return 'No decisions found.';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const lines: string[] = [];
|
|
104
|
+
lines.push('# Architectural Decisions');
|
|
105
|
+
lines.push('');
|
|
106
|
+
|
|
107
|
+
for (const decision of limited) {
|
|
108
|
+
lines.push(`## ${decision.title}`);
|
|
109
|
+
lines.push('');
|
|
110
|
+
lines.push(decision.content);
|
|
111
|
+
if (decision.metadata.alternatives?.length > 0) {
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push(`**Alternatives considered:** ${decision.metadata.alternatives.join(', ')}`);
|
|
114
|
+
}
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push('---');
|
|
117
|
+
lines.push('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
} finally {
|
|
122
|
+
await db.close();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get patterns
|
|
128
|
+
*/
|
|
129
|
+
export async function getPatterns(args: {
|
|
130
|
+
projectPath?: string;
|
|
131
|
+
limit?: number;
|
|
132
|
+
}): Promise<string> {
|
|
133
|
+
const projectPath = args.projectPath || process.cwd();
|
|
134
|
+
const dbPath = getDbPath(projectPath);
|
|
135
|
+
|
|
136
|
+
const db = new SQLiteDatabase(dbPath);
|
|
137
|
+
await db.initialize();
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const project = await db.getProjectByPath(projectPath);
|
|
141
|
+
if (!project) {
|
|
142
|
+
return 'No MemoCore project found.';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const engine = new MemoryEngine(db);
|
|
146
|
+
const patterns = await engine.listPatterns(project.id);
|
|
147
|
+
|
|
148
|
+
const limited = args.limit ? patterns.slice(0, args.limit) : patterns;
|
|
149
|
+
|
|
150
|
+
if (limited.length === 0) {
|
|
151
|
+
return 'No patterns found.';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const lines: string[] = [];
|
|
155
|
+
lines.push('# Code Patterns & Conventions');
|
|
156
|
+
lines.push('');
|
|
157
|
+
|
|
158
|
+
for (const pattern of limited) {
|
|
159
|
+
lines.push(`## ${pattern.title}`);
|
|
160
|
+
lines.push('');
|
|
161
|
+
lines.push(pattern.content);
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push('---');
|
|
164
|
+
lines.push('');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return lines.join('\n');
|
|
168
|
+
} finally {
|
|
169
|
+
await db.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Search memory
|
|
175
|
+
*/
|
|
176
|
+
export async function searchMemory(args: {
|
|
177
|
+
query: string;
|
|
178
|
+
projectPath?: string;
|
|
179
|
+
limit?: number;
|
|
180
|
+
}): Promise<string> {
|
|
181
|
+
const projectPath = args.projectPath || process.cwd();
|
|
182
|
+
const dbPath = getDbPath(projectPath);
|
|
183
|
+
|
|
184
|
+
const db = new SQLiteDatabase(dbPath);
|
|
185
|
+
await db.initialize();
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const project = await db.getProjectByPath(projectPath);
|
|
189
|
+
if (!project) {
|
|
190
|
+
return 'No MemoCore project found.';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const engine = new MemoryEngine(db);
|
|
194
|
+
const results = await engine.searchEntries(project.id, args.query);
|
|
195
|
+
|
|
196
|
+
const limited = args.limit ? results.slice(0, args.limit) : results;
|
|
197
|
+
|
|
198
|
+
if (limited.length === 0) {
|
|
199
|
+
return `No results found for "${args.query}".`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const lines: string[] = [];
|
|
203
|
+
lines.push(`# Search Results for "${args.query}"`);
|
|
204
|
+
lines.push('');
|
|
205
|
+
|
|
206
|
+
for (const entry of limited) {
|
|
207
|
+
lines.push(`## ${entry.type.toUpperCase()}: ${entry.title}`);
|
|
208
|
+
lines.push('');
|
|
209
|
+
lines.push(entry.content);
|
|
210
|
+
lines.push('');
|
|
211
|
+
lines.push('---');
|
|
212
|
+
lines.push('');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return lines.join('\n');
|
|
216
|
+
} finally {
|
|
217
|
+
await db.close();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Log decision
|
|
223
|
+
*/
|
|
224
|
+
export async function logDecision(args: {
|
|
225
|
+
title: string;
|
|
226
|
+
rationale: string;
|
|
227
|
+
alternatives?: string[];
|
|
228
|
+
projectPath?: string;
|
|
229
|
+
}): Promise<string> {
|
|
230
|
+
const projectPath = args.projectPath || process.cwd();
|
|
231
|
+
const dbPath = getDbPath(projectPath);
|
|
232
|
+
|
|
233
|
+
const db = new SQLiteDatabase(dbPath);
|
|
234
|
+
await db.initialize();
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const project = await db.getProjectByPath(projectPath);
|
|
238
|
+
if (!project) {
|
|
239
|
+
return 'No MemoCore project found.';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const engine = new MemoryEngine(db);
|
|
243
|
+
|
|
244
|
+
const input: DecisionInput = {
|
|
245
|
+
title: args.title,
|
|
246
|
+
rationale: args.rationale,
|
|
247
|
+
alternatives: args.alternatives,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const entry = await engine.addDecision(project.id, input);
|
|
251
|
+
|
|
252
|
+
return `Decision "${entry.title}" logged successfully.`;
|
|
253
|
+
} finally {
|
|
254
|
+
await db.close();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Update context
|
|
260
|
+
*/
|
|
261
|
+
export async function updateContext(args: {
|
|
262
|
+
feature?: string;
|
|
263
|
+
blockers?: string[];
|
|
264
|
+
nextSteps?: string[];
|
|
265
|
+
projectPath?: string;
|
|
266
|
+
}): Promise<string> {
|
|
267
|
+
const projectPath = args.projectPath || process.cwd();
|
|
268
|
+
const dbPath = getDbPath(projectPath);
|
|
269
|
+
|
|
270
|
+
const db = new SQLiteDatabase(dbPath);
|
|
271
|
+
await db.initialize();
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const project = await db.getProjectByPath(projectPath);
|
|
275
|
+
if (!project) {
|
|
276
|
+
return 'No MemoCore project found.';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const engine = new MemoryEngine(db);
|
|
280
|
+
|
|
281
|
+
const input: ContextInput = {
|
|
282
|
+
feature: args.feature,
|
|
283
|
+
blockers: args.blockers,
|
|
284
|
+
nextSteps: args.nextSteps,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
await engine.setContext(project.id, input);
|
|
288
|
+
|
|
289
|
+
return 'Project context updated successfully.';
|
|
290
|
+
} finally {
|
|
291
|
+
await db.close();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Save pattern
|
|
297
|
+
*/
|
|
298
|
+
export async function savePattern(args: {
|
|
299
|
+
title: string;
|
|
300
|
+
content: string;
|
|
301
|
+
category?: string;
|
|
302
|
+
projectPath?: string;
|
|
303
|
+
}): Promise<string> {
|
|
304
|
+
const projectPath = args.projectPath || process.cwd();
|
|
305
|
+
const dbPath = getDbPath(projectPath);
|
|
306
|
+
|
|
307
|
+
const db = new SQLiteDatabase(dbPath);
|
|
308
|
+
await db.initialize();
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const project = await db.getProjectByPath(projectPath);
|
|
312
|
+
if (!project) {
|
|
313
|
+
return 'No MemoCore project found.';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const engine = new MemoryEngine(db);
|
|
317
|
+
|
|
318
|
+
const input: PatternInput = {
|
|
319
|
+
title: args.title,
|
|
320
|
+
content: args.content,
|
|
321
|
+
category: args.category,
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const entry = await engine.addPattern(project.id, input);
|
|
325
|
+
|
|
326
|
+
return `Pattern "${entry.title}" saved successfully.`;
|
|
327
|
+
} finally {
|
|
328
|
+
await db.close();
|
|
329
|
+
}
|
|
330
|
+
}
|