memorix 1.6.3 → 1.7.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.
package/src/server.ts CHANGED
@@ -46,7 +46,8 @@ import {
46
46
  type ToolProfile,
47
47
  } from './server/tool-profile.js';
48
48
  import { initLLM, isLLMEnabled, getLLMConfig } from './llm/provider.js';
49
- import { compactOnWrite, deduplicateMemory } from './llm/memory-manager.js';
49
+ import { compactOnWrite } from './llm/memory-manager.js';
50
+ import { applyDeduplicationPlan, planMemoryDeduplication } from './memory/deduplication.js';
50
51
  import type { ExistingMemory } from './llm/memory-manager.js';
51
52
  import { runFormation, getMetricsSummary, getBeforeAfterMetrics } from './memory/formation/index.js';
52
53
  import type { FormationConfig, SearchHit, FormedMemory, FormationStage, FormationStageEvent } from './memory/formation/types.js';
@@ -2503,66 +2504,31 @@ export async function createMemorixServer(
2503
2504
  return { content: [{ type: 'text' as const, text: 'Not enough memories in scope to deduplicate.' }] };
2504
2505
  }
2505
2506
 
2506
- // Group by entity for focused dedup
2507
- const byEntity = new Map<string, typeof candidates>();
2508
- for (const obs of candidates) {
2509
- const list = byEntity.get(obs.entityName) ?? [];
2510
- list.push(obs);
2511
- byEntity.set(obs.entityName, list);
2512
- }
2513
-
2514
- const actions: string[] = [];
2515
- const toResolve: number[] = [];
2516
-
2517
- for (const [entity, group] of byEntity) {
2518
- if (group.length < 2) continue;
2519
-
2520
- // Compare each pair within entity group
2521
- for (let i = 0; i < group.length; i++) {
2522
- for (let j = i + 1; j < group.length; j++) {
2523
- const newer = group[j];
2524
- const older = group[i];
2525
- try {
2526
- const decision = await deduplicateMemory(
2527
- { title: newer.title, narrative: newer.narrative, facts: newer.facts },
2528
- [{ id: older.id, title: older.title, narrative: older.narrative, facts: older.facts.join('\n') }],
2529
- );
2530
- if (decision && decision.action === 'UPDATE' && decision.targetId) {
2531
- actions.push(`[UPDATED] #${older.id} "${older.title}" → superseded by #${newer.id} (${decision.reason})${decision.usedLLM ? ' [LLM]' : ' [heuristic]'}`);
2532
- toResolve.push(older.id);
2533
- } else if (decision && decision.action === 'NONE') {
2534
- actions.push(`[DELETE] #${newer.id} "${newer.title}" → redundant (${decision.reason})${decision.usedLLM ? ' [LLM]' : ' [heuristic]'}`);
2535
- toResolve.push(newer.id);
2536
- } else if (decision && decision.action === 'DELETE') {
2537
- actions.push(`[ERROR] #${decision.targetId ?? older.id} → outdated (${decision.reason})${decision.usedLLM ? ' [LLM]' : ' [heuristic]'}`);
2538
- toResolve.push(decision.targetId ?? older.id);
2539
- }
2540
- } catch (dedupErr) { actions.push(`[WARN] comparison failed: ${(dedupErr as Error)?.message ?? dedupErr}`); }
2541
- }
2542
- }
2543
- }
2507
+ const manageableCandidates = candidates.filter((observation) => canManageObservation(observation, reader));
2508
+ const plan = await planMemoryDeduplication(manageableCandidates);
2509
+ const actionLines = plan.actions.map((item) =>
2510
+ `Resolve #${item.resolveId} "${item.resolveTitle}"; keep #${item.keepId} "${item.keepTitle}" (${item.reason})${item.usedLLM ? ' [LLM]' : ' [heuristic]'}`,
2511
+ );
2544
2512
 
2545
- if (actions.length === 0) {
2546
- return { content: [{ type: 'text' as const, text: `[OK] Scanned ${candidates.length} memories across ${byEntity.size} entities — no duplicates found.` }] };
2513
+ if (actionLines.length === 0) {
2514
+ return { content: [{ type: 'text' as const, text: `[OK] Scanned ${plan.scanned} memories across ${plan.entities} entities — no duplicates found.` }] };
2547
2515
  }
2548
2516
 
2549
2517
  if (dryRun) {
2550
2518
  return {
2551
2519
  content: [{
2552
2520
  type: 'text' as const,
2553
- text: `[SEARCH] DRY RUN — ${actions.length} action(s) found:\n\n${actions.join('\n')}\n\nRun with dryRun=false to apply.`,
2521
+ text: `[SEARCH] DRY RUN — ${actionLines.length} action(s) found:\n\n${actionLines.join('\n')}\n\nRun with dryRun=false to apply.`,
2554
2522
  }],
2555
2523
  };
2556
2524
  }
2557
2525
 
2558
- // Apply resolutions
2559
- const unique = [...new Set(toResolve)];
2560
- await resolveObservations(unique, 'resolved');
2526
+ const result = await applyDeduplicationPlan(getObservationStore(), project.id, plan.resolveIds, reader);
2561
2527
 
2562
2528
  return {
2563
2529
  content: [{
2564
2530
  type: 'text' as const,
2565
- text: `[CLEANUP] Deduplicated: resolved ${unique.length} memory(ies)\n\n${actions.join('\n')}`,
2531
+ text: `[CLEANUP] Deduplicated: resolved ${result.resolved.length} memory(ies)\n\n${actionLines.join('\n')}`,
2566
2532
  }],
2567
2533
  };
2568
2534
  },