@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 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
- If no rows are found in strict scope, the server now falls back automatically to broader scope levels.
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
 
@@ -25,7 +25,14 @@ export declare function buildContextPack(db: RagDatabase, options: {
25
25
  taskType?: string;
26
26
  includeDraft?: boolean;
27
27
  limit: number;
28
- }): ContextPackRecord[];
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.includeBranch && options.branch) {
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 strictRows = runQuery({
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
- if (strictRows.length > 0) {
453
- return strictRows;
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
- const broadRows = runQuery({
456
- includeDomain: true,
457
- includeFeature: false,
458
- includeBranch: false,
459
- });
460
- if (broadRows.length > 0) {
461
- return broadRows;
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
- return runQuery({
464
- includeDomain: false,
465
- includeFeature: false,
466
- includeBranch: false,
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();
@@ -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 to keep agent prompts small.",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jussmor/commit-memory-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "mcpName": "io.github.jussmor/commit-memory",
5
5
  "description": "Commit-aware RAG with sqlite-vec and MCP tools for local agent workflows",
6
6
  "license": "MIT",