@prmichaelsen/remember-mcp 3.14.18 → 3.14.20

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.
@@ -0,0 +1,48 @@
1
+ name: Bump remember-core
2
+
3
+ on:
4
+ repository_dispatch:
5
+ types: [dependency-update]
6
+
7
+ jobs:
8
+ bump:
9
+ if: github.event.client_payload.package == '@prmichaelsen/remember-core'
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ ref: mainline
15
+
16
+ - uses: actions/setup-node@v4
17
+ with:
18
+ node-version: 20
19
+ registry-url: https://registry.npmjs.org
20
+
21
+ - name: Bump remember-core
22
+ run: npm install @prmichaelsen/remember-core@${{ github.event.client_payload.version }} --save
23
+
24
+ - name: Bump package version
25
+ id: bump
26
+ run: |
27
+ CURRENT=$(node -p "require('./package.json').version")
28
+ NEW=$(echo "$CURRENT" | awk -F. '{print $1"."$2"."$3+1}')
29
+ npm version "$NEW" --no-git-tag-version
30
+ echo "version=$NEW" >> "$GITHUB_OUTPUT"
31
+
32
+ - name: Commit and tag
33
+ run: |
34
+ git config user.name "github-actions[bot]"
35
+ git config user.email "github-actions[bot]@users.noreply.github.com"
36
+ git add package.json package-lock.json
37
+ git commit -m "bump: remember core"
38
+ git commit --allow-empty -m "${{ steps.bump.outputs.version }}"
39
+ git tag "v${{ steps.bump.outputs.version }}"
40
+ git push --follow-tags
41
+
42
+ - name: Publish to npm
43
+ run: npm publish --access public
44
+ env:
45
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46
+
47
+ - name: Summary
48
+ run: echo "Bumped remember-core to ${{ github.event.client_payload.version }}, published remember-mcp@${{ steps.bump.outputs.version }}"
@@ -1070,11 +1070,21 @@ tasks:
1070
1070
  notes: |
1071
1071
  Static imports for ghost-config and access-control
1072
1072
 
1073
+ unassigned:
1074
+ - id: task-201
1075
+ name: Auto-bump remember-core via GitHub Actions
1076
+ status: not_started
1077
+ file: agent/tasks/unassigned/task-201-auto-bump-remember-core-ci.md
1078
+ estimated_hours: 2
1079
+ notes: |
1080
+ Set up repository_dispatch CI/CD: remember-core publish triggers remember-mcp bump + publish.
1081
+ Draft workflow files already created in both repos (not yet committed).
1082
+
1073
1083
  documentation:
1074
1084
  design_documents: 31
1075
1085
  milestone_documents: 14
1076
1086
  pattern_documents: 7
1077
- task_documents: 91
1087
+ task_documents: 92
1078
1088
 
1079
1089
  progress:
1080
1090
  planning: 100%
@@ -0,0 +1,85 @@
1
+ # Task 201: Auto-bump remember-core via GitHub Actions
2
+
3
+ **Milestone**: Unassigned
4
+ **Estimated Time**: 1-2 hours
5
+ **Dependencies**: None
6
+ **Status**: Not Started
7
+
8
+ ---
9
+
10
+ ## Objective
11
+
12
+ Set up GitHub Actions CI/CD so that when remember-core publishes a new version to npm, remember-mcp automatically bumps the dependency, patches its own version, and publishes to npm.
13
+
14
+ ---
15
+
16
+ ## Context
17
+
18
+ Currently, bumping remember-core in remember-mcp is a manual process: update package.json, commit, tag, publish. This happens frequently (almost every remember-core release). Automating this eliminates toil and reduces lag between core changes and MCP availability.
19
+
20
+ The approach uses `repository_dispatch` events: remember-core's publish workflow notifies remember-mcp after a successful npm publish, and remember-mcp's workflow handles the bump + publish.
21
+
22
+ ---
23
+
24
+ ## Steps
25
+
26
+ ### 1. Configure GitHub Secrets
27
+
28
+ Add required secrets to both repositories:
29
+
30
+ - **remember-core**: `REPO_DISPATCH_TOKEN` - a GitHub PAT with `repo` scope (to trigger dispatches on remember-mcp)
31
+ - **remember-mcp**: `NPM_TOKEN` - an npm access token for publishing
32
+
33
+ ### 2. Add dispatch step to remember-core publish workflow
34
+
35
+ File: `remember-core/.github/workflows/publish.yml`
36
+
37
+ A "Notify remember-mcp" step has been drafted that sends a `repository_dispatch` event after successful npm publish. Review and merge.
38
+
39
+ ### 3. Add bump workflow to remember-mcp
40
+
41
+ File: `remember-mcp/.github/workflows/bump-remember-core.yml`
42
+
43
+ A workflow has been drafted that:
44
+ - Triggers on `repository_dispatch` with `dependency-update` type
45
+ - Filters to `@prmichaelsen/remember-core` events
46
+ - Installs the new version
47
+ - Patch-bumps remember-mcp version
48
+ - Commits (`bump: remember core` then `{version}`)
49
+ - Tags and pushes
50
+ - Publishes to npm
51
+
52
+ ### 4. Fix remember-mcp git remote
53
+
54
+ The remember-mcp git remote currently points to `remember-core.git` instead of `remember-mcp.git`. Fix with:
55
+
56
+ ```bash
57
+ git remote set-url origin git@github.com:prmichaelsen/remember-mcp.git
58
+ ```
59
+
60
+ ### 5. Test end-to-end
61
+
62
+ - Push a version bump to remember-core main
63
+ - Verify remember-core publishes to npm
64
+ - Verify dispatch fires
65
+ - Verify remember-mcp bumps, commits, tags, publishes
66
+
67
+ ---
68
+
69
+ ## Verification
70
+
71
+ - [ ] `REPO_DISPATCH_TOKEN` secret set in remember-core
72
+ - [ ] `NPM_TOKEN` secret set in remember-mcp
73
+ - [ ] remember-core publish.yml includes dispatch step
74
+ - [ ] remember-mcp bump-remember-core.yml exists
75
+ - [ ] remember-mcp git remote points to correct repo
76
+ - [ ] End-to-end test: remember-core publish triggers remember-mcp bump + publish
77
+
78
+ ---
79
+
80
+ ## Notes
81
+
82
+ - Draft workflow files already created in both repos (not yet committed)
83
+ - remember-core publish.yml already has NPM_TOKEN configured
84
+ - The bump workflow commits in the existing pattern: `bump: remember core` then `{version}`
85
+ - Consider adding error notifications (GitHub Actions failure emails are on by default)
@@ -803,6 +803,64 @@ function handleToolError(error, context) {
803
803
  throw new Error(detailedMessage);
804
804
  }
805
805
 
806
+ // node_modules/@prmichaelsen/remember-core/dist/types/trust.types.js
807
+ var TrustLevel = {
808
+ /** Anyone can see this, including strangers */
809
+ PUBLIC: 1,
810
+ /** Friends and known users */
811
+ INTERNAL: 2,
812
+ /** Trusted friends only */
813
+ CONFIDENTIAL: 3,
814
+ /** Close/intimate contacts only */
815
+ RESTRICTED: 4,
816
+ /** Owner only (or explicitly granted) */
817
+ SECRET: 5
818
+ };
819
+ var TRUST_LABELS = {
820
+ [TrustLevel.PUBLIC]: "Public",
821
+ [TrustLevel.INTERNAL]: "Internal",
822
+ [TrustLevel.CONFIDENTIAL]: "Confidential",
823
+ [TrustLevel.RESTRICTED]: "Restricted",
824
+ [TrustLevel.SECRET]: "Secret"
825
+ };
826
+ var ALL_TRUST_LEVELS = [
827
+ TrustLevel.PUBLIC,
828
+ TrustLevel.INTERNAL,
829
+ TrustLevel.CONFIDENTIAL,
830
+ TrustLevel.RESTRICTED,
831
+ TrustLevel.SECRET
832
+ ];
833
+ function isValidTrustLevel(value) {
834
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 5;
835
+ }
836
+ function normalizeTrustScore(value) {
837
+ if (value == null)
838
+ return TrustLevel.INTERNAL;
839
+ if (isValidTrustLevel(value))
840
+ return value;
841
+ const inverted = 1 - value;
842
+ if (inverted >= 0.875)
843
+ return TrustLevel.SECRET;
844
+ if (inverted >= 0.625)
845
+ return TrustLevel.RESTRICTED;
846
+ if (inverted >= 0.375)
847
+ return TrustLevel.CONFIDENTIAL;
848
+ if (inverted >= 0.125)
849
+ return TrustLevel.INTERNAL;
850
+ return TrustLevel.PUBLIC;
851
+ }
852
+
853
+ // node_modules/@prmichaelsen/remember-core/dist/types/ghost-config.types.js
854
+ var DEFAULT_GHOST_CONFIG = {
855
+ enabled: false,
856
+ public_ghost_enabled: false,
857
+ default_friend_trust: TrustLevel.INTERNAL,
858
+ default_public_trust: TrustLevel.PUBLIC,
859
+ per_user_trust: {},
860
+ blocked_users: [],
861
+ enforcement_mode: "query"
862
+ };
863
+
806
864
  // node_modules/@prmichaelsen/remember-core/dist/types/preferences.types.js
807
865
  var DEFAULT_PREFERENCES = {
808
866
  templates: {
@@ -828,7 +886,7 @@ var DEFAULT_PREFERENCES = {
828
886
  share_with_memories: true
829
887
  },
830
888
  privacy: {
831
- default_trust_level: 0.25,
889
+ default_trust_level: 2,
832
890
  allow_cross_user_access: false,
833
891
  auto_approve_requests: false,
834
892
  audit_logging: true
@@ -847,6 +905,14 @@ var DEFAULT_PREFERENCES = {
847
905
  }
848
906
  };
849
907
 
908
+ // node_modules/@prmichaelsen/remember-core/dist/types/rating.types.js
909
+ var RATING_MIN_THRESHOLD = 5;
910
+ function computeRatingAvg(ratingSum, ratingCount) {
911
+ if (ratingCount < RATING_MIN_THRESHOLD)
912
+ return null;
913
+ return ratingSum / ratingCount;
914
+ }
915
+
850
916
  // node_modules/@prmichaelsen/remember-core/dist/types/space.types.js
851
917
  var SUPPORTED_SPACES = [
852
918
  "the_void",
@@ -1303,6 +1369,35 @@ var DebugLevel2;
1303
1369
  DebugLevel3[DebugLevel3["TRACE"] = 5] = "TRACE";
1304
1370
  })(DebugLevel2 || (DebugLevel2 = {}));
1305
1371
 
1372
+ // node_modules/@prmichaelsen/remember-core/dist/errors/base.error.js
1373
+ var AppError = class extends Error {
1374
+ context;
1375
+ constructor(message, context = {}) {
1376
+ super(message);
1377
+ this.context = context;
1378
+ this.name = this.constructor.name;
1379
+ Object.setPrototypeOf(this, new.target.prototype);
1380
+ }
1381
+ toJSON() {
1382
+ return {
1383
+ kind: this.kind,
1384
+ name: this.name,
1385
+ message: this.message,
1386
+ context: this.context
1387
+ };
1388
+ }
1389
+ };
1390
+
1391
+ // node_modules/@prmichaelsen/remember-core/dist/errors/app-errors.js
1392
+ var ValidationError = class extends AppError {
1393
+ fields;
1394
+ kind = "validation";
1395
+ constructor(message, fields = {}) {
1396
+ super(message, { fields });
1397
+ this.fields = fields;
1398
+ }
1399
+ };
1400
+
1306
1401
  // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
1307
1402
  var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
1308
1403
 
@@ -1579,9 +1674,15 @@ function buildDocTypeFilters(collection, docType, filters) {
1579
1674
  if (filters?.relationship_count_max !== void 0) {
1580
1675
  filterList.push(collection.filter.byProperty("relationship_count").lessOrEqual(filters.relationship_count_max));
1581
1676
  }
1677
+ if (filters?.rating_min !== void 0) {
1678
+ filterList.push(collection.filter.byProperty("rating_bayesian").greaterOrEqual(filters.rating_min));
1679
+ }
1582
1680
  if (filters?.tags && filters.tags.length > 0) {
1583
1681
  filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
1584
1682
  }
1683
+ if (filters?.memory_ids && filters.memory_ids.length > 0) {
1684
+ filterList.push(collection.filter.byId().containsAny(filters.memory_ids));
1685
+ }
1585
1686
  return combineFiltersWithAnd(filterList);
1586
1687
  }
1587
1688
  function buildMemoryOnlyFilters(collection, filters) {
@@ -1943,6 +2044,9 @@ var ALL_MEMORY_PROPERTIES = [
1943
2044
  "observation",
1944
2045
  "strength",
1945
2046
  "source",
2047
+ "rating_sum",
2048
+ "rating_count",
2049
+ "rating_bayesian",
1946
2050
  "access_count",
1947
2051
  "last_accessed_at",
1948
2052
  "tags",
@@ -1988,14 +2092,27 @@ function buildTrustFilter(collection, accessorTrustLevel) {
1988
2092
  }
1989
2093
 
1990
2094
  // node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
2095
+ function normalizeDoc(doc) {
2096
+ if ("trust_score" in doc) {
2097
+ doc.trust_score = normalizeTrustScore(doc.trust_score);
2098
+ }
2099
+ const ratingSum = doc.rating_sum;
2100
+ const ratingCount = doc.rating_count;
2101
+ if (ratingSum !== void 0 && ratingCount !== void 0) {
2102
+ doc.rating_avg = computeRatingAvg(ratingSum, ratingCount);
2103
+ }
2104
+ return doc;
2105
+ }
1991
2106
  var MemoryService = class {
1992
2107
  collection;
1993
2108
  userId;
1994
2109
  logger;
1995
- constructor(collection, userId, logger2) {
2110
+ options;
2111
+ constructor(collection, userId, logger2, options) {
1996
2112
  this.collection = collection;
1997
2113
  this.userId = userId;
1998
2114
  this.logger = logger2;
2115
+ this.options = options;
1999
2116
  }
2000
2117
  /**
2001
2118
  * Execute a search function, retrying without the deleted_at filter if
@@ -2018,7 +2135,31 @@ var MemoryService = class {
2018
2135
  throw new Error(`Memory not found: ${memoryId}`);
2019
2136
  if (existing.properties.user_id !== this.userId)
2020
2137
  throw new Error("Unauthorized");
2021
- return { memory: { id: existing.uuid, ...existing.properties } };
2138
+ return { memory: normalizeDoc({ id: existing.uuid, ...existing.properties }) };
2139
+ }
2140
+ // ── Resolve by ID (cross-collection) ─────────────────────────────────
2141
+ /**
2142
+ * Resolve any memory by UUID alone using the Firestore index.
2143
+ * Requires `memoryIndex` and `weaviateClient` in constructor options.
2144
+ */
2145
+ async resolveById(memoryId) {
2146
+ if (!this.options?.weaviateClient) {
2147
+ throw new Error("resolveById requires weaviateClient in options");
2148
+ }
2149
+ if (this.options?.memoryIndex) {
2150
+ const collectionName = await this.options.memoryIndex.lookup(memoryId);
2151
+ if (collectionName) {
2152
+ const col = this.options.weaviateClient.collections.get(collectionName);
2153
+ const memory = await fetchMemoryWithAllProperties(col, memoryId);
2154
+ if (memory?.properties) {
2155
+ return {
2156
+ memory: normalizeDoc({ id: memory.uuid, ...memory.properties }),
2157
+ collectionName
2158
+ };
2159
+ }
2160
+ }
2161
+ }
2162
+ return { memory: null, collectionName: null };
2022
2163
  }
2023
2164
  // ── Create ──────────────────────────────────────────────────────────
2024
2165
  async create(input) {
@@ -2032,12 +2173,16 @@ var MemoryService = class {
2032
2173
  summary: input.title,
2033
2174
  content_type: contentType,
2034
2175
  weight: input.weight ?? 0.5,
2035
- trust_score: input.trust ?? 0.25,
2176
+ trust_score: normalizeTrustScore(input.trust ?? TrustLevel.INTERNAL),
2036
2177
  confidence: 1,
2037
2178
  context_summary: input.context_summary || "Memory created",
2038
2179
  context_conversation_id: input.context_conversation_id,
2039
2180
  relationship_ids: [],
2040
2181
  relationship_count: 0,
2182
+ rating_sum: 0,
2183
+ rating_count: 0,
2184
+ rating_bayesian: 3,
2185
+ // (0 + 15) / (0 + 5) = 3.0 (prior mean)
2041
2186
  access_count: 0,
2042
2187
  last_accessed_at: now,
2043
2188
  created_at: now,
@@ -2057,6 +2202,14 @@ var MemoryService = class {
2057
2202
  };
2058
2203
  const memoryId = await this.collection.data.insert({ properties });
2059
2204
  this.logger.info("Memory created", { memoryId, userId: this.userId });
2205
+ if (this.options?.memoryIndex) {
2206
+ try {
2207
+ const collectionName = this.collection.name;
2208
+ await this.options.memoryIndex.index(memoryId, collectionName);
2209
+ } catch (err2) {
2210
+ this.logger.warn?.(`[MemoryService] Index write failed for ${memoryId}: ${err2}`);
2211
+ }
2212
+ }
2060
2213
  return { memory_id: memoryId, created_at: now };
2061
2214
  }
2062
2215
  // ── Search (hybrid) ─────────────────────────────────────────────────
@@ -2094,7 +2247,7 @@ var MemoryService = class {
2094
2247
  const memories = [];
2095
2248
  const relationships = [];
2096
2249
  for (const obj of paginated) {
2097
- const doc = { id: obj.uuid, ...obj.properties };
2250
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2098
2251
  if (doc.doc_type === "memory")
2099
2252
  memories.push(doc);
2100
2253
  else if (doc.doc_type === "relationship")
@@ -2137,7 +2290,7 @@ var MemoryService = class {
2137
2290
  const paginated = results.objects.slice(offset);
2138
2291
  const memories = [];
2139
2292
  for (const obj of paginated) {
2140
- const doc = { id: obj.uuid, ...obj.properties };
2293
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2141
2294
  if (doc.doc_type === "memory") {
2142
2295
  memories.push(doc);
2143
2296
  }
@@ -2181,7 +2334,48 @@ var MemoryService = class {
2181
2334
  const paginated = results.objects.slice(offset);
2182
2335
  const memories = [];
2183
2336
  for (const obj of paginated) {
2184
- const doc = { id: obj.uuid, ...obj.properties };
2337
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2338
+ if (doc.doc_type === "memory") {
2339
+ memories.push(doc);
2340
+ }
2341
+ }
2342
+ return {
2343
+ memories,
2344
+ total: memories.length,
2345
+ offset,
2346
+ limit
2347
+ };
2348
+ }
2349
+ // ── By Rating (Bayesian average) ─────────────────────────────────
2350
+ async byRating(input) {
2351
+ const limit = input.limit ?? 50;
2352
+ const offset = input.offset ?? 0;
2353
+ const direction = input.direction ?? "desc";
2354
+ const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
2355
+ const ghostFilters = [];
2356
+ if (input.ghost_context) {
2357
+ ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
2358
+ }
2359
+ if (!input.ghost_context?.include_ghost_content) {
2360
+ ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
2361
+ }
2362
+ const executeQuery = async (useDeletedFilter) => {
2363
+ const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
2364
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null));
2365
+ const queryOptions = {
2366
+ limit: limit + offset,
2367
+ sort: this.collection.sort.byProperty("rating_bayesian", direction === "asc")
2368
+ };
2369
+ if (combinedFilters) {
2370
+ queryOptions.filters = combinedFilters;
2371
+ }
2372
+ return this.collection.query.fetchObjects(queryOptions);
2373
+ };
2374
+ const results = await this.retryWithoutDeletedFilter(executeQuery);
2375
+ const paginated = results.objects.slice(offset);
2376
+ const memories = [];
2377
+ for (const obj of paginated) {
2378
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2185
2379
  if (doc.doc_type === "memory") {
2186
2380
  memories.push(doc);
2187
2381
  }
@@ -2240,7 +2434,7 @@ var MemoryService = class {
2240
2434
  if (!input.include_relationships) {
2241
2435
  results.objects = results.objects.filter((o) => o.properties.doc_type === "memory");
2242
2436
  }
2243
- const items = results.objects.map((obj) => ({
2437
+ const items = results.objects.map((obj) => normalizeDoc({
2244
2438
  id: obj.uuid,
2245
2439
  ...obj.properties,
2246
2440
  similarity: Math.max(0, Math.min(1, 1 - (obj.metadata?.distance ?? 0)))
@@ -2269,7 +2463,7 @@ var MemoryService = class {
2269
2463
  opts.filters = combinedFilters;
2270
2464
  return this.collection.query.nearText(input.query, opts);
2271
2465
  });
2272
- const items = results.objects.map((obj) => ({
2466
+ const items = results.objects.map((obj) => normalizeDoc({
2273
2467
  id: obj.uuid,
2274
2468
  ...obj.properties,
2275
2469
  relevance: Math.max(0, Math.min(1, 1 - (obj.metadata?.distance ?? 0)))
@@ -2313,8 +2507,8 @@ var MemoryService = class {
2313
2507
  updatedFields.push("weight");
2314
2508
  }
2315
2509
  if (input.trust !== void 0) {
2316
- if (input.trust < 0 || input.trust > 1)
2317
- throw new Error("Trust must be between 0 and 1");
2510
+ if (!isValidTrustLevel(input.trust))
2511
+ throw new Error("Trust must be an integer between 1 and 5");
2318
2512
  updates.trust_score = input.trust;
2319
2513
  updatedFields.push("trust_score");
2320
2514
  }
@@ -2768,6 +2962,10 @@ var COMMON_MEMORY_PROPERTIES = [
2768
2962
  { name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
2769
2963
  { name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
2770
2964
  { name: "relationship_count", dataType: configure.dataType.INT },
2965
+ // Rating aggregates (denormalized from Firestore individual ratings)
2966
+ { name: "rating_sum", dataType: configure.dataType.INT },
2967
+ { name: "rating_count", dataType: configure.dataType.INT },
2968
+ { name: "rating_bayesian", dataType: configure.dataType.NUMBER },
2771
2969
  // Access tracking
2772
2970
  { name: "access_count", dataType: configure.dataType.NUMBER },
2773
2971
  { name: "last_accessed_at", dataType: configure.dataType.DATE },
@@ -2944,12 +3142,26 @@ var SpaceService = class {
2944
3142
  userId;
2945
3143
  confirmationTokenService;
2946
3144
  logger;
2947
- constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2) {
3145
+ moderationClient;
3146
+ constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, options) {
2948
3147
  this.weaviateClient = weaviateClient;
2949
3148
  this.userCollection = userCollection;
2950
3149
  this.userId = userId;
2951
3150
  this.confirmationTokenService = confirmationTokenService;
2952
3151
  this.logger = logger2;
3152
+ this.moderationClient = options?.moderationClient;
3153
+ }
3154
+ // ── Content moderation helper ────────────────────────────────────────
3155
+ async checkModeration(content) {
3156
+ if (!this.moderationClient)
3157
+ return;
3158
+ const result = await this.moderationClient.moderate(content);
3159
+ if (!result.pass) {
3160
+ throw new ValidationError(result.reason, {
3161
+ moderation: ["blocked"],
3162
+ ...result.category ? { category: [result.category] } : {}
3163
+ });
3164
+ }
2953
3165
  }
2954
3166
  // ── Publish (phase 1: generate confirmation token) ──────────────────
2955
3167
  async publish(input) {
@@ -2984,6 +3196,7 @@ var SpaceService = class {
2984
3196
  throw new Error(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
2985
3197
  }
2986
3198
  }
3199
+ await this.checkModeration(memory.properties.content);
2987
3200
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
2988
3201
  memory_id: input.memory_id,
2989
3202
  spaces,
@@ -3050,6 +3263,7 @@ var SpaceService = class {
3050
3263
  if (spaceIds.length === 0 && groupIds.length === 0) {
3051
3264
  throw new Error("Memory has no published copies to revise. Publish first with publish().");
3052
3265
  }
3266
+ await this.checkModeration(memory.properties.content);
3053
3267
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
3054
3268
  memory_id: input.memory_id,
3055
3269
  space_ids: spaceIds,
package/dist/server.js CHANGED
@@ -807,6 +807,64 @@ function handleToolError(error, context) {
807
807
  throw new Error(detailedMessage);
808
808
  }
809
809
 
810
+ // node_modules/@prmichaelsen/remember-core/dist/types/trust.types.js
811
+ var TrustLevel = {
812
+ /** Anyone can see this, including strangers */
813
+ PUBLIC: 1,
814
+ /** Friends and known users */
815
+ INTERNAL: 2,
816
+ /** Trusted friends only */
817
+ CONFIDENTIAL: 3,
818
+ /** Close/intimate contacts only */
819
+ RESTRICTED: 4,
820
+ /** Owner only (or explicitly granted) */
821
+ SECRET: 5
822
+ };
823
+ var TRUST_LABELS = {
824
+ [TrustLevel.PUBLIC]: "Public",
825
+ [TrustLevel.INTERNAL]: "Internal",
826
+ [TrustLevel.CONFIDENTIAL]: "Confidential",
827
+ [TrustLevel.RESTRICTED]: "Restricted",
828
+ [TrustLevel.SECRET]: "Secret"
829
+ };
830
+ var ALL_TRUST_LEVELS = [
831
+ TrustLevel.PUBLIC,
832
+ TrustLevel.INTERNAL,
833
+ TrustLevel.CONFIDENTIAL,
834
+ TrustLevel.RESTRICTED,
835
+ TrustLevel.SECRET
836
+ ];
837
+ function isValidTrustLevel(value) {
838
+ return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 5;
839
+ }
840
+ function normalizeTrustScore(value) {
841
+ if (value == null)
842
+ return TrustLevel.INTERNAL;
843
+ if (isValidTrustLevel(value))
844
+ return value;
845
+ const inverted = 1 - value;
846
+ if (inverted >= 0.875)
847
+ return TrustLevel.SECRET;
848
+ if (inverted >= 0.625)
849
+ return TrustLevel.RESTRICTED;
850
+ if (inverted >= 0.375)
851
+ return TrustLevel.CONFIDENTIAL;
852
+ if (inverted >= 0.125)
853
+ return TrustLevel.INTERNAL;
854
+ return TrustLevel.PUBLIC;
855
+ }
856
+
857
+ // node_modules/@prmichaelsen/remember-core/dist/types/ghost-config.types.js
858
+ var DEFAULT_GHOST_CONFIG = {
859
+ enabled: false,
860
+ public_ghost_enabled: false,
861
+ default_friend_trust: TrustLevel.INTERNAL,
862
+ default_public_trust: TrustLevel.PUBLIC,
863
+ per_user_trust: {},
864
+ blocked_users: [],
865
+ enforcement_mode: "query"
866
+ };
867
+
810
868
  // node_modules/@prmichaelsen/remember-core/dist/types/preferences.types.js
811
869
  var DEFAULT_PREFERENCES = {
812
870
  templates: {
@@ -832,7 +890,7 @@ var DEFAULT_PREFERENCES = {
832
890
  share_with_memories: true
833
891
  },
834
892
  privacy: {
835
- default_trust_level: 0.25,
893
+ default_trust_level: 2,
836
894
  allow_cross_user_access: false,
837
895
  auto_approve_requests: false,
838
896
  audit_logging: true
@@ -851,6 +909,14 @@ var DEFAULT_PREFERENCES = {
851
909
  }
852
910
  };
853
911
 
912
+ // node_modules/@prmichaelsen/remember-core/dist/types/rating.types.js
913
+ var RATING_MIN_THRESHOLD = 5;
914
+ function computeRatingAvg(ratingSum, ratingCount) {
915
+ if (ratingCount < RATING_MIN_THRESHOLD)
916
+ return null;
917
+ return ratingSum / ratingCount;
918
+ }
919
+
854
920
  // node_modules/@prmichaelsen/remember-core/dist/types/space.types.js
855
921
  var SUPPORTED_SPACES = [
856
922
  "the_void",
@@ -1307,6 +1373,35 @@ var DebugLevel2;
1307
1373
  DebugLevel3[DebugLevel3["TRACE"] = 5] = "TRACE";
1308
1374
  })(DebugLevel2 || (DebugLevel2 = {}));
1309
1375
 
1376
+ // node_modules/@prmichaelsen/remember-core/dist/errors/base.error.js
1377
+ var AppError = class extends Error {
1378
+ context;
1379
+ constructor(message, context = {}) {
1380
+ super(message);
1381
+ this.context = context;
1382
+ this.name = this.constructor.name;
1383
+ Object.setPrototypeOf(this, new.target.prototype);
1384
+ }
1385
+ toJSON() {
1386
+ return {
1387
+ kind: this.kind,
1388
+ name: this.name,
1389
+ message: this.message,
1390
+ context: this.context
1391
+ };
1392
+ }
1393
+ };
1394
+
1395
+ // node_modules/@prmichaelsen/remember-core/dist/errors/app-errors.js
1396
+ var ValidationError = class extends AppError {
1397
+ fields;
1398
+ kind = "validation";
1399
+ constructor(message, fields = {}) {
1400
+ super(message, { fields });
1401
+ this.fields = fields;
1402
+ }
1403
+ };
1404
+
1310
1405
  // node_modules/@prmichaelsen/remember-core/node_modules/uuid/dist/esm/regex.js
1311
1406
  var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
1312
1407
 
@@ -1583,9 +1678,15 @@ function buildDocTypeFilters(collection, docType, filters) {
1583
1678
  if (filters?.relationship_count_max !== void 0) {
1584
1679
  filterList.push(collection.filter.byProperty("relationship_count").lessOrEqual(filters.relationship_count_max));
1585
1680
  }
1681
+ if (filters?.rating_min !== void 0) {
1682
+ filterList.push(collection.filter.byProperty("rating_bayesian").greaterOrEqual(filters.rating_min));
1683
+ }
1586
1684
  if (filters?.tags && filters.tags.length > 0) {
1587
1685
  filterList.push(collection.filter.byProperty("tags").containsAny(filters.tags));
1588
1686
  }
1687
+ if (filters?.memory_ids && filters.memory_ids.length > 0) {
1688
+ filterList.push(collection.filter.byId().containsAny(filters.memory_ids));
1689
+ }
1589
1690
  return combineFiltersWithAnd(filterList);
1590
1691
  }
1591
1692
  function buildMemoryOnlyFilters(collection, filters) {
@@ -1947,6 +2048,9 @@ var ALL_MEMORY_PROPERTIES = [
1947
2048
  "observation",
1948
2049
  "strength",
1949
2050
  "source",
2051
+ "rating_sum",
2052
+ "rating_count",
2053
+ "rating_bayesian",
1950
2054
  "access_count",
1951
2055
  "last_accessed_at",
1952
2056
  "tags",
@@ -1992,14 +2096,27 @@ function buildTrustFilter(collection, accessorTrustLevel) {
1992
2096
  }
1993
2097
 
1994
2098
  // node_modules/@prmichaelsen/remember-core/dist/services/memory.service.js
2099
+ function normalizeDoc(doc) {
2100
+ if ("trust_score" in doc) {
2101
+ doc.trust_score = normalizeTrustScore(doc.trust_score);
2102
+ }
2103
+ const ratingSum = doc.rating_sum;
2104
+ const ratingCount = doc.rating_count;
2105
+ if (ratingSum !== void 0 && ratingCount !== void 0) {
2106
+ doc.rating_avg = computeRatingAvg(ratingSum, ratingCount);
2107
+ }
2108
+ return doc;
2109
+ }
1995
2110
  var MemoryService = class {
1996
2111
  collection;
1997
2112
  userId;
1998
2113
  logger;
1999
- constructor(collection, userId, logger2) {
2114
+ options;
2115
+ constructor(collection, userId, logger2, options) {
2000
2116
  this.collection = collection;
2001
2117
  this.userId = userId;
2002
2118
  this.logger = logger2;
2119
+ this.options = options;
2003
2120
  }
2004
2121
  /**
2005
2122
  * Execute a search function, retrying without the deleted_at filter if
@@ -2022,7 +2139,31 @@ var MemoryService = class {
2022
2139
  throw new Error(`Memory not found: ${memoryId}`);
2023
2140
  if (existing.properties.user_id !== this.userId)
2024
2141
  throw new Error("Unauthorized");
2025
- return { memory: { id: existing.uuid, ...existing.properties } };
2142
+ return { memory: normalizeDoc({ id: existing.uuid, ...existing.properties }) };
2143
+ }
2144
+ // ── Resolve by ID (cross-collection) ─────────────────────────────────
2145
+ /**
2146
+ * Resolve any memory by UUID alone using the Firestore index.
2147
+ * Requires `memoryIndex` and `weaviateClient` in constructor options.
2148
+ */
2149
+ async resolveById(memoryId) {
2150
+ if (!this.options?.weaviateClient) {
2151
+ throw new Error("resolveById requires weaviateClient in options");
2152
+ }
2153
+ if (this.options?.memoryIndex) {
2154
+ const collectionName = await this.options.memoryIndex.lookup(memoryId);
2155
+ if (collectionName) {
2156
+ const col = this.options.weaviateClient.collections.get(collectionName);
2157
+ const memory = await fetchMemoryWithAllProperties(col, memoryId);
2158
+ if (memory?.properties) {
2159
+ return {
2160
+ memory: normalizeDoc({ id: memory.uuid, ...memory.properties }),
2161
+ collectionName
2162
+ };
2163
+ }
2164
+ }
2165
+ }
2166
+ return { memory: null, collectionName: null };
2026
2167
  }
2027
2168
  // ── Create ──────────────────────────────────────────────────────────
2028
2169
  async create(input) {
@@ -2036,12 +2177,16 @@ var MemoryService = class {
2036
2177
  summary: input.title,
2037
2178
  content_type: contentType,
2038
2179
  weight: input.weight ?? 0.5,
2039
- trust_score: input.trust ?? 0.25,
2180
+ trust_score: normalizeTrustScore(input.trust ?? TrustLevel.INTERNAL),
2040
2181
  confidence: 1,
2041
2182
  context_summary: input.context_summary || "Memory created",
2042
2183
  context_conversation_id: input.context_conversation_id,
2043
2184
  relationship_ids: [],
2044
2185
  relationship_count: 0,
2186
+ rating_sum: 0,
2187
+ rating_count: 0,
2188
+ rating_bayesian: 3,
2189
+ // (0 + 15) / (0 + 5) = 3.0 (prior mean)
2045
2190
  access_count: 0,
2046
2191
  last_accessed_at: now,
2047
2192
  created_at: now,
@@ -2061,6 +2206,14 @@ var MemoryService = class {
2061
2206
  };
2062
2207
  const memoryId = await this.collection.data.insert({ properties });
2063
2208
  this.logger.info("Memory created", { memoryId, userId: this.userId });
2209
+ if (this.options?.memoryIndex) {
2210
+ try {
2211
+ const collectionName = this.collection.name;
2212
+ await this.options.memoryIndex.index(memoryId, collectionName);
2213
+ } catch (err2) {
2214
+ this.logger.warn?.(`[MemoryService] Index write failed for ${memoryId}: ${err2}`);
2215
+ }
2216
+ }
2064
2217
  return { memory_id: memoryId, created_at: now };
2065
2218
  }
2066
2219
  // ── Search (hybrid) ─────────────────────────────────────────────────
@@ -2098,7 +2251,7 @@ var MemoryService = class {
2098
2251
  const memories = [];
2099
2252
  const relationships = [];
2100
2253
  for (const obj of paginated) {
2101
- const doc = { id: obj.uuid, ...obj.properties };
2254
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2102
2255
  if (doc.doc_type === "memory")
2103
2256
  memories.push(doc);
2104
2257
  else if (doc.doc_type === "relationship")
@@ -2141,7 +2294,7 @@ var MemoryService = class {
2141
2294
  const paginated = results.objects.slice(offset);
2142
2295
  const memories = [];
2143
2296
  for (const obj of paginated) {
2144
- const doc = { id: obj.uuid, ...obj.properties };
2297
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2145
2298
  if (doc.doc_type === "memory") {
2146
2299
  memories.push(doc);
2147
2300
  }
@@ -2185,7 +2338,48 @@ var MemoryService = class {
2185
2338
  const paginated = results.objects.slice(offset);
2186
2339
  const memories = [];
2187
2340
  for (const obj of paginated) {
2188
- const doc = { id: obj.uuid, ...obj.properties };
2341
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2342
+ if (doc.doc_type === "memory") {
2343
+ memories.push(doc);
2344
+ }
2345
+ }
2346
+ return {
2347
+ memories,
2348
+ total: memories.length,
2349
+ offset,
2350
+ limit
2351
+ };
2352
+ }
2353
+ // ── By Rating (Bayesian average) ─────────────────────────────────
2354
+ async byRating(input) {
2355
+ const limit = input.limit ?? 50;
2356
+ const offset = input.offset ?? 0;
2357
+ const direction = input.direction ?? "desc";
2358
+ const memoryFilters = buildMemoryOnlyFilters(this.collection, input.filters);
2359
+ const ghostFilters = [];
2360
+ if (input.ghost_context) {
2361
+ ghostFilters.push(buildTrustFilter(this.collection, input.ghost_context.accessor_trust_level));
2362
+ }
2363
+ if (!input.ghost_context?.include_ghost_content) {
2364
+ ghostFilters.push(this.collection.filter.byProperty("content_type").notEqual("ghost"));
2365
+ }
2366
+ const executeQuery = async (useDeletedFilter) => {
2367
+ const deletedFilter = useDeletedFilter ? buildDeletedFilter(this.collection, input.deleted_filter || "exclude") : null;
2368
+ const combinedFilters = combineFiltersWithAnd([deletedFilter, memoryFilters, ...ghostFilters].filter((f) => f !== null));
2369
+ const queryOptions = {
2370
+ limit: limit + offset,
2371
+ sort: this.collection.sort.byProperty("rating_bayesian", direction === "asc")
2372
+ };
2373
+ if (combinedFilters) {
2374
+ queryOptions.filters = combinedFilters;
2375
+ }
2376
+ return this.collection.query.fetchObjects(queryOptions);
2377
+ };
2378
+ const results = await this.retryWithoutDeletedFilter(executeQuery);
2379
+ const paginated = results.objects.slice(offset);
2380
+ const memories = [];
2381
+ for (const obj of paginated) {
2382
+ const doc = normalizeDoc({ id: obj.uuid, ...obj.properties });
2189
2383
  if (doc.doc_type === "memory") {
2190
2384
  memories.push(doc);
2191
2385
  }
@@ -2244,7 +2438,7 @@ var MemoryService = class {
2244
2438
  if (!input.include_relationships) {
2245
2439
  results.objects = results.objects.filter((o) => o.properties.doc_type === "memory");
2246
2440
  }
2247
- const items = results.objects.map((obj) => ({
2441
+ const items = results.objects.map((obj) => normalizeDoc({
2248
2442
  id: obj.uuid,
2249
2443
  ...obj.properties,
2250
2444
  similarity: Math.max(0, Math.min(1, 1 - (obj.metadata?.distance ?? 0)))
@@ -2273,7 +2467,7 @@ var MemoryService = class {
2273
2467
  opts.filters = combinedFilters;
2274
2468
  return this.collection.query.nearText(input.query, opts);
2275
2469
  });
2276
- const items = results.objects.map((obj) => ({
2470
+ const items = results.objects.map((obj) => normalizeDoc({
2277
2471
  id: obj.uuid,
2278
2472
  ...obj.properties,
2279
2473
  relevance: Math.max(0, Math.min(1, 1 - (obj.metadata?.distance ?? 0)))
@@ -2317,8 +2511,8 @@ var MemoryService = class {
2317
2511
  updatedFields.push("weight");
2318
2512
  }
2319
2513
  if (input.trust !== void 0) {
2320
- if (input.trust < 0 || input.trust > 1)
2321
- throw new Error("Trust must be between 0 and 1");
2514
+ if (!isValidTrustLevel(input.trust))
2515
+ throw new Error("Trust must be an integer between 1 and 5");
2322
2516
  updates.trust_score = input.trust;
2323
2517
  updatedFields.push("trust_score");
2324
2518
  }
@@ -2772,6 +2966,10 @@ var COMMON_MEMORY_PROPERTIES = [
2772
2966
  { name: "relationships", dataType: configure.dataType.TEXT_ARRAY },
2773
2967
  { name: "memory_ids", dataType: configure.dataType.TEXT_ARRAY },
2774
2968
  { name: "relationship_count", dataType: configure.dataType.INT },
2969
+ // Rating aggregates (denormalized from Firestore individual ratings)
2970
+ { name: "rating_sum", dataType: configure.dataType.INT },
2971
+ { name: "rating_count", dataType: configure.dataType.INT },
2972
+ { name: "rating_bayesian", dataType: configure.dataType.NUMBER },
2775
2973
  // Access tracking
2776
2974
  { name: "access_count", dataType: configure.dataType.NUMBER },
2777
2975
  { name: "last_accessed_at", dataType: configure.dataType.DATE },
@@ -2948,12 +3146,26 @@ var SpaceService = class {
2948
3146
  userId;
2949
3147
  confirmationTokenService;
2950
3148
  logger;
2951
- constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2) {
3149
+ moderationClient;
3150
+ constructor(weaviateClient, userCollection, userId, confirmationTokenService, logger2, options) {
2952
3151
  this.weaviateClient = weaviateClient;
2953
3152
  this.userCollection = userCollection;
2954
3153
  this.userId = userId;
2955
3154
  this.confirmationTokenService = confirmationTokenService;
2956
3155
  this.logger = logger2;
3156
+ this.moderationClient = options?.moderationClient;
3157
+ }
3158
+ // ── Content moderation helper ────────────────────────────────────────
3159
+ async checkModeration(content) {
3160
+ if (!this.moderationClient)
3161
+ return;
3162
+ const result = await this.moderationClient.moderate(content);
3163
+ if (!result.pass) {
3164
+ throw new ValidationError(result.reason, {
3165
+ moderation: ["blocked"],
3166
+ ...result.category ? { category: [result.category] } : {}
3167
+ });
3168
+ }
2957
3169
  }
2958
3170
  // ── Publish (phase 1: generate confirmation token) ──────────────────
2959
3171
  async publish(input) {
@@ -2988,6 +3200,7 @@ var SpaceService = class {
2988
3200
  throw new Error(`Space '${spaceId}' only accepts content_type '${requiredType}', got '${memoryContentType ?? "undefined"}'`);
2989
3201
  }
2990
3202
  }
3203
+ await this.checkModeration(memory.properties.content);
2991
3204
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "publish_memory", {
2992
3205
  memory_id: input.memory_id,
2993
3206
  spaces,
@@ -3054,6 +3267,7 @@ var SpaceService = class {
3054
3267
  if (spaceIds.length === 0 && groupIds.length === 0) {
3055
3268
  throw new Error("Memory has no published copies to revise. Publish first with publish().");
3056
3269
  }
3270
+ await this.checkModeration(memory.properties.content);
3057
3271
  const { token } = await this.confirmationTokenService.createRequest(this.userId, "revise_memory", {
3058
3272
  memory_id: input.memory_id,
3059
3273
  space_ids: spaceIds,
package/esbuild.build.js CHANGED
@@ -13,7 +13,10 @@ await esbuild.build({
13
13
  external: [
14
14
  'weaviate-client',
15
15
  '@prmichaelsen/firebase-admin-sdk-v8',
16
- '@modelcontextprotocol/sdk'
16
+ '@modelcontextprotocol/sdk',
17
+ 'unpdf',
18
+ 'mammoth',
19
+ 'turndown',
17
20
  ],
18
21
  banner: {
19
22
  js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);"
@@ -35,7 +38,10 @@ await esbuild.build({
35
38
  external: [
36
39
  'weaviate-client',
37
40
  '@prmichaelsen/firebase-admin-sdk-v8',
38
- '@modelcontextprotocol/sdk'
41
+ '@modelcontextprotocol/sdk',
42
+ 'unpdf',
43
+ 'mammoth',
44
+ 'turndown',
39
45
  ],
40
46
  banner: {
41
47
  js: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/remember-mcp",
3
- "version": "3.14.18",
3
+ "version": "3.14.20",
4
4
  "description": "Multi-tenant memory system MCP server with vector search and relationships",
5
5
  "main": "dist/server.js",
6
6
  "type": "module",
@@ -50,7 +50,7 @@
50
50
  "@modelcontextprotocol/sdk": "^1.0.4",
51
51
  "@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0",
52
52
  "@prmichaelsen/mcp-auth": "^7.0.4",
53
- "@prmichaelsen/remember-core": "^0.27.3",
53
+ "@prmichaelsen/remember-core": "^0.32.1",
54
54
  "dotenv": "^16.4.5",
55
55
  "uuid": "^13.0.0",
56
56
  "weaviate-client": "^3.2.0"