@ghostpaw/affinity 0.1.6 → 0.2.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/index.js CHANGED
@@ -388,11 +388,32 @@ function initContactMergesTables(db) {
388
388
  );
389
389
  }
390
390
 
391
+ // src/merges/init_dismissed_duplicates_tables.ts
392
+ function initDismissedDuplicatesTables(db) {
393
+ db.exec(`
394
+ CREATE TABLE IF NOT EXISTS dismissed_duplicates (
395
+ left_id INTEGER NOT NULL,
396
+ right_id INTEGER NOT NULL,
397
+ reason TEXT,
398
+ dismissed_at INTEGER NOT NULL,
399
+ PRIMARY KEY (left_id, right_id),
400
+ CHECK (left_id < right_id)
401
+ )
402
+ `);
403
+ db.exec(
404
+ "CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_left ON dismissed_duplicates(left_id)"
405
+ );
406
+ db.exec(
407
+ "CREATE INDEX IF NOT EXISTS idx_dismissed_duplicates_right ON dismissed_duplicates(right_id)"
408
+ );
409
+ }
410
+
391
411
  // src/init_affinity_tables.ts
392
412
  function initAffinityTables(db) {
393
413
  initContactsTables(db);
394
414
  initContactRollupsTables(db);
395
415
  initContactMergesTables(db);
416
+ initDismissedDuplicatesTables(db);
396
417
  initIdentitiesTables(db);
397
418
  initLinksTables(db);
398
419
  initLinkRollupsTables(db);
@@ -500,6 +521,7 @@ __export(read_exports, {
500
521
  getMergeHistory: () => getMergeHistory,
501
522
  getOwnerProfile: () => getOwnerProfile,
502
523
  listContacts: () => listContacts,
524
+ listDismissedDuplicates: () => listDismissedDuplicates,
503
525
  listDuplicateCandidates: () => listDuplicateCandidates,
504
526
  listMoments: () => listMoments,
505
527
  listObservedLinks: () => listObservedLinks,
@@ -1352,8 +1374,24 @@ function listDuplicateCandidates(db, filters, options) {
1352
1374
  }
1353
1375
  }
1354
1376
  }
1377
+ const dismissedRows = db.prepare("SELECT left_id, right_id FROM dismissed_duplicates").all();
1378
+ const dismissedSet = new Set(
1379
+ dismissedRows.map((r) => pairKey(r.left_id, r.right_id))
1380
+ );
1381
+ const includeDismissed = filters?.includeDismissed === true;
1382
+ for (const candidate of pairs.values()) {
1383
+ if (dismissedSet.has(
1384
+ pairKey(candidate.leftContactId, candidate.rightContactId)
1385
+ )) {
1386
+ candidate.dismissed = true;
1387
+ }
1388
+ }
1355
1389
  const minScore = filters?.minScore ?? 0;
1356
- return [...pairs.values()].filter((candidate) => candidate.matchScore >= minScore).sort((left, right) => {
1390
+ return [...pairs.values()].filter((candidate) => {
1391
+ if (candidate.matchScore < minScore) return false;
1392
+ if (candidate.dismissed === true && !includeDismissed) return false;
1393
+ return true;
1394
+ }).sort((left, right) => {
1357
1395
  const leftExact = left.matchScore === 1 ? 1 : 0;
1358
1396
  const rightExact = right.matchScore === 1 ? 1 : 0;
1359
1397
  return rightExact - leftExact || right.matchScore - left.matchScore;
@@ -3416,6 +3454,12 @@ function mergeContacts(db, input) {
3416
3454
  `INSERT INTO contact_merges (winner_contact_id, loser_contact_id, merged_at, reason_summary, manual)
3417
3455
  VALUES (?, ?, ?, ?, 1)`
3418
3456
  ).run(winnerId, loserId, now, reasonSummary);
3457
+ db.prepare(
3458
+ "DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
3459
+ ).run(loserId, loserId);
3460
+ db.prepare(
3461
+ "DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
3462
+ ).run(winnerId, winnerId);
3419
3463
  db.prepare(
3420
3464
  "DELETE FROM link_rollups WHERE link_id IN (SELECT id FROM links WHERE removed_at IS NOT NULL)"
3421
3465
  ).run();
@@ -3443,6 +3487,117 @@ function mergeContacts(db, input) {
3443
3487
  });
3444
3488
  }
3445
3489
 
3490
+ // src/merges/receipts.ts
3491
+ function buildDismissalMutationReceipt(primary, partial) {
3492
+ const normalized = normalizeMutationReceiptPartial(partial);
3493
+ return {
3494
+ primary,
3495
+ created: normalized.created,
3496
+ updated: normalized.updated,
3497
+ archived: normalized.archived,
3498
+ removed: normalized.removed,
3499
+ affectedLinks: normalized.affectedLinks,
3500
+ derivedEffects: normalized.derivedEffects
3501
+ };
3502
+ }
3503
+
3504
+ // src/merges/dismiss_duplicate.ts
3505
+ function canonicalize(a, b) {
3506
+ return {
3507
+ leftContactId: Math.min(a, b),
3508
+ rightContactId: Math.max(a, b)
3509
+ };
3510
+ }
3511
+ function dismissDuplicate(db, leftContactId, rightContactId, reason, now) {
3512
+ return withTransaction(db, () => {
3513
+ if (leftContactId === rightContactId) {
3514
+ throw new AffinityValidationError(
3515
+ "cannot dismiss a contact as a duplicate of itself"
3516
+ );
3517
+ }
3518
+ const left = getContactRowById(db, leftContactId);
3519
+ if (!left) {
3520
+ throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
3521
+ }
3522
+ if (left.lifecycle_state === "merged") {
3523
+ throw new AffinityStateError(
3524
+ `contact ${leftContactId} is merged and cannot be dismissed`
3525
+ );
3526
+ }
3527
+ const right = getContactRowById(db, rightContactId);
3528
+ if (!right) {
3529
+ throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
3530
+ }
3531
+ if (right.lifecycle_state === "merged") {
3532
+ throw new AffinityStateError(
3533
+ `contact ${rightContactId} is merged and cannot be dismissed`
3534
+ );
3535
+ }
3536
+ const pair = canonicalize(leftContactId, rightContactId);
3537
+ const resolvedNow = resolveNow(now);
3538
+ const resolvedReason = reason === void 0 || reason === null ? null : (() => {
3539
+ const t = reason.trim();
3540
+ return t.length === 0 ? null : t;
3541
+ })();
3542
+ db.prepare(
3543
+ `INSERT INTO dismissed_duplicates (left_id, right_id, reason, dismissed_at)
3544
+ VALUES (?, ?, ?, ?)
3545
+ ON CONFLICT(left_id, right_id) DO UPDATE SET
3546
+ reason = excluded.reason,
3547
+ dismissed_at = excluded.dismissed_at`
3548
+ ).run(pair.leftContactId, pair.rightContactId, resolvedReason, resolvedNow);
3549
+ return buildDismissalMutationReceipt(pair);
3550
+ });
3551
+ }
3552
+
3553
+ // src/merges/undismiss_duplicate.ts
3554
+ function canonicalize2(a, b) {
3555
+ return {
3556
+ leftContactId: Math.min(a, b),
3557
+ rightContactId: Math.max(a, b)
3558
+ };
3559
+ }
3560
+ function undismissDuplicate(db, leftContactId, rightContactId) {
3561
+ return withTransaction(db, () => {
3562
+ if (leftContactId === rightContactId) {
3563
+ throw new AffinityValidationError(
3564
+ "cannot undismiss a contact from itself"
3565
+ );
3566
+ }
3567
+ const left = getContactRowById(db, leftContactId);
3568
+ if (!left) {
3569
+ throw new AffinityNotFoundError(`contact ${leftContactId} not found`);
3570
+ }
3571
+ const right = getContactRowById(db, rightContactId);
3572
+ if (!right) {
3573
+ throw new AffinityNotFoundError(`contact ${rightContactId} not found`);
3574
+ }
3575
+ const pair = canonicalize2(leftContactId, rightContactId);
3576
+ db.prepare(
3577
+ "DELETE FROM dismissed_duplicates WHERE left_id = ? AND right_id = ?"
3578
+ ).run(pair.leftContactId, pair.rightContactId);
3579
+ return buildDismissalMutationReceipt(pair);
3580
+ });
3581
+ }
3582
+
3583
+ // src/merges/list_dismissed_duplicates.ts
3584
+ function listDismissedDuplicates(db, options) {
3585
+ assertDefaultOrdering("listDismissedDuplicates", options);
3586
+ const { limit, offset } = resolveListLimitOffset(options);
3587
+ const rows = db.prepare(
3588
+ `SELECT left_id, right_id, reason, dismissed_at
3589
+ FROM dismissed_duplicates
3590
+ ORDER BY dismissed_at DESC
3591
+ LIMIT ? OFFSET ?`
3592
+ ).all(limit, offset);
3593
+ return rows.map((row) => ({
3594
+ leftContactId: row.left_id,
3595
+ rightContactId: row.right_id,
3596
+ reason: row.reason,
3597
+ dismissedAt: row.dismissed_at
3598
+ }));
3599
+ }
3600
+
3446
3601
  // src/events/get_contact_journal.ts
3447
3602
  function getContactJournal(db, contactId, options) {
3448
3603
  assertDefaultOrdering("getContactJournal", options);
@@ -5331,6 +5486,9 @@ __export(tools_exports, {
5331
5486
  manageDateAnchorTool: () => manageDateAnchorTool,
5332
5487
  manageDateAnchorToolHandler: () => manageDateAnchorToolHandler,
5333
5488
  manageDateAnchorToolName: () => manageDateAnchorToolName,
5489
+ manageDuplicateDismissalTool: () => manageDuplicateDismissalTool,
5490
+ manageDuplicateDismissalToolHandler: () => manageDuplicateDismissalToolHandler,
5491
+ manageDuplicateDismissalToolName: () => manageDuplicateDismissalToolName,
5334
5492
  manageIdentityTool: () => manageIdentityTool,
5335
5493
  manageIdentityToolHandler: () => manageIdentityToolHandler,
5336
5494
  manageIdentityToolName: () => manageIdentityToolName,
@@ -5453,6 +5611,7 @@ var reviewAffinityToolName = "review_affinity";
5453
5611
  var inspectAffinityItemToolName = "inspect_affinity_item";
5454
5612
  var manageContactToolName = "manage_contact";
5455
5613
  var mergeContactsToolName = "merge_contacts";
5614
+ var manageDuplicateDismissalToolName = "manage_duplicate_dismissal";
5456
5615
  var manageIdentityToolName = "manage_identity";
5457
5616
  var manageRelationshipToolName = "manage_relationship";
5458
5617
  var recordEventToolName = "record_event";
@@ -6704,7 +6863,7 @@ var manageContactTool = defineAffinityTool({
6704
6863
  handler: manageContactToolHandler
6705
6864
  });
6706
6865
 
6707
- // src/tools/manage_date_anchor_tool.ts
6866
+ // src/tools/manage_duplicate_dismissal_tool.ts
6708
6867
  function contactLocatorSchema4(description) {
6709
6868
  return {
6710
6869
  type: "object",
@@ -6722,6 +6881,86 @@ function contactLocatorSchema4(description) {
6722
6881
  description
6723
6882
  };
6724
6883
  }
6884
+ function manageDuplicateDismissalToolHandler(db, input) {
6885
+ return withToolHandling(() => {
6886
+ const left = resolveContactLocator(db, input.left, "left");
6887
+ if (!left.ok) {
6888
+ return left.result;
6889
+ }
6890
+ const right = resolveContactLocator(db, input.right, "right");
6891
+ if (!right.ok) {
6892
+ return right.result;
6893
+ }
6894
+ if (input.action === "dismiss") {
6895
+ const receipt2 = dismissDuplicate(
6896
+ db,
6897
+ left.value.id,
6898
+ right.value.id,
6899
+ input.reason ?? null
6900
+ );
6901
+ return toolSuccess(
6902
+ `Dismissed pair #${receipt2.primary.leftContactId} / #${receipt2.primary.rightContactId}.`,
6903
+ { action: "dismiss", primary: receipt2.primary }
6904
+ );
6905
+ }
6906
+ const receipt = undismissDuplicate(db, left.value.id, right.value.id);
6907
+ return toolSuccess(
6908
+ `Un-dismissed pair #${receipt.primary.leftContactId} / #${receipt.primary.rightContactId}.`,
6909
+ { action: "undismiss", primary: receipt.primary }
6910
+ );
6911
+ }, "Duplicate dismissal failed.");
6912
+ }
6913
+ var manageDuplicateDismissalTool = defineAffinityTool({
6914
+ name: manageDuplicateDismissalToolName,
6915
+ description: "Dismiss or un-dismiss a duplicate candidate pair so it does not resurface in listDuplicateCandidates.",
6916
+ whenToUse: "Use dismiss after confirming two contacts are different people (not duplicates). Use undismiss to re-surface a pair for re-evaluation.",
6917
+ whenNotToUse: "Do not use this to merge contacts \u2014 use merge_contacts for confirmed duplicates.",
6918
+ sideEffects: "writes_state",
6919
+ readOnly: false,
6920
+ supportsClarification: true,
6921
+ targetKinds: ["contact"],
6922
+ inputDescriptions: {
6923
+ action: "dismiss to suppress the pair; undismiss to re-surface it.",
6924
+ left: "One contact of the pair.",
6925
+ right: "The other contact of the pair.",
6926
+ reason: "Optional reason for the dismissal (audit trail)."
6927
+ },
6928
+ outputDescription: "Returns the dismissal receipt with the canonical pair identifiers.",
6929
+ inputSchema: objectSchema(
6930
+ {
6931
+ action: enumSchema("Action to perform.", ["dismiss", "undismiss"]),
6932
+ left: contactLocatorSchema4(
6933
+ "Left contact. Provide contactId or identity."
6934
+ ),
6935
+ right: contactLocatorSchema4(
6936
+ "Right contact. Provide contactId or identity."
6937
+ ),
6938
+ reason: nullableStringSchema("Optional reason for the dismissal.")
6939
+ },
6940
+ ["action", "left", "right"],
6941
+ "Dismiss or un-dismiss a duplicate candidate pair."
6942
+ ),
6943
+ handler: manageDuplicateDismissalToolHandler
6944
+ });
6945
+
6946
+ // src/tools/manage_date_anchor_tool.ts
6947
+ function contactLocatorSchema5(description) {
6948
+ return {
6949
+ type: "object",
6950
+ properties: {
6951
+ contactId: integerSchema("Exact contact id."),
6952
+ identity: objectSchema(
6953
+ {
6954
+ type: stringSchema("Identity type."),
6955
+ value: stringSchema("Identity value.")
6956
+ },
6957
+ ["type", "value"],
6958
+ "Contact identity locator."
6959
+ )
6960
+ },
6961
+ description
6962
+ };
6963
+ }
6725
6964
  function linkLocatorSchema3(description) {
6726
6965
  return {
6727
6966
  type: "object",
@@ -6812,7 +7051,7 @@ var manageDateAnchorTool = defineAffinityTool({
6812
7051
  target: objectSchema(
6813
7052
  {
6814
7053
  kind: enumSchema("Target kind.", ["contact", "link"]),
6815
- contact: contactLocatorSchema4(
7054
+ contact: contactLocatorSchema5(
6816
7055
  "Contact locator. Required when kind=contact. Provide contactId or identity."
6817
7056
  ),
6818
7057
  link: linkLocatorSchema3(
@@ -6966,6 +7205,9 @@ function addIdentity(db, contactId, input) {
6966
7205
  throw new AffinityInvariantError("inserted identity not found");
6967
7206
  }
6968
7207
  refreshContactRollup(db, contactId, now);
7208
+ db.prepare(
7209
+ "DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
7210
+ ).run(contactId, contactId);
6969
7211
  return buildIdentityMutationReceipt(mapIdentityRowToIdentityRecord(row), {
6970
7212
  created: [{ kind: "identity", id }]
6971
7213
  });
@@ -6989,6 +7231,9 @@ function removeIdentity(db, identityId, removedAt) {
6989
7231
  "UPDATE identities SET removed_at = ?, updated_at = ? WHERE id = ?"
6990
7232
  ).run(now, now, identityId);
6991
7233
  refreshContactRollup(db, row.contact_id, now);
7234
+ db.prepare(
7235
+ "DELETE FROM dismissed_duplicates WHERE left_id = ? OR right_id = ?"
7236
+ ).run(row.contact_id, row.contact_id);
6992
7237
  return buildIdentityMutationReceipt(primary, {
6993
7238
  removed: [{ kind: "identity", id: identityId }]
6994
7239
  });
@@ -7067,7 +7312,7 @@ function verifyIdentity(db, identityId, verifiedAt) {
7067
7312
  }
7068
7313
 
7069
7314
  // src/tools/manage_identity_tool.ts
7070
- function contactLocatorSchema5(description) {
7315
+ function contactLocatorSchema6(description) {
7071
7316
  return {
7072
7317
  type: "object",
7073
7318
  properties: {
@@ -7146,7 +7391,7 @@ var manageIdentityTool = defineAffinityTool({
7146
7391
  "verify",
7147
7392
  "remove"
7148
7393
  ]),
7149
- contact: contactLocatorSchema5(
7394
+ contact: contactLocatorSchema6(
7150
7395
  "Target contact. Required when action=add. Provide contactId or identity."
7151
7396
  ),
7152
7397
  input: objectSchema(
@@ -7186,7 +7431,7 @@ var manageIdentityTool = defineAffinityTool({
7186
7431
  });
7187
7432
 
7188
7433
  // src/tools/manage_relationship_tool.ts
7189
- function contactLocatorSchema6(description) {
7434
+ function contactLocatorSchema7(description) {
7190
7435
  return {
7191
7436
  type: "object",
7192
7437
  properties: {
@@ -7342,10 +7587,10 @@ var manageRelationshipTool = defineAffinityTool({
7342
7587
  "set_structural_tie",
7343
7588
  "remove_structural_tie"
7344
7589
  ]),
7345
- from: contactLocatorSchema6(
7590
+ from: contactLocatorSchema7(
7346
7591
  "Source contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
7347
7592
  ),
7348
- to: contactLocatorSchema6(
7593
+ to: contactLocatorSchema7(
7349
7594
  "Target contact. Required when action=seed_social_link or set_structural_tie. Provide contactId or identity."
7350
7595
  ),
7351
7596
  kind: stringSchema(
@@ -7387,7 +7632,7 @@ var manageRelationshipTool = defineAffinityTool({
7387
7632
  });
7388
7633
 
7389
7634
  // src/tools/merge_contacts_tool.ts
7390
- function contactLocatorSchema7(description) {
7635
+ function contactLocatorSchema8(description) {
7391
7636
  return {
7392
7637
  type: "object",
7393
7638
  properties: {
@@ -7444,10 +7689,10 @@ var mergeContactsTool = defineAffinityTool({
7444
7689
  outputDescription: "Returns the merge receipt with rewired entities and affected-link information.",
7445
7690
  inputSchema: objectSchema(
7446
7691
  {
7447
- winner: contactLocatorSchema7(
7692
+ winner: contactLocatorSchema8(
7448
7693
  "Winner contact. Provide contactId or identity."
7449
7694
  ),
7450
- loser: contactLocatorSchema7(
7695
+ loser: contactLocatorSchema8(
7451
7696
  "Loser contact. Provide contactId or identity."
7452
7697
  ),
7453
7698
  reasonSummary: stringSchema("Optional merge reason."),
@@ -7618,7 +7863,7 @@ function buildChartOptions(input) {
7618
7863
  ...input.contactIds === void 0 ? {} : { contactIds: input.contactIds }
7619
7864
  };
7620
7865
  }
7621
- function contactLocatorSchema8(description) {
7866
+ function contactLocatorSchema9(description) {
7622
7867
  return {
7623
7868
  type: "object",
7624
7869
  properties: {
@@ -7709,6 +7954,14 @@ function validateReviewInput(input) {
7709
7954
  "Use `contactIds` only with duplicate-candidate or chart review views."
7710
7955
  );
7711
7956
  }
7957
+ if (input.includeDismissed !== void 0 && input.view !== "contacts.duplicates") {
7958
+ return toolFailure(
7959
+ "protocol",
7960
+ "invalid_input",
7961
+ "The `includeDismissed` filter is not valid for this review view.",
7962
+ "Use `includeDismissed` only with the contacts.duplicates review view."
7963
+ );
7964
+ }
7712
7965
  return null;
7713
7966
  }
7714
7967
  function duplicateCandidateItem(candidate) {
@@ -7765,6 +8018,15 @@ function mergeHistoryItem(record) {
7765
8018
  snippet: record.reasonSummary ?? void 0
7766
8019
  };
7767
8020
  }
8021
+ function dismissedPairItem(record) {
8022
+ return {
8023
+ kind: "contact",
8024
+ id: record.leftContactId,
8025
+ title: `Dismissed pair: #${record.leftContactId} and #${record.rightContactId}`,
8026
+ subtitle: record.reason ?? "no reason given",
8027
+ snippet: `dismissed at ${record.dismissedAt}`
8028
+ };
8029
+ }
7768
8030
  function compactFilters(input) {
7769
8031
  return Object.fromEntries(
7770
8032
  Object.entries(input).filter(
@@ -7800,7 +8062,8 @@ function reviewAffinityToolHandler(db, input) {
7800
8062
  {
7801
8063
  ...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
7802
8064
  ...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
7803
- ...input.minScore === void 0 ? {} : { minScore: input.minScore }
8065
+ ...input.minScore === void 0 ? {} : { minScore: input.minScore },
8066
+ ...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
7804
8067
  },
7805
8068
  options
7806
8069
  ).map(duplicateCandidateItem);
@@ -7981,6 +8244,9 @@ function reviewAffinityToolHandler(db, input) {
7981
8244
  );
7982
8245
  break;
7983
8246
  }
8247
+ case "merges.dismissed":
8248
+ items = listDismissedDuplicates(db, options).map(dismissedPairItem);
8249
+ break;
7984
8250
  }
7985
8251
  return toolSuccess(
7986
8252
  summarizeCount(items.length, "item"),
@@ -8031,6 +8297,7 @@ var reviewAffinityTool = defineAffinityTool({
8031
8297
  horizonDays: "Optional horizon filter for commitments or upcoming dates.",
8032
8298
  exactOnly: "Whether duplicate review should only include exact identity matches.",
8033
8299
  minScore: "Optional minimum duplicate score.",
8300
+ includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
8034
8301
  since: "Optional lower timestamp bound for time-based views.",
8035
8302
  until: "Optional upper timestamp bound for time-based views.",
8036
8303
  limit: "Optional result limit.",
@@ -8052,9 +8319,10 @@ var reviewAffinityTool = defineAffinityTool({
8052
8319
  "links.radar",
8053
8320
  "dates.upcoming",
8054
8321
  "graph.chart",
8055
- "merges.history"
8322
+ "merges.history",
8323
+ "merges.dismissed"
8056
8324
  ]),
8057
- contact: contactLocatorSchema8("Optional target contact locator."),
8325
+ contact: contactLocatorSchema9("Optional target contact locator."),
8058
8326
  link: linkLocatorSchema5("Optional target link locator."),
8059
8327
  contactIds: arraySchema(
8060
8328
  integerSchema("Contact id."),
@@ -8105,6 +8373,9 @@ var reviewAffinityTool = defineAffinityTool({
8105
8373
  horizonDays: integerSchema("Optional horizon-days filter."),
8106
8374
  exactOnly: booleanSchema("Whether duplicate review is exact-only."),
8107
8375
  minScore: numberSchema("Optional duplicate minimum score."),
8376
+ includeDismissed: booleanSchema(
8377
+ "Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
8378
+ ),
8108
8379
  since: integerSchema("Optional lower timestamp bound."),
8109
8380
  until: integerSchema("Optional upper timestamp bound."),
8110
8381
  limit: integerSchema("Optional result limit."),
@@ -8203,6 +8474,7 @@ var affinityTools = [
8203
8474
  inspectAffinityItemTool,
8204
8475
  manageContactTool,
8205
8476
  mergeContactsTool,
8477
+ manageDuplicateDismissalTool,
8206
8478
  manageIdentityTool,
8207
8479
  manageRelationshipTool,
8208
8480
  recordEventTool,
@@ -8304,6 +8576,21 @@ var affinityToolMappings = [
8304
8576
  action: "set_lifecycle"
8305
8577
  },
8306
8578
  { source: "mergeContacts", tool: mergeContactsToolName },
8579
+ {
8580
+ source: "dismissDuplicate",
8581
+ tool: manageDuplicateDismissalToolName,
8582
+ action: "dismiss"
8583
+ },
8584
+ {
8585
+ source: "undismissDuplicate",
8586
+ tool: manageDuplicateDismissalToolName,
8587
+ action: "undismiss"
8588
+ },
8589
+ {
8590
+ source: "listDismissedDuplicates",
8591
+ tool: reviewAffinityToolName,
8592
+ view: "merges.dismissed"
8593
+ },
8307
8594
  { source: "addIdentity", tool: manageIdentityToolName, action: "add" },
8308
8595
  { source: "reviseIdentity", tool: manageIdentityToolName, action: "revise" },
8309
8596
  { source: "verifyIdentity", tool: manageIdentityToolName, action: "verify" },
@@ -8385,6 +8672,7 @@ __export(write_exports, {
8385
8672
  addDateAnchor: () => addDateAnchor,
8386
8673
  addIdentity: () => addIdentity,
8387
8674
  createContact: () => createContact,
8675
+ dismissDuplicate: () => dismissDuplicate,
8388
8676
  mergeContacts: () => mergeContacts,
8389
8677
  overrideLinkState: () => overrideLinkState,
8390
8678
  recordCommitment: () => recordCommitment,
@@ -8405,6 +8693,7 @@ __export(write_exports, {
8405
8693
  setAttribute: () => setAttribute,
8406
8694
  setContactLifecycle: () => setContactLifecycle,
8407
8695
  setStructuralTie: () => setStructuralTie,
8696
+ undismissDuplicate: () => undismissDuplicate,
8408
8697
  unsetAttribute: () => unsetAttribute,
8409
8698
  verifyIdentity: () => verifyIdentity
8410
8699
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostpaw/affinity",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "Relationship intelligence and social CRM model for Node.js — SQLite, zero dependencies, with a built-in LLM tool facade",
5
5
  "license": "MIT",
6
6
  "author": {