@gethmy/mcp 2.3.4 → 2.4.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/dist/cli.js CHANGED
@@ -28063,6 +28063,334 @@ async function runLifecycleMaintenance(client3, workspaceId, projectId) {
28063
28063
  return result;
28064
28064
  }
28065
28065
 
28066
+ // src/memory-audit.ts
28067
+ init_dist();
28068
+ var EMBEDDINGS_MIGRATION_AT = Date.parse("2026-02-18T00:00:00Z");
28069
+ var MS_PER_DAY = 1000 * 60 * 60 * 24;
28070
+ var BATCH_SIZE = 100;
28071
+ var CONCURRENCY_LIMIT = 5;
28072
+ var BOILERPLATE_PATTERNS = [
28073
+ /^todo:?$/i,
28074
+ /^placeholder/i,
28075
+ /^\.\.\.$/,
28076
+ /^untitled/i,
28077
+ /^(note|memo|draft)\s*\d*$/i
28078
+ ];
28079
+ function isBoilerplate(title, content) {
28080
+ const t = title.trim();
28081
+ const c = content.trim();
28082
+ if (c.length === 0)
28083
+ return true;
28084
+ for (const pat of BOILERPLATE_PATTERNS) {
28085
+ if (pat.test(t))
28086
+ return true;
28087
+ }
28088
+ return false;
28089
+ }
28090
+ function scoreEntity(entity, relationCount, archiveBelow, deleteBelow) {
28091
+ const now = Date.now();
28092
+ const ageDays = (now - Date.parse(entity.created_at)) / MS_PER_DAY;
28093
+ const effectiveLastAccess = entity.last_accessed_at ?? entity.created_at;
28094
+ const lifecycle2 = evaluateLifecycle({
28095
+ memory_tier: entity.memory_tier,
28096
+ confidence: entity.confidence,
28097
+ access_count: entity.access_count,
28098
+ last_accessed_at: effectiveLastAccess,
28099
+ created_at: entity.created_at
28100
+ });
28101
+ const reasons = [];
28102
+ const legacyReasons = [];
28103
+ const confidence = Math.max(0, Math.min(1, entity.confidence)) * 25;
28104
+ const decay = Math.max(0, Math.min(1, lifecycle2.decay.score)) * 20;
28105
+ if (lifecycle2.decay.score < 0.2)
28106
+ reasons.push(`decay score ${lifecycle2.decay.score.toFixed(2)}`);
28107
+ const hasEmbedding = entity.embedding != null;
28108
+ const hasTags = (entity.tags?.length || 0) >= 1;
28109
+ const hasRelations = relationCount > 0;
28110
+ let structural = 0;
28111
+ if (hasEmbedding)
28112
+ structural += 6;
28113
+ if (hasTags)
28114
+ structural += 4;
28115
+ if (hasRelations)
28116
+ structural += 5;
28117
+ if (!hasEmbedding)
28118
+ reasons.push("no embedding");
28119
+ if (!hasTags)
28120
+ reasons.push("no tags");
28121
+ if (!hasRelations)
28122
+ reasons.push("no relations");
28123
+ let content = 0;
28124
+ const contentLen = entity.content?.length || 0;
28125
+ if (contentLen >= 80)
28126
+ content += 8;
28127
+ const titleOk = entity.title.trim().length >= 4 && !/^(untitled|draft|note)\b/i.test(entity.title.trim());
28128
+ if (titleOk)
28129
+ content += 4;
28130
+ if (!isBoilerplate(entity.title, entity.content))
28131
+ content += 3;
28132
+ if (contentLen < 80)
28133
+ reasons.push(`thin content (${contentLen} chars)`);
28134
+ if (isBoilerplate(entity.title, entity.content))
28135
+ reasons.push("boilerplate title/content");
28136
+ let tierAgeFit = 15;
28137
+ if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
28138
+ tierAgeFit = 0;
28139
+ reasons.push("stuck draft >60d never promoted");
28140
+ }
28141
+ if (entity.promoted_from_id) {
28142
+ tierAgeFit = Math.min(15, tierAgeFit + 5);
28143
+ }
28144
+ const access = Math.min(10, Math.log10((entity.access_count || 0) + 1) * 5);
28145
+ if (entity.access_count === 0 && ageDays > 14)
28146
+ reasons.push("never accessed");
28147
+ const raw = confidence + decay + structural + content + tierAgeFit + access;
28148
+ const score = Math.round(Math.max(0, Math.min(100, raw)));
28149
+ let legacy = false;
28150
+ if (entity.confidence === 1 && entity.access_count === 0 && ageDays > 30) {
28151
+ legacy = true;
28152
+ legacyReasons.push("default confidence never validated");
28153
+ }
28154
+ if (!hasEmbedding && Date.parse(entity.created_at) < EMBEDDINGS_MIGRATION_AT) {
28155
+ legacy = true;
28156
+ legacyReasons.push("pre-embeddings migration");
28157
+ }
28158
+ if (entity.memory_tier === "draft" && ageDays > 60 && !entity.promoted_from_id) {
28159
+ legacy = true;
28160
+ legacyReasons.push("stuck draft");
28161
+ }
28162
+ if (!hasTags && !hasRelations) {
28163
+ legacy = true;
28164
+ legacyReasons.push("no graph presence");
28165
+ }
28166
+ let bucket;
28167
+ if (score < deleteBelow)
28168
+ bucket = "delete";
28169
+ else if (score < archiveBelow)
28170
+ bucket = "archive";
28171
+ else if (score < 70)
28172
+ bucket = "review";
28173
+ else
28174
+ bucket = "keep";
28175
+ return {
28176
+ id: entity.id,
28177
+ title: entity.title,
28178
+ type: entity.type,
28179
+ tier: entity.memory_tier,
28180
+ ageDays: Math.round(ageDays),
28181
+ score,
28182
+ bucket,
28183
+ reasons,
28184
+ legacy,
28185
+ legacyReasons,
28186
+ subScores: {
28187
+ confidence: Math.round(confidence),
28188
+ decay: Math.round(decay),
28189
+ structural,
28190
+ content,
28191
+ tierAgeFit,
28192
+ access: Math.round(access)
28193
+ }
28194
+ };
28195
+ }
28196
+ async function runMemoryAudit(client3, workspaceId, projectId, options) {
28197
+ const dryRun = options?.dryRun !== false;
28198
+ const archiveBelow = options?.archiveBelow ?? 40;
28199
+ const deleteBelow = options?.deleteBelow ?? 20;
28200
+ const limit = options?.limit ?? 500;
28201
+ const report = {
28202
+ success: true,
28203
+ dryRun,
28204
+ timestamp: new Date().toISOString(),
28205
+ workspace: { id: workspaceId, projectId },
28206
+ summary: {
28207
+ totalEntities: 0,
28208
+ scanned: 0,
28209
+ keep: 0,
28210
+ review: 0,
28211
+ archive: 0,
28212
+ delete: 0,
28213
+ legacyCount: 0
28214
+ },
28215
+ actionsTaken: { flaggedReview: 0, archived: 0, deleted: 0 },
28216
+ distribution: { "0-20": 0, "20-40": 0, "40-70": 0, "70-100": 0 },
28217
+ legacyBreakdown: {
28218
+ defaultConfidence: 0,
28219
+ missingEmbedding: 0,
28220
+ stuckDraft: 0,
28221
+ noGraphPresence: 0
28222
+ },
28223
+ lowest: [],
28224
+ errors: [],
28225
+ healthReport: ""
28226
+ };
28227
+ const entities = [];
28228
+ let offset = 0;
28229
+ try {
28230
+ while (entities.length < limit) {
28231
+ const pageSize = Math.min(BATCH_SIZE, limit - entities.length);
28232
+ const result = await client3.listMemoryEntities({
28233
+ workspace_id: workspaceId,
28234
+ project_id: projectId,
28235
+ limit: pageSize,
28236
+ offset
28237
+ });
28238
+ const page = result.entities || [];
28239
+ if (page.length === 0)
28240
+ break;
28241
+ entities.push(...page);
28242
+ if (page.length < pageSize)
28243
+ break;
28244
+ offset += pageSize;
28245
+ }
28246
+ } catch (err) {
28247
+ report.errors.push({
28248
+ step: "fetch",
28249
+ message: `Failed to fetch entities: ${err.message}`
28250
+ });
28251
+ report.success = false;
28252
+ report.healthReport = renderReport(report);
28253
+ return report;
28254
+ }
28255
+ report.summary.totalEntities = entities.length;
28256
+ const relationCounts = new Map;
28257
+ for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
28258
+ const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
28259
+ const results = await Promise.allSettled(batch.map(async (e) => {
28260
+ const related = await client3.getRelatedEntities(e.id);
28261
+ const count = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
28262
+ return { id: e.id, count };
28263
+ }));
28264
+ for (const r of results) {
28265
+ if (r.status === "fulfilled") {
28266
+ relationCounts.set(r.value.id, r.value.count);
28267
+ }
28268
+ }
28269
+ }
28270
+ const audits = [];
28271
+ for (const entity of entities) {
28272
+ const relCount = relationCounts.get(entity.id) ?? 0;
28273
+ const audit = scoreEntity(entity, relCount, archiveBelow, deleteBelow);
28274
+ audits.push(audit);
28275
+ report.summary.scanned++;
28276
+ report.summary[audit.bucket]++;
28277
+ if (audit.legacy)
28278
+ report.summary.legacyCount++;
28279
+ if (audit.score < 20)
28280
+ report.distribution["0-20"]++;
28281
+ else if (audit.score < 40)
28282
+ report.distribution["20-40"]++;
28283
+ else if (audit.score < 70)
28284
+ report.distribution["40-70"]++;
28285
+ else
28286
+ report.distribution["70-100"]++;
28287
+ for (const reason of audit.legacyReasons) {
28288
+ if (reason.startsWith("default confidence"))
28289
+ report.legacyBreakdown.defaultConfidence++;
28290
+ else if (reason.startsWith("pre-embeddings"))
28291
+ report.legacyBreakdown.missingEmbedding++;
28292
+ else if (reason.startsWith("stuck draft"))
28293
+ report.legacyBreakdown.stuckDraft++;
28294
+ else if (reason.startsWith("no graph"))
28295
+ report.legacyBreakdown.noGraphPresence++;
28296
+ }
28297
+ }
28298
+ report.lowest = [...audits].sort((a, b) => a.score - b.score).slice(0, 10);
28299
+ if (!dryRun) {
28300
+ for (const audit of audits) {
28301
+ try {
28302
+ if (audit.bucket === "delete") {
28303
+ await client3.deleteMemoryEntity(audit.id);
28304
+ report.actionsTaken.deleted++;
28305
+ } else if (audit.bucket === "archive") {
28306
+ await client3.updateMemoryEntity(audit.id, {
28307
+ confidence: 0.25,
28308
+ metadata: {
28309
+ audit_archived_at: new Date().toISOString(),
28310
+ audit_score: audit.score,
28311
+ audit_reasons: audit.reasons
28312
+ }
28313
+ });
28314
+ report.actionsTaken.archived++;
28315
+ } else if (audit.bucket === "review") {
28316
+ await client3.updateMemoryEntity(audit.id, {
28317
+ metadata: {
28318
+ needs_review: true,
28319
+ audit_score: audit.score,
28320
+ audit_reasons: audit.reasons,
28321
+ audit_at: new Date().toISOString()
28322
+ }
28323
+ });
28324
+ report.actionsTaken.flaggedReview++;
28325
+ }
28326
+ } catch (err) {
28327
+ report.errors.push({
28328
+ entityId: audit.id,
28329
+ step: audit.bucket,
28330
+ message: err.message
28331
+ });
28332
+ }
28333
+ }
28334
+ }
28335
+ report.healthReport = renderReport(report);
28336
+ return report;
28337
+ }
28338
+ function renderReport(report) {
28339
+ const mode = report.dryRun ? "Dry Run (preview)" : "Executed";
28340
+ const s = report.summary;
28341
+ const lines = [
28342
+ `# Memory Quality Audit
28343
+ `,
28344
+ `**Mode:** ${mode} | **Scanned:** ${s.scanned}/${s.totalEntities} | **Legacy:** ${s.legacyCount}`,
28345
+ "",
28346
+ "## Distribution",
28347
+ `- 70-100 (keep): ${report.distribution["70-100"]}`,
28348
+ `- 40-69 (review): ${report.distribution["40-70"]}`,
28349
+ `- 20-39 (archive): ${report.distribution["20-40"]}`,
28350
+ `- 0-19 (delete): ${report.distribution["0-20"]}`,
28351
+ "",
28352
+ "## Buckets",
28353
+ `- **Keep:** ${s.keep}`,
28354
+ `- **Review:** ${s.review}${!report.dryRun ? ` (flagged ${report.actionsTaken.flaggedReview})` : ""}`,
28355
+ `- **Archive:** ${s.archive}${!report.dryRun ? ` (archived ${report.actionsTaken.archived})` : ""}`,
28356
+ `- **Delete:** ${s.delete}${!report.dryRun ? ` (deleted ${report.actionsTaken.deleted})` : ""}`,
28357
+ ""
28358
+ ];
28359
+ const l = report.legacyBreakdown;
28360
+ if (s.legacyCount > 0) {
28361
+ lines.push("## Legacy Breakdown");
28362
+ lines.push(`- Default confidence, never validated: ${l.defaultConfidence}`);
28363
+ lines.push(`- Pre-embeddings migration: ${l.missingEmbedding}`);
28364
+ lines.push(`- Stuck drafts (>60d, no promotion): ${l.stuckDraft}`);
28365
+ lines.push(`- No tags + no relations: ${l.noGraphPresence}`);
28366
+ lines.push("");
28367
+ }
28368
+ if (report.lowest.length > 0) {
28369
+ lines.push("## Lowest-Scoring (top 10)");
28370
+ lines.push("| Score | Bucket | Tier | Age | Title | Reasons |");
28371
+ lines.push("|-------|--------|------|-----|-------|---------|");
28372
+ for (const a of report.lowest) {
28373
+ const reasonStr = a.reasons.slice(0, 3).join(", ") || "—";
28374
+ const titleTrunc = a.title.length > 40 ? `${a.title.slice(0, 37)}...` : a.title;
28375
+ lines.push(`| ${a.score} | ${a.bucket} | ${a.tier} | ${a.ageDays}d | ${titleTrunc} | ${reasonStr} |`);
28376
+ }
28377
+ lines.push("");
28378
+ }
28379
+ if (report.errors.length > 0) {
28380
+ lines.push("## Errors");
28381
+ for (const e of report.errors.slice(0, 10)) {
28382
+ lines.push(`- **${e.step}${e.entityId ? ` ${e.entityId}` : ""}:** ${e.message}`);
28383
+ }
28384
+ lines.push("");
28385
+ }
28386
+ if (report.dryRun) {
28387
+ lines.push("---");
28388
+ lines.push("*Run with `dryRun: false` to flag review entries, archive low-quality memories, and delete worst offenders.*");
28389
+ }
28390
+ return lines.join(`
28391
+ `);
28392
+ }
28393
+
28066
28394
  // src/memory-cleanup.ts
28067
28395
  init_dist();
28068
28396
  var ALL_STEPS = [
@@ -28070,12 +28398,13 @@ var ALL_STEPS = [
28070
28398
  "consolidate",
28071
28399
  "orphans",
28072
28400
  "duplicates",
28073
- "backfill"
28401
+ "backfill",
28402
+ "audit"
28074
28403
  ];
28075
- var MS_PER_DAY = 1000 * 60 * 60 * 24;
28404
+ var MS_PER_DAY2 = 1000 * 60 * 60 * 24;
28076
28405
  var MAX_ENTITIES_FETCH = 200;
28077
28406
  var DUPLICATE_SIMILARITY_THRESHOLD = 0.85;
28078
- var CONCURRENCY_LIMIT = 5;
28407
+ var CONCURRENCY_LIMIT2 = 5;
28079
28408
  async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28080
28409
  const dryRun = options?.dryRun !== false;
28081
28410
  const steps = options?.steps ?? ALL_STEPS;
@@ -28231,6 +28560,44 @@ async function runMemoryCleanup(client3, workspaceId, projectId, options) {
28231
28560
  });
28232
28561
  }
28233
28562
  }
28563
+ if (steps.includes("audit")) {
28564
+ try {
28565
+ const auditReport = await runMemoryAudit(client3, workspaceId, projectId, {
28566
+ dryRun,
28567
+ archiveBelow: options?.auditArchiveBelow,
28568
+ deleteBelow: options?.auditDeleteBelow
28569
+ });
28570
+ const low = auditReport.lowest.length > 0 ? auditReport.lowest[0].score : null;
28571
+ report.steps.audit = {
28572
+ scanned: auditReport.summary.scanned,
28573
+ legacyCount: auditReport.summary.legacyCount,
28574
+ buckets: {
28575
+ keep: auditReport.summary.keep,
28576
+ review: auditReport.summary.review,
28577
+ archive: auditReport.summary.archive,
28578
+ delete: auditReport.summary.delete
28579
+ },
28580
+ actions: auditReport.actionsTaken,
28581
+ lowestScore: low,
28582
+ report: auditReport
28583
+ };
28584
+ report.summary.issuesFound += auditReport.summary.review + auditReport.summary.archive + auditReport.summary.delete;
28585
+ if (!dryRun) {
28586
+ report.summary.actionsTaken += auditReport.actionsTaken.flaggedReview + auditReport.actionsTaken.archived + auditReport.actionsTaken.deleted;
28587
+ }
28588
+ for (const err of auditReport.errors) {
28589
+ report.errors.push({
28590
+ step: `audit:${err.step}`,
28591
+ message: err.entityId ? `${err.entityId}: ${err.message}` : err.message
28592
+ });
28593
+ }
28594
+ } catch (err) {
28595
+ report.errors.push({
28596
+ step: "audit",
28597
+ message: err.message
28598
+ });
28599
+ }
28600
+ }
28234
28601
  report.healthReport = generateHealthReport(report);
28235
28602
  return report;
28236
28603
  }
@@ -28239,7 +28606,7 @@ function runPruneStep(entities, maxAgeDays) {
28239
28606
  const drafts = entities.filter((e) => e.memory_tier === "draft");
28240
28607
  const stale = [];
28241
28608
  for (const entity of drafts) {
28242
- const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
28609
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
28243
28610
  if (ageDays < maxAgeDays)
28244
28611
  continue;
28245
28612
  const lifecycle2 = evaluateLifecycle(entity);
@@ -28260,17 +28627,17 @@ async function runOrphanStep(client3, entities, orphanAgeDays) {
28260
28627
  return false;
28261
28628
  if (e.access_count >= 2)
28262
28629
  return false;
28263
- const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY;
28630
+ const ageDays = (now - new Date(e.created_at).getTime()) / MS_PER_DAY2;
28264
28631
  return ageDays >= orphanAgeDays;
28265
28632
  });
28266
- for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT) {
28267
- const batch = candidates.slice(i, i + CONCURRENCY_LIMIT);
28633
+ for (let i = 0;i < candidates.length; i += CONCURRENCY_LIMIT2) {
28634
+ const batch = candidates.slice(i, i + CONCURRENCY_LIMIT2);
28268
28635
  const results = await Promise.allSettled(batch.map(async (entity) => {
28269
28636
  const related = await client3.getRelatedEntities(entity.id);
28270
28637
  const totalRelations = (related.outgoing?.length || 0) + (related.incoming?.length || 0);
28271
28638
  if (totalRelations > 0)
28272
28639
  return null;
28273
- const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY;
28640
+ const ageDays = (now - new Date(entity.created_at).getTime()) / MS_PER_DAY2;
28274
28641
  return {
28275
28642
  id: entity.id,
28276
28643
  title: entity.title,
@@ -28299,8 +28666,8 @@ async function runDuplicateStep(client3, entities, workspaceId, projectId) {
28299
28666
  const flaggedForRemoval = new Set;
28300
28667
  const entityMap = new Map(entities.map((e) => [e.id, e]));
28301
28668
  const similarityMap = new Map;
28302
- for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT) {
28303
- const batch = entities.slice(i, i + CONCURRENCY_LIMIT);
28669
+ for (let i = 0;i < entities.length; i += CONCURRENCY_LIMIT2) {
28670
+ const batch = entities.slice(i, i + CONCURRENCY_LIMIT2);
28304
28671
  const results = await Promise.allSettled(batch.map(async (entity) => {
28305
28672
  const similar = await findSimilarEntities(client3, entity.title, entity.content, workspaceId, { projectId, limit: 5, minRrfScore: 0.05, excludeIds: [entity.id] });
28306
28673
  return { entityId: entity.id, similar };
@@ -28452,6 +28819,20 @@ function generateHealthReport(report) {
28452
28819
  `);
28453
28820
  }
28454
28821
  }
28822
+ if (report.steps.audit) {
28823
+ const a = report.steps.audit;
28824
+ lines.push("## Quality Audit");
28825
+ lines.push(`Scanned ${a.scanned} entities. Legacy signals on ${a.legacyCount}.`);
28826
+ lines.push(`Buckets — keep: ${a.buckets.keep}, review: ${a.buckets.review}, archive: ${a.buckets.archive}, delete: ${a.buckets.delete}.`);
28827
+ if (!report.dryRun) {
28828
+ lines.push(`Actions — flagged: ${a.actions.flaggedReview}, archived: ${a.actions.archived}, deleted: ${a.actions.deleted}.`);
28829
+ }
28830
+ if (a.report.lowest.length > 0) {
28831
+ const worst = a.report.lowest[0];
28832
+ lines.push(`Lowest score: **${worst.score}** — "${worst.title}" (${worst.reasons.slice(0, 2).join(", ") || "—"}).`);
28833
+ }
28834
+ lines.push("");
28835
+ }
28455
28836
  if (report.errors.length > 0) {
28456
28837
  lines.push("## Errors");
28457
28838
  for (const e of report.errors) {
@@ -28496,7 +28877,7 @@ async function purgeMemories(client3, workspaceId, projectId, options) {
28496
28877
  continue;
28497
28878
  if (filters.olderThanDays !== undefined) {
28498
28879
  const ref = entity.last_accessed_at || entity.created_at;
28499
- const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY;
28880
+ const ageDays = (now - new Date(ref).getTime()) / MS_PER_DAY2;
28500
28881
  if (ageDays < filters.olderThanDays)
28501
28882
  continue;
28502
28883
  }
@@ -28512,13 +28893,13 @@ async function purgeMemories(client3, workspaceId, projectId, options) {
28512
28893
  type: e.type,
28513
28894
  tier: e.memory_tier,
28514
28895
  confidence: e.confidence,
28515
- ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY)
28896
+ ageDays: Math.round((now - new Date(e.last_accessed_at || e.created_at).getTime()) / MS_PER_DAY2)
28516
28897
  }));
28517
28898
  const errors3 = [];
28518
28899
  let purged = 0;
28519
28900
  if (!dryRun) {
28520
- for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT) {
28521
- const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT);
28901
+ for (let i = 0;i < allMatches.length; i += CONCURRENCY_LIMIT2) {
28902
+ const batch = allMatches.slice(i, i + CONCURRENCY_LIMIT2);
28522
28903
  const results = await Promise.allSettled(batch.map((e) => client3.deleteMemoryEntity(e.id)));
28523
28904
  for (let j = 0;j < results.length; j++) {
28524
28905
  if (results[j].status === "fulfilled") {
@@ -29995,7 +30376,7 @@ var TOOLS = {
29995
30376
  }
29996
30377
  },
29997
30378
  harmony_cleanup_memories: {
29998
- description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, and backfill embeddings. Returns a health report. Dry-run by default — run with dryRun=false to execute.",
30379
+ description: "Run a unified memory cleanup: prune stale drafts, consolidate similar memories, detect orphans and duplicates, backfill embeddings, and optionally run a quality audit. Returns a health report. Dry-run by default — run with dryRun=false to execute.",
29999
30380
  inputSchema: {
30000
30381
  type: "object",
30001
30382
  properties: {
@@ -30015,9 +30396,16 @@ var TOOLS = {
30015
30396
  type: "array",
30016
30397
  items: {
30017
30398
  type: "string",
30018
- enum: ["prune", "consolidate", "orphans", "duplicates", "backfill"]
30399
+ enum: [
30400
+ "prune",
30401
+ "consolidate",
30402
+ "orphans",
30403
+ "duplicates",
30404
+ "backfill",
30405
+ "audit"
30406
+ ]
30019
30407
  },
30020
- description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill."
30408
+ description: "Which cleanup steps to run (default: all). Options: prune, consolidate, orphans, duplicates, backfill, audit."
30021
30409
  },
30022
30410
  maxAgeDays: {
30023
30411
  type: "number",
@@ -30030,6 +30418,47 @@ var TOOLS = {
30030
30418
  orphanAgeDays: {
30031
30419
  type: "number",
30032
30420
  description: "Min age in days for orphan detection (default: 14)"
30421
+ },
30422
+ auditArchiveBelow: {
30423
+ type: "number",
30424
+ description: "Audit: archive entities scoring below this (default: 40)"
30425
+ },
30426
+ auditDeleteBelow: {
30427
+ type: "number",
30428
+ description: "Audit: delete entities scoring below this (default: 20)"
30429
+ }
30430
+ },
30431
+ required: []
30432
+ }
30433
+ },
30434
+ harmony_audit_memories: {
30435
+ description: "Rate every memory against state-of-the-art quality standards (confidence, decay, structural completeness, content, tier-age fit, access). Flags legacy entities from before recent optimizations (default confidence, missing embeddings, stuck drafts). Buckets: keep (≥70), review (40-69), archive (20-39), delete (<20). Dry-run by default.",
30436
+ inputSchema: {
30437
+ type: "object",
30438
+ properties: {
30439
+ workspaceId: {
30440
+ type: "string",
30441
+ description: "Workspace ID (optional if context set)"
30442
+ },
30443
+ projectId: {
30444
+ type: "string",
30445
+ description: "Project ID (optional)"
30446
+ },
30447
+ dryRun: {
30448
+ type: "boolean",
30449
+ description: "Preview audit without flagging/archiving/deleting (default: true)"
30450
+ },
30451
+ archiveBelow: {
30452
+ type: "number",
30453
+ description: "Score threshold below which entities are archived (confidence set to 0.25). Default: 40"
30454
+ },
30455
+ deleteBelow: {
30456
+ type: "number",
30457
+ description: "Score threshold below which entities are hard-deleted. Default: 20. Set to 0 to never delete."
30458
+ },
30459
+ limit: {
30460
+ type: "number",
30461
+ description: "Max number of entities to audit (default: 500). Paginated fetch."
30033
30462
  }
30034
30463
  },
30035
30464
  required: []
@@ -31599,6 +32028,30 @@ async function handleToolCall(name, args, deps) {
31599
32028
  message: `Processed ${entitiesProcessed} entities, created ${relationsCreated} new relations.`
31600
32029
  };
31601
32030
  }
32031
+ case "harmony_audit_memories": {
32032
+ const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
32033
+ if (!workspaceId) {
32034
+ throw new Error("No workspace specified. Use harmony_set_workspace_context or provide workspaceId.");
32035
+ }
32036
+ const projectId = args.projectId || deps.getActiveProjectId() || undefined;
32037
+ const report = await runMemoryAudit(client3, workspaceId, projectId, {
32038
+ dryRun: args.dryRun,
32039
+ archiveBelow: args.archiveBelow,
32040
+ deleteBelow: args.deleteBelow,
32041
+ limit: args.limit
32042
+ });
32043
+ return {
32044
+ success: report.success,
32045
+ dryRun: report.dryRun,
32046
+ summary: report.summary,
32047
+ distribution: report.distribution,
32048
+ legacyBreakdown: report.legacyBreakdown,
32049
+ actionsTaken: report.actionsTaken,
32050
+ lowest: report.lowest,
32051
+ errors: report.errors,
32052
+ healthReport: report.healthReport
32053
+ };
32054
+ }
31602
32055
  case "harmony_cleanup_memories": {
31603
32056
  const workspaceId = args.workspaceId || deps.getActiveWorkspaceId();
31604
32057
  if (!workspaceId) {
@@ -31610,7 +32063,8 @@ async function handleToolCall(name, args, deps) {
31610
32063
  "consolidate",
31611
32064
  "orphans",
31612
32065
  "duplicates",
31613
- "backfill"
32066
+ "backfill",
32067
+ "audit"
31614
32068
  ];
31615
32069
  const rawSteps = args.steps;
31616
32070
  const steps = rawSteps?.filter((s) => validSteps.includes(s));
@@ -31623,7 +32077,9 @@ async function handleToolCall(name, args, deps) {
31623
32077
  steps,
31624
32078
  maxAgeDays: args.maxAgeDays,
31625
32079
  minClusterSize: args.minClusterSize,
31626
- orphanAgeDays: args.orphanAgeDays
32080
+ orphanAgeDays: args.orphanAgeDays,
32081
+ auditArchiveBelow: args.auditArchiveBelow,
32082
+ auditDeleteBelow: args.auditDeleteBelow
31627
32083
  });
31628
32084
  return {
31629
32085
  success: report.success,