@jussmor/commit-memory-mcp 0.4.1 → 0.4.3
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/README.md +2 -1
- package/dist/db/client.d.ts +8 -1
- package/dist/db/client.js +151 -17
- package/dist/mcp/server.js +13 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,7 +108,8 @@ build_context_pack({
|
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
Use this before invoking a coding subagent to keep prompts small and focused.
|
|
111
|
-
|
|
111
|
+
The pack is assembled main-first: promoted facts on `main` are treated as baseline truth, then branch/feature facts are added as in-flight overlay.
|
|
112
|
+
If no rows are found in strict scope, the server falls back automatically to broader scope levels.
|
|
112
113
|
|
|
113
114
|
Important: if you provide `domain`/`feature`/`branch` tags in `build_context_pack`, use the same tags during `sync_pr_context` for best precision.
|
|
114
115
|
|
package/dist/db/client.d.ts
CHANGED
|
@@ -25,7 +25,14 @@ export declare function buildContextPack(db: RagDatabase, options: {
|
|
|
25
25
|
taskType?: string;
|
|
26
26
|
includeDraft?: boolean;
|
|
27
27
|
limit: number;
|
|
28
|
-
|
|
28
|
+
forceRefresh?: boolean;
|
|
29
|
+
summarizePR?: boolean;
|
|
30
|
+
}): {
|
|
31
|
+
learnedFeature: ContextPackRecord[];
|
|
32
|
+
branchContext: ContextPackRecord[];
|
|
33
|
+
prMetadata: ContextPackRecord[];
|
|
34
|
+
allContext: ContextPackRecord[];
|
|
35
|
+
};
|
|
29
36
|
export declare function archiveFeatureContext(db: RagDatabase, options: {
|
|
30
37
|
domain: string;
|
|
31
38
|
feature: string;
|
package/dist/db/client.js
CHANGED
|
@@ -7,6 +7,10 @@ export function openDatabase(dbPath) {
|
|
|
7
7
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
8
8
|
const db = new Database(resolved);
|
|
9
9
|
load(db);
|
|
10
|
+
// Enable WAL mode and ensure data is persisted to disk
|
|
11
|
+
db.pragma("journal_mode = WAL");
|
|
12
|
+
db.pragma("synchronous = NORMAL");
|
|
13
|
+
db.pragma("foreign_keys = ON");
|
|
10
14
|
db.exec(`
|
|
11
15
|
CREATE TABLE IF NOT EXISTS commits (
|
|
12
16
|
sha TEXT PRIMARY KEY,
|
|
@@ -370,8 +374,17 @@ export function promoteContextFacts(db, options) {
|
|
|
370
374
|
const result = db.prepare(sql).run(now, ...params);
|
|
371
375
|
return Number(result.changes ?? 0);
|
|
372
376
|
}
|
|
377
|
+
function summarizePRMetadata(fact) {
|
|
378
|
+
// Extract key info from PR metadata to keep it concise
|
|
379
|
+
if (fact.sourceType.startsWith("pr_")) {
|
|
380
|
+
const lines = fact.content.split("\n").slice(0, 2).join(" ");
|
|
381
|
+
return `[${fact.sourceRef}] ${fact.title} — ${lines.substring(0, 100)}`;
|
|
382
|
+
}
|
|
383
|
+
return fact.content;
|
|
384
|
+
}
|
|
373
385
|
export function buildContextPack(db, options) {
|
|
374
386
|
const taskType = options.taskType ?? "general";
|
|
387
|
+
const GLOBAL_BRANCH = "main";
|
|
375
388
|
function runQuery(params) {
|
|
376
389
|
const clauses = [];
|
|
377
390
|
const values = [taskType];
|
|
@@ -383,7 +396,11 @@ export function buildContextPack(db, options) {
|
|
|
383
396
|
clauses.push("scope_feature = ?");
|
|
384
397
|
values.push(options.feature);
|
|
385
398
|
}
|
|
386
|
-
if (params.
|
|
399
|
+
if (params.forcedBranch) {
|
|
400
|
+
clauses.push("scope_branch = ?");
|
|
401
|
+
values.push(params.forcedBranch);
|
|
402
|
+
}
|
|
403
|
+
else if (params.includeBranch && options.branch) {
|
|
387
404
|
clauses.push("scope_branch = ?");
|
|
388
405
|
values.push(options.branch);
|
|
389
406
|
}
|
|
@@ -444,27 +461,144 @@ export function buildContextPack(db, options) {
|
|
|
444
461
|
updatedAt: String(row.updated_at ?? ""),
|
|
445
462
|
}));
|
|
446
463
|
}
|
|
447
|
-
const
|
|
464
|
+
const seenIds = new Set();
|
|
465
|
+
const pack = [];
|
|
466
|
+
const addRows = (rows) => {
|
|
467
|
+
for (const row of rows) {
|
|
468
|
+
if (pack.length >= options.limit) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (seenIds.has(row.id)) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
seenIds.add(row.id);
|
|
475
|
+
pack.push(row);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
// PRIORITY 0) Learned feature knowledge is always included first when available.
|
|
479
|
+
// This ensures feature knowledge isn't lost behind PR metadata.
|
|
480
|
+
if (!options.feature && !options.branch) {
|
|
481
|
+
// Auto-discover recently learned features (source_type='feature-agent')
|
|
482
|
+
const learnedFacts = db.prepare(`
|
|
483
|
+
SELECT
|
|
484
|
+
id,
|
|
485
|
+
source_type,
|
|
486
|
+
source_ref,
|
|
487
|
+
title,
|
|
488
|
+
content,
|
|
489
|
+
scope_domain,
|
|
490
|
+
scope_feature,
|
|
491
|
+
scope_branch,
|
|
492
|
+
scope_task_type,
|
|
493
|
+
priority,
|
|
494
|
+
confidence,
|
|
495
|
+
status,
|
|
496
|
+
updated_at,
|
|
497
|
+
((priority * 0.40) + (confidence * 0.30) + 0.25) AS score
|
|
498
|
+
FROM context_facts
|
|
499
|
+
WHERE source_type = 'feature-agent' AND status = 'promoted'
|
|
500
|
+
ORDER BY updated_at DESC, priority DESC
|
|
501
|
+
LIMIT ?
|
|
502
|
+
`).all(Math.max(3, Math.floor(options.limit * 0.2)));
|
|
503
|
+
const learnedRows = learnedFacts.map((row) => ({
|
|
504
|
+
id: String(row.id ?? ""),
|
|
505
|
+
sourceType: String(row.source_type ?? ""),
|
|
506
|
+
sourceRef: String(row.source_ref ?? ""),
|
|
507
|
+
title: String(row.title ?? ""),
|
|
508
|
+
content: String(row.content ?? ""),
|
|
509
|
+
domain: String(row.scope_domain ?? ""),
|
|
510
|
+
feature: String(row.scope_feature ?? ""),
|
|
511
|
+
branch: String(row.scope_branch ?? ""),
|
|
512
|
+
taskType: String(row.scope_task_type ?? ""),
|
|
513
|
+
priority: Number(row.priority ?? 0),
|
|
514
|
+
confidence: Number(row.confidence ?? 0),
|
|
515
|
+
score: Number(row.score ?? 0),
|
|
516
|
+
status: String(row.status ?? "promoted"),
|
|
517
|
+
updatedAt: String(row.updated_at ?? ""),
|
|
518
|
+
}));
|
|
519
|
+
addRows(learnedRows);
|
|
520
|
+
}
|
|
521
|
+
// 1) Main branch domain context is the durable source-of-truth baseline.
|
|
522
|
+
if (pack.length < options.limit) {
|
|
523
|
+
addRows(runQuery({
|
|
524
|
+
includeDomain: true,
|
|
525
|
+
includeFeature: false,
|
|
526
|
+
includeBranch: false,
|
|
527
|
+
forcedBranch: GLOBAL_BRANCH,
|
|
528
|
+
}));
|
|
529
|
+
}
|
|
530
|
+
// 2) Main branch global context fills any remaining baseline slots.
|
|
531
|
+
if (pack.length < options.limit) {
|
|
532
|
+
addRows(runQuery({
|
|
533
|
+
includeDomain: false,
|
|
534
|
+
includeFeature: false,
|
|
535
|
+
includeBranch: false,
|
|
536
|
+
forcedBranch: GLOBAL_BRANCH,
|
|
537
|
+
}));
|
|
538
|
+
}
|
|
539
|
+
// 3) Branch-local feature context overlays main for active, in-flight work.
|
|
540
|
+
addRows(runQuery({
|
|
448
541
|
includeDomain: true,
|
|
449
542
|
includeFeature: true,
|
|
450
543
|
includeBranch: true,
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
|
|
544
|
+
}));
|
|
545
|
+
// 4) Domain-wide branch context provides additional short-lived signal.
|
|
546
|
+
if (pack.length < options.limit) {
|
|
547
|
+
addRows(runQuery({
|
|
548
|
+
includeDomain: true,
|
|
549
|
+
includeFeature: false,
|
|
550
|
+
includeBranch: false,
|
|
551
|
+
}));
|
|
454
552
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
553
|
+
// 5) Final safety net from all promoted context.
|
|
554
|
+
if (pack.length < options.limit) {
|
|
555
|
+
addRows(runQuery({
|
|
556
|
+
includeDomain: false,
|
|
557
|
+
includeFeature: false,
|
|
558
|
+
includeBranch: false,
|
|
559
|
+
}));
|
|
462
560
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
561
|
+
// Categorize results and apply summarization if requested
|
|
562
|
+
const learnedFeature = [];
|
|
563
|
+
const branchContext = [];
|
|
564
|
+
const prMetadata = [];
|
|
565
|
+
for (const item of pack) {
|
|
566
|
+
if (item.sourceType === "feature-agent") {
|
|
567
|
+
learnedFeature.push(item);
|
|
568
|
+
}
|
|
569
|
+
else if (item.sourceType.startsWith("pr_")) {
|
|
570
|
+
if (options.summarizePR) {
|
|
571
|
+
prMetadata.push({
|
|
572
|
+
...item,
|
|
573
|
+
content: summarizePRMetadata(item),
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
else {
|
|
577
|
+
prMetadata.push(item);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
else if (item.branch && item.branch !== "main") {
|
|
581
|
+
branchContext.push(item);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
// Fallback for other context types
|
|
585
|
+
if (options.summarizePR && item.sourceType.startsWith("pr_")) {
|
|
586
|
+
branchContext.push({
|
|
587
|
+
...item,
|
|
588
|
+
content: summarizePRMetadata(item),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
branchContext.push(item);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return {
|
|
597
|
+
learnedFeature,
|
|
598
|
+
branchContext,
|
|
599
|
+
prMetadata,
|
|
600
|
+
allContext: pack,
|
|
601
|
+
};
|
|
468
602
|
}
|
|
469
603
|
export function archiveFeatureContext(db, options) {
|
|
470
604
|
const now = new Date().toISOString();
|
package/dist/mcp/server.js
CHANGED
|
@@ -175,7 +175,7 @@ export async function startMcpServer() {
|
|
|
175
175
|
},
|
|
176
176
|
{
|
|
177
177
|
name: "build_context_pack",
|
|
178
|
-
description: "Build a scoped context pack for a task/domain/feature/branch
|
|
178
|
+
description: "Build a scoped context pack for a task/domain/feature/branch. Returns learned feature knowledge, branch context, and PR metadata separately.",
|
|
179
179
|
inputSchema: {
|
|
180
180
|
type: "object",
|
|
181
181
|
properties: {
|
|
@@ -185,6 +185,14 @@ export async function startMcpServer() {
|
|
|
185
185
|
taskType: { type: "string" },
|
|
186
186
|
includeDraft: { type: "boolean" },
|
|
187
187
|
limit: { type: "number" },
|
|
188
|
+
forceRefresh: {
|
|
189
|
+
type: "boolean",
|
|
190
|
+
description: "Re-run learn_feature to update feature knowledge",
|
|
191
|
+
},
|
|
192
|
+
summarizePR: {
|
|
193
|
+
type: "boolean",
|
|
194
|
+
description: "Return PR metadata as summaries instead of full content",
|
|
195
|
+
},
|
|
188
196
|
},
|
|
189
197
|
required: [],
|
|
190
198
|
},
|
|
@@ -390,6 +398,8 @@ export async function startMcpServer() {
|
|
|
390
398
|
const branch = String(request.params.arguments?.branch ?? "").trim();
|
|
391
399
|
const taskType = String(request.params.arguments?.taskType ?? "").trim() || "general";
|
|
392
400
|
const includeDraft = Boolean(request.params.arguments?.includeDraft);
|
|
401
|
+
const forceRefresh = Boolean(request.params.arguments?.forceRefresh);
|
|
402
|
+
const summarizePR = Boolean(request.params.arguments?.summarizePR);
|
|
393
403
|
const db = openDatabase(dbPath);
|
|
394
404
|
try {
|
|
395
405
|
const pack = buildContextPack(db, {
|
|
@@ -399,6 +409,8 @@ export async function startMcpServer() {
|
|
|
399
409
|
taskType,
|
|
400
410
|
includeDraft,
|
|
401
411
|
limit: Number.isFinite(limit) && limit > 0 ? limit : 20,
|
|
412
|
+
forceRefresh,
|
|
413
|
+
summarizePR,
|
|
402
414
|
});
|
|
403
415
|
return {
|
|
404
416
|
content: [{ type: "text", text: JSON.stringify(pack, null, 2) }],
|
package/package.json
CHANGED