@ghostpaw/affinity 0.6.0 → 0.6.1

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.
@@ -296,7 +296,7 @@ var init_mcp_stdio = __esm({
296
296
  var affinityVersion;
297
297
  var init_version = __esm({
298
298
  "src/version.ts"() {
299
- affinityVersion = "0.6.0";
299
+ affinityVersion = "0.6.1";
300
300
  }
301
301
  });
302
302
 
@@ -767,6 +767,8 @@ function mapEventRowToEventRecord(row, participants) {
767
767
  momentKind: row.moment_kind,
768
768
  participants
769
769
  };
770
+ if (row.provenance_json != null)
771
+ base.provenance = JSON.parse(row.provenance_json);
770
772
  if (row.content != null) base.content = decodeEventText(row.content);
771
773
  if (row.channel != null) base.channel = row.channel;
772
774
  if (row.external_source != null && row.external_id != null)
@@ -1082,14 +1084,9 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
1082
1084
  text = text.replace(" ", "T");
1083
1085
  if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(text)) text += "Z";
1084
1086
  }
1085
- let rounding = 0;
1086
1087
  text = text.replace(
1087
1088
  /\.(\d{4,})(?=Z|[+-]\d{2}:\d{2}$)/,
1088
- (_, digits) => {
1089
- const ms = Math.round(Number(`0.${digits}`) * 1e3);
1090
- rounding = ms === 1e3 ? 1 : 0;
1091
- return `.${String(ms === 1e3 ? 999 : ms).padStart(3, "0")}`;
1092
- }
1089
+ (_, digits) => `.${digits.slice(0, 3)}`
1093
1090
  );
1094
1091
  if (/^(today|yesterday|tomorrow|[+-]\d+d)$/.test(text)) {
1095
1092
  const offset = text === "today" ? 0 : text === "tomorrow" ? 1 : text === "yesterday" ? -1 : Number(text.slice(0, -1));
@@ -1109,7 +1106,7 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
1109
1106
  const midnight = Date.parse(`${day}T00:00:00Z`);
1110
1107
  if (!Number.isFinite(midnight) || new Date(midnight).toISOString().slice(0, 10) !== day)
1111
1108
  return fail();
1112
- const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text) + rounding;
1109
+ const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text);
1113
1110
  if (!Number.isFinite(timestamp)) return fail();
1114
1111
  return { timestamp, day };
1115
1112
  }
@@ -2289,7 +2286,7 @@ var init_event_metadata_schema = __esm({
2289
2286
  );
2290
2287
  eventMetadataSchema = {
2291
2288
  affectsBond: booleanSchema(
2292
- "Whether evidence affects the relationship; defaults false for observations with significance <=2."
2289
+ "Whether evidence affects the relationship; defaults false for like/follow/repost labels and observations with significance <=2; explicit true opts in."
2293
2290
  ),
2294
2291
  content: stringSchema(
2295
2292
  "Verbatim text, including empty text; at most 1 MB UTF-8. Summary may be omitted when content is supplied."
@@ -2876,6 +2873,13 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
2876
2873
  `${path}.${key}`,
2877
2874
  referenceNow
2878
2875
  );
2876
+ if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key) && typeof object[key] === "string" && /\.\d{4,}/.test(object[key])) {
2877
+ const provenance = object.provenance;
2878
+ object.provenance = {
2879
+ ...provenance && typeof provenance === "object" ? provenance : {},
2880
+ [`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: object[key]
2881
+ };
2882
+ }
2879
2883
  object[key] = parsed.timestamp;
2880
2884
  if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key))
2881
2885
  object[key.replace(/At$/, "On")] ??= parsed.day;
@@ -2893,7 +2897,7 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
2893
2897
  const error = validateToolInput(
2894
2898
  child,
2895
2899
  object[key],
2896
- `${path}.${key}`,
2900
+ key === "input" && path === "input" ? path : `${path}.${key}`,
2897
2901
  referenceNow,
2898
2902
  strict
2899
2903
  );
@@ -3408,6 +3412,11 @@ function setAttribute(db, target, name, value) {
3408
3412
  const stored = normalizeAttributeValue(value);
3409
3413
  const now = resolveNow();
3410
3414
  const existing = getLiveAttributeRow(db, target, key);
3415
+ if (existing?.value === stored)
3416
+ return buildAttributeMutationReceipt(
3417
+ mapAttributeRowToAttributeRecord(existing),
3418
+ {}
3419
+ );
3411
3420
  if (existing) {
3412
3421
  db.prepare(
3413
3422
  "UPDATE attributes SET value = ?, updated_at = ? WHERE id = ?"
@@ -3578,7 +3587,30 @@ function linkLocatorSchema2(description) {
3578
3587
  };
3579
3588
  }
3580
3589
  function resolveAttributeTarget(db, target) {
3581
- if (target.kind === "event") return { kind: "ok", target };
3590
+ if (target.kind === "event") {
3591
+ if (target.eventId === void 0 === (target.ref === void 0))
3592
+ return {
3593
+ kind: "error",
3594
+ result: toolFailure(
3595
+ "protocol",
3596
+ "invalid_input",
3597
+ "Invalid event target.",
3598
+ "Provide exactly one of target.eventId or target.ref."
3599
+ )
3600
+ };
3601
+ const eventId = target.eventId ?? (target.ref && getEventByExternalRef(db, target.ref.source, target.ref.id)?.id);
3602
+ if (eventId === void 0)
3603
+ return {
3604
+ kind: "error",
3605
+ result: toolFailure(
3606
+ "domain",
3607
+ "not_found",
3608
+ "Event not found.",
3609
+ "No event matches target.ref."
3610
+ )
3611
+ };
3612
+ return { kind: "ok", target: { kind: "event", eventId } };
3613
+ }
3582
3614
  if (target.kind === "contact") {
3583
3615
  const contact = resolveContactLocator(db, target.contact, "target.contact");
3584
3616
  if (!contact.ok) {
@@ -3642,6 +3674,8 @@ var init_manage_attribute_tool = __esm({
3642
3674
  init_replace_attributes();
3643
3675
  init_set_attribute();
3644
3676
  init_unset_attribute();
3677
+ init_event_metadata();
3678
+ init_event_metadata_schema();
3645
3679
  init_tool_metadata();
3646
3680
  init_tool_mutation();
3647
3681
  init_tool_names();
@@ -3672,7 +3706,8 @@ var init_manage_attribute_tool = __esm({
3672
3706
  target: objectSchema(
3673
3707
  {
3674
3708
  kind: enumSchema("Target kind.", ["contact", "link", "event"]),
3675
- eventId: integerSchema("Target event id."),
3709
+ eventId: integerSchema("Target event id; provide eventId or ref."),
3710
+ ref: externalRefSchema,
3676
3711
  contact: contactLocatorSchema2(
3677
3712
  "Contact locator. Required when kind=contact. Provide contactId or identity."
3678
3713
  ),
@@ -3730,7 +3765,9 @@ function insertJournalEvent(db, params) {
3730
3765
  );
3731
3766
  const eventId = Number(result.lastInsertRowid);
3732
3767
  db.prepare("UPDATE events SET affects_bond=? WHERE id=?").run(
3733
- params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 ? 0 : 1 : Number(params.affectsBond),
3768
+ params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 || ["activity", "milestone", "observation"].includes(params.type) && /^(?:follow(?:ed|ing)?|like(?:d)?|repost(?:ed)?)(?:\b|[-_])/i.test(
3769
+ summary
3770
+ ) && !/^follow[ -]up\b/i.test(summary) ? 0 : 1 : Number(params.affectsBond),
3734
3771
  eventId
3735
3772
  );
3736
3773
  db.prepare("UPDATE events SET declared_moment_kind=? WHERE id=?").run(
@@ -5538,7 +5575,7 @@ function exportContact(db, id) {
5538
5575
  const contact = query("SELECT * FROM contacts WHERE id = ?", id)[0];
5539
5576
  if (!contact) throw new AffinityNotFoundError("contact not found");
5540
5577
  const events = query(
5541
- "SELECT DISTINCT e.*, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
5578
+ "SELECT DISTINCT e.*, COALESCE(e.parent_event_id, (SELECT parent.id FROM events parent WHERE parent.external_source=e.parent_source AND parent.external_id=e.parent_ref_id AND parent.deleted_at IS NULL)) AS parent_event_id, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
5542
5579
  id
5543
5580
  ).map((event) => ({
5544
5581
  ...event,
@@ -9200,12 +9237,14 @@ function getEventThread(db, source, id, options) {
9200
9237
  const rows = db.prepare(`WITH RECURSIVE thread(id) AS (
9201
9238
  SELECT ? UNION SELECT e.id FROM events e JOIN thread t JOIN events p ON p.id=t.id
9202
9239
  WHERE e.deleted_at IS NULL AND (e.parent_event_id=p.id OR (e.parent_source=p.external_source AND e.parent_ref_id=p.external_id) OR (e.thread_source=? AND e.thread_id=?))
9203
- ) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
9240
+ ) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) AND (? IS NULL OR e.type IN (SELECT value FROM json_each(?))) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
9204
9241
  root.id,
9205
9242
  root.externalRef?.source ?? null,
9206
9243
  root.externalRef?.id ?? null,
9207
9244
  options?.channel ?? null,
9208
9245
  options?.channel?.trim().toLowerCase() ?? null,
9246
+ options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
9247
+ options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
9209
9248
  limit,
9210
9249
  offset
9211
9250
  );
@@ -9923,12 +9962,12 @@ function reviewPage(db, rawInput, pageOnly) {
9923
9962
  if (!pageOnly && input.view !== "graph.chart") {
9924
9963
  const limit = input.limit ?? 50;
9925
9964
  const offset = input.offset ?? 0;
9926
- if (!Number.isInteger(limit) || limit < 1 || limit > 250 || !Number.isInteger(offset) || offset < 0)
9965
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e4 || !Number.isInteger(offset) || offset < 0)
9927
9966
  return toolFailure(
9928
9967
  "protocol",
9929
9968
  "invalid_input",
9930
9969
  "Invalid pagination.",
9931
- "limit must be 1..250; offset must be non-negative"
9970
+ "limit must be 1..10000; use offset to page through larger results; offset must be non-negative"
9932
9971
  );
9933
9972
  const all = [];
9934
9973
  let page;
@@ -9980,7 +10019,7 @@ function reviewPage(db, rawInput, pageOnly) {
9980
10019
  {
9981
10020
  ...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
9982
10021
  ...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
9983
- ...input.minScore === void 0 ? {} : { minScore: input.minScore },
10022
+ ...input.minScore === void 0 ? { minScore: 0.5 } : { minScore: input.minScore },
9984
10023
  ...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
9985
10024
  },
9986
10025
  options
@@ -9994,9 +10033,10 @@ function reviewPage(db, rawInput, pageOnly) {
9994
10033
  "Missing thread reference.",
9995
10034
  "ref is required for events.thread."
9996
10035
  );
9997
- items = getEventThread(db, input.ref.source, input.ref.id, options).map(
9998
- toEventListToolItem
9999
- );
10036
+ items = getEventThread(db, input.ref.source, input.ref.id, {
10037
+ ...options,
10038
+ ...input.eventTypes === void 0 ? {} : { eventTypes: input.eventTypes }
10039
+ }).map(toEventListToolItem);
10000
10040
  break;
10001
10041
  }
10002
10042
  case "events.contact_journal": {
@@ -10284,7 +10324,7 @@ var init_review_affinity_tool = __esm({
10284
10324
  includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
10285
10325
  since: "Optional lower timestamp bound for time-based views.",
10286
10326
  until: "Optional upper timestamp bound for time-based views.",
10287
- limit: "Optional result limit.",
10327
+ limit: "Optional result limit, 1..10000 (default 50); use offset for paging.",
10288
10328
  offset: "Optional result offset."
10289
10329
  },
10290
10330
  outputDescription: "Returns compact review items, a count, the applied filters, and the chart payload for graph review.",
@@ -10389,13 +10429,17 @@ var init_review_affinity_tool = __esm({
10389
10429
  ]),
10390
10430
  horizonDays: integerSchema("Optional horizon-days filter."),
10391
10431
  exactOnly: booleanSchema("Whether duplicate review is exact-only."),
10392
- minScore: numberSchema("Optional duplicate minimum score."),
10432
+ minScore: numberSchema(
10433
+ "Optional duplicate minimum score (default 0.5; use 0 for all candidates)."
10434
+ ),
10393
10435
  includeDismissed: booleanSchema(
10394
10436
  "Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
10395
10437
  ),
10396
10438
  since: integerSchema("Optional lower timestamp bound."),
10397
10439
  until: integerSchema("Optional upper timestamp bound."),
10398
- limit: integerSchema("Optional result limit."),
10440
+ limit: integerSchema(
10441
+ "Optional result limit, 1..10000 (default 50); use offset for paging."
10442
+ ),
10399
10443
  offset: integerSchema("Optional result offset.")
10400
10444
  },
10401
10445
  ["view"],
@@ -10733,10 +10777,18 @@ function normalizeTimeInputs(value, now = Date.now(), inPatch = false) {
10733
10777
  `Invalid timestamp "${item}". ${error.message}`
10734
10778
  );
10735
10779
  }
10780
+ if (!inPatch && ["occurredAt", "dueAt"].includes(key) && typeof item === "string" && /\.\d{4,}/.test(item)) {
10781
+ const provenance = result.provenance ?? value.provenance;
10782
+ result.provenance = {
10783
+ ...provenance && typeof provenance === "object" ? provenance : {},
10784
+ [`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: item
10785
+ };
10786
+ }
10736
10787
  result[key] = parsed.timestamp;
10737
10788
  if (!inPatch && ["occurredAt", "dueAt"].includes(key))
10738
10789
  result[key.replace(/At$/, "On")] = parsed.day;
10739
- } else result[key] = normalizeTimeInputs(item, now, key === "patch");
10790
+ } else if (key !== "provenance" || result.provenance === void 0)
10791
+ result[key] = key === "provenance" ? item : normalizeTimeInputs(item, now, key === "patch");
10740
10792
  }
10741
10793
  return result;
10742
10794
  }
@@ -10778,6 +10830,12 @@ var init_timestamp_schema = __esm({
10778
10830
  function surfaceProperties(tool) {
10779
10831
  return {
10780
10832
  ...tool.inputSchema.properties,
10833
+ ...tool.name === "review_affinity" ? {
10834
+ includeContent: {
10835
+ type: "boolean",
10836
+ description: "Deprecated alias for --with-content; includes verbatim event content."
10837
+ }
10838
+ } : {},
10781
10839
  ...["inspect_affinity_item", "review_affinity", "manage_event"].includes(
10782
10840
  tool.name
10783
10841
  ) ? {
@@ -11464,9 +11522,9 @@ async function openAffinityDb(path) {
11464
11522
  }
11465
11523
  const db = new sqlite.DatabaseSync(path);
11466
11524
  try {
11467
- db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;");
11525
+ db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL;");
11468
11526
  const context = db;
11469
- initAffinityTables(context);
11527
+ withTransaction(context, () => initAffinityTables(context));
11470
11528
  return context;
11471
11529
  } catch (error) {
11472
11530
  db.close();
@@ -11476,6 +11534,7 @@ async function openAffinityDb(path) {
11476
11534
  var init_open_affinity_db = __esm({
11477
11535
  "src/surface/open_affinity_db.ts"() {
11478
11536
  init_init_affinity_tables();
11537
+ init_with_transaction();
11479
11538
  }
11480
11539
  });
11481
11540
  function resolveAffinityDbPath(options = {}) {
package/dist/mcp.cjs CHANGED
@@ -5,7 +5,7 @@ var node_path = require('node:path');
5
5
  var node_os = require('node:os');
6
6
 
7
7
  // src/version.ts
8
- var affinityVersion = "0.6.0";
8
+ var affinityVersion = "0.6.1";
9
9
 
10
10
  // src/surface/affinity_mcp_instructions.ts
11
11
  var affinityMcpInstructions = "Affinity is a local relationship model and social CRM. Search before creating contacts. Inspect before revising or merging. Record direct evidence honestly, keep identities and structural ties distinct from social links, and ask for clarification when a target is ambiguous. Review commitments and recurring dates regularly. Derived scores guide attention and do not prove relationship quality.";
@@ -382,6 +382,8 @@ function mapEventRowToEventRecord(row, participants) {
382
382
  momentKind: row.moment_kind,
383
383
  participants
384
384
  };
385
+ if (row.provenance_json != null)
386
+ base.provenance = JSON.parse(row.provenance_json);
385
387
  if (row.content != null) base.content = decodeEventText(row.content);
386
388
  if (row.channel != null) base.channel = row.channel;
387
389
  if (row.external_source != null && row.external_id != null)
@@ -672,14 +674,9 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
672
674
  text = text.replace(" ", "T");
673
675
  if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(text)) text += "Z";
674
676
  }
675
- let rounding = 0;
676
677
  text = text.replace(
677
678
  /\.(\d{4,})(?=Z|[+-]\d{2}:\d{2}$)/,
678
- (_, digits) => {
679
- const ms = Math.round(Number(`0.${digits}`) * 1e3);
680
- rounding = ms === 1e3 ? 1 : 0;
681
- return `.${String(ms === 1e3 ? 999 : ms).padStart(3, "0")}`;
682
- }
679
+ (_, digits) => `.${digits.slice(0, 3)}`
683
680
  );
684
681
  if (/^(today|yesterday|tomorrow|[+-]\d+d)$/.test(text)) {
685
682
  const offset = text === "today" ? 0 : text === "tomorrow" ? 1 : text === "yesterday" ? -1 : Number(text.slice(0, -1));
@@ -699,7 +696,7 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
699
696
  const midnight = Date.parse(`${day}T00:00:00Z`);
700
697
  if (!Number.isFinite(midnight) || new Date(midnight).toISOString().slice(0, 10) !== day)
701
698
  return fail();
702
- const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text) + rounding;
699
+ const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text);
703
700
  if (!Number.isFinite(timestamp)) return fail();
704
701
  return { timestamp, day };
705
702
  }
@@ -1739,7 +1736,7 @@ var externalRefSchema = objectSchema(
1739
1736
  );
1740
1737
  var eventMetadataSchema = {
1741
1738
  affectsBond: booleanSchema(
1742
- "Whether evidence affects the relationship; defaults false for observations with significance <=2."
1739
+ "Whether evidence affects the relationship; defaults false for like/follow/repost labels and observations with significance <=2; explicit true opts in."
1743
1740
  ),
1744
1741
  content: stringSchema(
1745
1742
  "Verbatim text, including empty text; at most 1 MB UTF-8. Summary may be omitted when content is supplied."
@@ -2289,6 +2286,13 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
2289
2286
  `${path}.${key}`,
2290
2287
  referenceNow
2291
2288
  );
2289
+ if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key) && typeof object[key] === "string" && /\.\d{4,}/.test(object[key])) {
2290
+ const provenance = object.provenance;
2291
+ object.provenance = {
2292
+ ...provenance && typeof provenance === "object" ? provenance : {},
2293
+ [`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: object[key]
2294
+ };
2295
+ }
2292
2296
  object[key] = parsed.timestamp;
2293
2297
  if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key))
2294
2298
  object[key.replace(/At$/, "On")] ??= parsed.day;
@@ -2306,7 +2310,7 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
2306
2310
  const error = validateToolInput(
2307
2311
  child,
2308
2312
  object[key],
2309
- `${path}.${key}`,
2313
+ key === "input" && path === "input" ? path : `${path}.${key}`,
2310
2314
  referenceNow,
2311
2315
  strict
2312
2316
  );
@@ -2743,6 +2747,11 @@ function setAttribute(db, target, name, value) {
2743
2747
  const stored = normalizeAttributeValue(value);
2744
2748
  const now = resolveNow();
2745
2749
  const existing = getLiveAttributeRow(db, target, key);
2750
+ if (existing?.value === stored)
2751
+ return buildAttributeMutationReceipt(
2752
+ mapAttributeRowToAttributeRecord(existing),
2753
+ {}
2754
+ );
2746
2755
  if (existing) {
2747
2756
  db.prepare(
2748
2757
  "UPDATE attributes SET value = ?, updated_at = ? WHERE id = ?"
@@ -2882,7 +2891,30 @@ function linkLocatorSchema2(description) {
2882
2891
  };
2883
2892
  }
2884
2893
  function resolveAttributeTarget(db, target) {
2885
- if (target.kind === "event") return { kind: "ok", target };
2894
+ if (target.kind === "event") {
2895
+ if (target.eventId === void 0 === (target.ref === void 0))
2896
+ return {
2897
+ kind: "error",
2898
+ result: toolFailure(
2899
+ "protocol",
2900
+ "invalid_input",
2901
+ "Invalid event target.",
2902
+ "Provide exactly one of target.eventId or target.ref."
2903
+ )
2904
+ };
2905
+ const eventId = target.eventId ?? (target.ref && getEventByExternalRef(db, target.ref.source, target.ref.id)?.id);
2906
+ if (eventId === void 0)
2907
+ return {
2908
+ kind: "error",
2909
+ result: toolFailure(
2910
+ "domain",
2911
+ "not_found",
2912
+ "Event not found.",
2913
+ "No event matches target.ref."
2914
+ )
2915
+ };
2916
+ return { kind: "ok", target: { kind: "event", eventId } };
2917
+ }
2886
2918
  if (target.kind === "contact") {
2887
2919
  const contact = resolveContactLocator(db, target.contact, "target.contact");
2888
2920
  if (!contact.ok) {
@@ -2964,7 +2996,8 @@ var manageAttributeTool = defineAffinityTool({
2964
2996
  target: objectSchema(
2965
2997
  {
2966
2998
  kind: enumSchema("Target kind.", ["contact", "link", "event"]),
2967
- eventId: integerSchema("Target event id."),
2999
+ eventId: integerSchema("Target event id; provide eventId or ref."),
3000
+ ref: externalRefSchema,
2968
3001
  contact: contactLocatorSchema2(
2969
3002
  "Contact locator. Required when kind=contact. Provide contactId or identity."
2970
3003
  ),
@@ -3020,7 +3053,9 @@ function insertJournalEvent(db, params) {
3020
3053
  );
3021
3054
  const eventId = Number(result.lastInsertRowid);
3022
3055
  db.prepare("UPDATE events SET affects_bond=? WHERE id=?").run(
3023
- params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 ? 0 : 1 : Number(params.affectsBond),
3056
+ params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 || ["activity", "milestone", "observation"].includes(params.type) && /^(?:follow(?:ed|ing)?|like(?:d)?|repost(?:ed)?)(?:\b|[-_])/i.test(
3057
+ summary
3058
+ ) && !/^follow[ -]up\b/i.test(summary) ? 0 : 1 : Number(params.affectsBond),
3024
3059
  eventId
3025
3060
  );
3026
3061
  db.prepare("UPDATE events SET declared_moment_kind=? WHERE id=?").run(
@@ -4692,7 +4727,7 @@ function exportContact(db, id) {
4692
4727
  const contact = query("SELECT * FROM contacts WHERE id = ?", id)[0];
4693
4728
  if (!contact) throw new AffinityNotFoundError("contact not found");
4694
4729
  const events = query(
4695
- "SELECT DISTINCT e.*, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
4730
+ "SELECT DISTINCT e.*, COALESCE(e.parent_event_id, (SELECT parent.id FROM events parent WHERE parent.external_source=e.parent_source AND parent.external_id=e.parent_ref_id AND parent.deleted_at IS NULL)) AS parent_event_id, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
4696
4731
  id
4697
4732
  ).map((event) => ({
4698
4733
  ...event,
@@ -7833,12 +7868,14 @@ function getEventThread(db, source, id, options) {
7833
7868
  const rows = db.prepare(`WITH RECURSIVE thread(id) AS (
7834
7869
  SELECT ? UNION SELECT e.id FROM events e JOIN thread t JOIN events p ON p.id=t.id
7835
7870
  WHERE e.deleted_at IS NULL AND (e.parent_event_id=p.id OR (e.parent_source=p.external_source AND e.parent_ref_id=p.external_id) OR (e.thread_source=? AND e.thread_id=?))
7836
- ) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
7871
+ ) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) AND (? IS NULL OR e.type IN (SELECT value FROM json_each(?))) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
7837
7872
  root.id,
7838
7873
  root.externalRef?.source ?? null,
7839
7874
  root.externalRef?.id ?? null,
7840
7875
  options?.channel ?? null,
7841
7876
  options?.channel?.trim().toLowerCase() ?? null,
7877
+ options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
7878
+ options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
7842
7879
  limit,
7843
7880
  offset
7844
7881
  );
@@ -8490,12 +8527,12 @@ function reviewPage(db, rawInput, pageOnly) {
8490
8527
  if (!pageOnly && input.view !== "graph.chart") {
8491
8528
  const limit = input.limit ?? 50;
8492
8529
  const offset = input.offset ?? 0;
8493
- if (!Number.isInteger(limit) || limit < 1 || limit > 250 || !Number.isInteger(offset) || offset < 0)
8530
+ if (!Number.isInteger(limit) || limit < 1 || limit > 1e4 || !Number.isInteger(offset) || offset < 0)
8494
8531
  return toolFailure(
8495
8532
  "protocol",
8496
8533
  "invalid_input",
8497
8534
  "Invalid pagination.",
8498
- "limit must be 1..250; offset must be non-negative"
8535
+ "limit must be 1..10000; use offset to page through larger results; offset must be non-negative"
8499
8536
  );
8500
8537
  const all = [];
8501
8538
  let page;
@@ -8547,7 +8584,7 @@ function reviewPage(db, rawInput, pageOnly) {
8547
8584
  {
8548
8585
  ...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
8549
8586
  ...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
8550
- ...input.minScore === void 0 ? {} : { minScore: input.minScore },
8587
+ ...input.minScore === void 0 ? { minScore: 0.5 } : { minScore: input.minScore },
8551
8588
  ...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
8552
8589
  },
8553
8590
  options
@@ -8561,9 +8598,10 @@ function reviewPage(db, rawInput, pageOnly) {
8561
8598
  "Missing thread reference.",
8562
8599
  "ref is required for events.thread."
8563
8600
  );
8564
- items = getEventThread(db, input.ref.source, input.ref.id, options).map(
8565
- toEventListToolItem
8566
- );
8601
+ items = getEventThread(db, input.ref.source, input.ref.id, {
8602
+ ...options,
8603
+ ...input.eventTypes === void 0 ? {} : { eventTypes: input.eventTypes }
8604
+ }).map(toEventListToolItem);
8567
8605
  break;
8568
8606
  }
8569
8607
  case "events.contact_journal": {
@@ -8822,7 +8860,7 @@ var reviewAffinityTool = defineAffinityTool({
8822
8860
  includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
8823
8861
  since: "Optional lower timestamp bound for time-based views.",
8824
8862
  until: "Optional upper timestamp bound for time-based views.",
8825
- limit: "Optional result limit.",
8863
+ limit: "Optional result limit, 1..10000 (default 50); use offset for paging.",
8826
8864
  offset: "Optional result offset."
8827
8865
  },
8828
8866
  outputDescription: "Returns compact review items, a count, the applied filters, and the chart payload for graph review.",
@@ -8927,13 +8965,17 @@ var reviewAffinityTool = defineAffinityTool({
8927
8965
  ]),
8928
8966
  horizonDays: integerSchema("Optional horizon-days filter."),
8929
8967
  exactOnly: booleanSchema("Whether duplicate review is exact-only."),
8930
- minScore: numberSchema("Optional duplicate minimum score."),
8968
+ minScore: numberSchema(
8969
+ "Optional duplicate minimum score (default 0.5; use 0 for all candidates)."
8970
+ ),
8931
8971
  includeDismissed: booleanSchema(
8932
8972
  "Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
8933
8973
  ),
8934
8974
  since: integerSchema("Optional lower timestamp bound."),
8935
8975
  until: integerSchema("Optional upper timestamp bound."),
8936
- limit: integerSchema("Optional result limit."),
8976
+ limit: integerSchema(
8977
+ "Optional result limit, 1..10000 (default 50); use offset for paging."
8978
+ ),
8937
8979
  offset: integerSchema("Optional result offset.")
8938
8980
  },
8939
8981
  ["view"],
@@ -9227,10 +9269,18 @@ function normalizeTimeInputs(value, now = Date.now(), inPatch = false) {
9227
9269
  `Invalid timestamp "${item}". ${error.message}`
9228
9270
  );
9229
9271
  }
9272
+ if (!inPatch && ["occurredAt", "dueAt"].includes(key) && typeof item === "string" && /\.\d{4,}/.test(item)) {
9273
+ const provenance = result.provenance ?? value.provenance;
9274
+ result.provenance = {
9275
+ ...provenance && typeof provenance === "object" ? provenance : {},
9276
+ [`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: item
9277
+ };
9278
+ }
9230
9279
  result[key] = parsed.timestamp;
9231
9280
  if (!inPatch && ["occurredAt", "dueAt"].includes(key))
9232
9281
  result[key.replace(/At$/, "On")] = parsed.day;
9233
- } else result[key] = normalizeTimeInputs(item, now, key === "patch");
9282
+ } else if (key !== "provenance" || result.provenance === void 0)
9283
+ result[key] = key === "provenance" ? item : normalizeTimeInputs(item, now, key === "patch");
9234
9284
  }
9235
9285
  return result;
9236
9286
  }
@@ -9262,6 +9312,12 @@ function timestampSchema(schema) {
9262
9312
  function surfaceProperties(tool) {
9263
9313
  return {
9264
9314
  ...tool.inputSchema.properties,
9315
+ ...tool.name === "review_affinity" ? {
9316
+ includeContent: {
9317
+ type: "boolean",
9318
+ description: "Deprecated alias for --with-content; includes verbatim event content."
9319
+ }
9320
+ } : {},
9265
9321
  ...["inspect_affinity_item", "review_affinity", "manage_event"].includes(
9266
9322
  tool.name
9267
9323
  ) ? {
@@ -9862,9 +9918,9 @@ async function openAffinityDb(path) {
9862
9918
  }
9863
9919
  const db = new sqlite.DatabaseSync(path);
9864
9920
  try {
9865
- db.exec("PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000;");
9921
+ db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL;");
9866
9922
  const context = db;
9867
- initAffinityTables(context);
9923
+ withTransaction(context, () => initAffinityTables(context));
9868
9924
  return context;
9869
9925
  } catch (error) {
9870
9926
  db.close();