slashvibe-mcp 0.3.21 → 0.3.23

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.
Files changed (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +280 -47
  3. package/auto-update.js +10 -15
  4. package/config.js +36 -31
  5. package/crypto.js +1 -6
  6. package/debug.js +12 -0
  7. package/discord.js +19 -19
  8. package/eslint.config.js +54 -0
  9. package/index.js +217 -207
  10. package/intelligence/index.js +2 -9
  11. package/intelligence/infer.js +10 -16
  12. package/intelligence/patterns.js +23 -18
  13. package/intelligence/proactive.js +16 -15
  14. package/intelligence/serendipity.js +57 -20
  15. package/memory.js +13 -8
  16. package/migrate-v2.js +72 -0
  17. package/notification-emitter.js +2 -2
  18. package/notify.js +39 -14
  19. package/package.json +28 -29
  20. package/post-install.js +141 -0
  21. package/presence.js +2 -2
  22. package/prompts.js +5 -9
  23. package/protocol/index.js +123 -87
  24. package/protocol/telegram-commands.js +36 -37
  25. package/store/api.js +358 -529
  26. package/store/local.js +9 -10
  27. package/store/profiles.js +48 -192
  28. package/store/reservations.js +2 -9
  29. package/store/skills.js +69 -71
  30. package/store/sqlite.js +355 -0
  31. package/test-skills-bootstrap.js +20 -0
  32. package/test-v2-integration.js +385 -0
  33. package/tools/_actions.js +48 -387
  34. package/tools/_connection-queue.js +45 -56
  35. package/tools/_discovery-enhanced.js +52 -57
  36. package/tools/_discovery.js +87 -185
  37. package/tools/{l2-status.js → _experimental/l2-status.js} +68 -70
  38. package/tools/{shipback.js → _experimental/shipback.js} +4 -3
  39. package/tools/_proactive-discovery.js +60 -73
  40. package/tools/_shared/index.js +41 -64
  41. package/tools/admin-inbox.js +10 -15
  42. package/tools/agents.js +1 -1
  43. package/tools/artifact-create.js +13 -23
  44. package/tools/artifact-view.js +4 -4
  45. package/tools/{_deprecated/back.js → back.js} +1 -1
  46. package/tools/bye.js +3 -5
  47. package/tools/consent.js +2 -2
  48. package/tools/context.js +9 -10
  49. package/tools/crossword.js +3 -2
  50. package/tools/discover.js +94 -356
  51. package/tools/dm.js +27 -86
  52. package/tools/doctor.js +12 -41
  53. package/tools/drawing.js +34 -20
  54. package/tools/echo.js +11 -11
  55. package/tools/feed.js +30 -58
  56. package/tools/follow.js +64 -187
  57. package/tools/{_deprecated/forget.js → forget.js} +4 -7
  58. package/tools/game.js +144 -48
  59. package/tools/handoff.js +6 -8
  60. package/tools/help.js +3 -3
  61. package/tools/idea.js +15 -27
  62. package/tools/inbox.js +121 -293
  63. package/tools/init.js +54 -151
  64. package/tools/invite.js +8 -21
  65. package/tools/migrate.js +27 -24
  66. package/tools/multiplayer-game.js +50 -40
  67. package/tools/{_deprecated/mute.js → mute.js} +4 -3
  68. package/tools/notifications.js +58 -48
  69. package/tools/observe.js +12 -15
  70. package/tools/onboarding.js +8 -11
  71. package/tools/open.js +13 -144
  72. package/tools/party-game.js +23 -12
  73. package/tools/patterns.js +2 -1
  74. package/tools/ping.js +5 -7
  75. package/tools/react.js +28 -30
  76. package/tools/{_deprecated/recall.js → recall.js} +5 -10
  77. package/tools/release.js +4 -2
  78. package/tools/{_deprecated/remember.js → remember.js} +4 -6
  79. package/tools/report.js +2 -2
  80. package/tools/request.js +6 -26
  81. package/tools/reserve.js +1 -1
  82. package/tools/session-fork.js +97 -0
  83. package/tools/session-save.js +109 -0
  84. package/tools/settings.js +30 -99
  85. package/tools/ship.js +74 -56
  86. package/tools/{_deprecated/skills-exchange.js → skills-exchange.js} +38 -39
  87. package/tools/social-inbox.js +22 -28
  88. package/tools/social-post.js +24 -27
  89. package/tools/solo-game.js +54 -46
  90. package/tools/start.js +14 -148
  91. package/tools/status.js +21 -68
  92. package/tools/submit.js +4 -2
  93. package/tools/suggest-tags.js +36 -33
  94. package/tools/summarize.js +19 -16
  95. package/tools/tag-suggestions.js +72 -73
  96. package/tools/test.js +1 -1
  97. package/tools/{_deprecated/tictactoe.js → tictactoe.js} +26 -26
  98. package/tools/token.js +4 -4
  99. package/tools/update.js +1 -2
  100. package/tools/watch.js +132 -112
  101. package/tools/who.js +20 -40
  102. package/tools/{_deprecated/wordassociation.js → wordassociation.js} +23 -20
  103. package/tools/workshop-buddy.js +52 -53
  104. package/tools/x-mentions.js +0 -1
  105. package/tools/x-reply.js +0 -1
  106. package/twitter.js +14 -20
  107. package/version.json +8 -10
  108. package/webhook-runner.js +132 -0
  109. package/auth-store.js +0 -148
  110. package/bridges/bridge-monitor.js +0 -388
  111. package/bridges/discord-bot.js +0 -431
  112. package/bridges/farcaster.js +0 -299
  113. package/bridges/telegram.js +0 -261
  114. package/bridges/webhook-health.js +0 -420
  115. package/bridges/webhook-server.js +0 -437
  116. package/bridges/whatsapp.js +0 -441
  117. package/bridges/x-webhook.js +0 -423
  118. package/games/arcade.js +0 -406
  119. package/games/chess.js +0 -451
  120. package/games/colorguess.js +0 -343
  121. package/games/crossword-words.js +0 -171
  122. package/games/crossword.js +0 -461
  123. package/games/drawing.js +0 -347
  124. package/games/gameroulette.js +0 -300
  125. package/games/gamerouter.js +0 -336
  126. package/games/gamestatus.js +0 -337
  127. package/games/guessnumber.js +0 -209
  128. package/games/hangman.js +0 -279
  129. package/games/memory.js +0 -338
  130. package/games/multiplayer-tictactoe.js +0 -389
  131. package/games/pixelart.js +0 -399
  132. package/games/quickduel.js +0 -354
  133. package/games/riddle.js +0 -371
  134. package/games/rockpaperscissors.js +0 -291
  135. package/games/snake.js +0 -406
  136. package/games/storybuilder.js +0 -343
  137. package/games/tictactoe.js +0 -345
  138. package/games/twentyquestions.js +0 -286
  139. package/games/twotruths.js +0 -207
  140. package/games/werewolf.js +0 -508
  141. package/games/wordassociation.js +0 -247
  142. package/games/wordchain.js +0 -135
  143. package/intelligence/interests.js +0 -369
  144. package/setup.js +0 -480
  145. package/smart-inbox.js +0 -276
  146. package/tools/_deprecated/auto-suggest-connections.js +0 -304
  147. package/tools/_deprecated/bootstrap-skills.js +0 -231
  148. package/tools/_deprecated/bridge-dashboard.js +0 -342
  149. package/tools/_deprecated/bridge-health.js +0 -400
  150. package/tools/_deprecated/bridge-live.js +0 -384
  151. package/tools/_deprecated/bridges.js +0 -383
  152. package/tools/_deprecated/colorguess.js +0 -281
  153. package/tools/_deprecated/discover-insights.js +0 -379
  154. package/tools/_deprecated/discover-momentum.js +0 -256
  155. package/tools/_deprecated/discovery-analytics.js +0 -345
  156. package/tools/_deprecated/discovery-auto-suggest.js +0 -275
  157. package/tools/_deprecated/discovery-bootstrap.js +0 -267
  158. package/tools/_deprecated/discovery-daily.js +0 -375
  159. package/tools/_deprecated/discovery-dashboard.js +0 -385
  160. package/tools/_deprecated/discovery-digest.js +0 -314
  161. package/tools/_deprecated/discovery-hub.js +0 -357
  162. package/tools/_deprecated/discovery-insights.js +0 -384
  163. package/tools/_deprecated/discovery-momentum.js +0 -281
  164. package/tools/_deprecated/discovery-monitor.js +0 -319
  165. package/tools/_deprecated/discovery-proactive.js +0 -300
  166. package/tools/_deprecated/draw.js +0 -317
  167. package/tools/_deprecated/farcaster.js +0 -307
  168. package/tools/_deprecated/games-catalog.js +0 -376
  169. package/tools/_deprecated/games.js +0 -313
  170. package/tools/_deprecated/guessnumber.js +0 -194
  171. package/tools/_deprecated/hangman.js +0 -129
  172. package/tools/_deprecated/multiplayer-tictactoe.js +0 -303
  173. package/tools/_deprecated/riddle.js +0 -240
  174. package/tools/_deprecated/run-bootstrap.js +0 -69
  175. package/tools/_deprecated/skills-analytics.js +0 -349
  176. package/tools/_deprecated/skills-bootstrap.js +0 -301
  177. package/tools/_deprecated/skills-dashboard.js +0 -268
  178. package/tools/_deprecated/skills.js +0 -380
  179. package/tools/_deprecated/smart-intro.js +0 -353
  180. package/tools/_deprecated/storybuilder.js +0 -331
  181. package/tools/_deprecated/telegram-bot.js +0 -183
  182. package/tools/_deprecated/telegram-setup.js +0 -214
  183. package/tools/_deprecated/twentyquestions.js +0 -143
  184. package/tools/_shared.js +0 -234
  185. package/tools/_work-context.js +0 -338
  186. package/tools/_work-context.manual-test.js +0 -199
  187. package/tools/_work-context.test.js +0 -260
  188. package/tools/activity.js +0 -220
  189. package/tools/agent-treasury.js +0 -288
  190. package/tools/analytics.js +0 -191
  191. package/tools/approve.js +0 -197
  192. package/tools/arcade.js +0 -173
  193. package/tools/artifacts-price.js +0 -107
  194. package/tools/ask-expert.js +0 -160
  195. package/tools/available.js +0 -120
  196. package/tools/become-expert.js +0 -150
  197. package/tools/broadcast.js +0 -325
  198. package/tools/chat.js +0 -202
  199. package/tools/collaborative-drawing.js +0 -286
  200. package/tools/connection-status.js +0 -178
  201. package/tools/earnings.js +0 -126
  202. package/tools/friends.js +0 -207
  203. package/tools/genesis.js +0 -233
  204. package/tools/gig-browse.js +0 -206
  205. package/tools/gig-complete.js +0 -144
  206. package/tools/health.js +0 -87
  207. package/tools/leaderboard.js +0 -117
  208. package/tools/lib/git-apply.js +0 -206
  209. package/tools/lib/git-bundle.js +0 -407
  210. package/tools/mint.js +0 -377
  211. package/tools/plan.js +0 -225
  212. package/tools/profile.js +0 -219
  213. package/tools/proof-of-work.js +0 -144
  214. package/tools/pulse.js +0 -218
  215. package/tools/reply.js +0 -166
  216. package/tools/reputation.js +0 -175
  217. package/tools/schedule.js +0 -367
  218. package/tools/search-messages.js +0 -123
  219. package/tools/session.js +0 -467
  220. package/tools/session_price.js +0 -128
  221. package/tools/smart-check.js +0 -201
  222. package/tools/social-processor.js +0 -445
  223. package/tools/streak.js +0 -147
  224. package/tools/stuck.js +0 -297
  225. package/tools/subscribe.js +0 -148
  226. package/tools/subscriptions.js +0 -134
  227. package/tools/tip.js +0 -193
  228. package/tools/wallet.js +0 -269
  229. package/tools/webhook-test.js +0 -388
  230. package/tools/withdraw.js +0 -145
  231. package/tools/work-summary.js +0 -96
  232. package/tools/workshop.js +0 -327
  233. /package/tools/{l2-bridge.js → _experimental/l2-bridge.js} +0 -0
  234. /package/tools/{l2.js → _experimental/l2.js} +0 -0
  235. /package/tools/{_deprecated/away.js → away.js} +0 -0
package/store/local.js CHANGED
@@ -91,7 +91,9 @@ function loadMessages() {
91
91
  try {
92
92
  if (fs.existsSync(MESSAGES_FILE)) {
93
93
  const content = fs.readFileSync(MESSAGES_FILE, 'utf8');
94
- return content.trim().split('\n')
94
+ return content
95
+ .trim()
96
+ .split('\n')
95
97
  .filter(line => line.length > 0)
96
98
  .map(line => JSON.parse(line));
97
99
  }
@@ -122,9 +124,7 @@ async function getInbox(handle) {
122
124
  const h = handle.toLowerCase().replace('@', '');
123
125
 
124
126
  // Get messages TO this user
125
- return messages
126
- .filter(m => m.to === h)
127
- .sort((a, b) => b.timestamp - a.timestamp);
127
+ return messages.filter(m => m.to === h).sort((a, b) => b.timestamp - a.timestamp);
128
128
  }
129
129
 
130
130
  async function getUnreadCount(handle) {
@@ -144,10 +144,7 @@ async function getThread(myHandle, theirHandle) {
144
144
 
145
145
  // Get messages between these two users
146
146
  return messages
147
- .filter(m =>
148
- (m.from === me && m.to === them) ||
149
- (m.from === them && m.to === me)
150
- )
147
+ .filter(m => (m.from === me && m.to === them) || (m.from === them && m.to === me))
151
148
  .sort((a, b) => a.timestamp - b.timestamp);
152
149
  }
153
150
 
@@ -175,7 +172,9 @@ function loadSkillExchanges() {
175
172
  try {
176
173
  if (fs.existsSync(SKILL_EXCHANGE_FILE)) {
177
174
  const content = fs.readFileSync(SKILL_EXCHANGE_FILE, 'utf8');
178
- return content.trim().split('\n')
175
+ return content
176
+ .trim()
177
+ .split('\n')
179
178
  .filter(line => line.length > 0)
180
179
  .map(line => JSON.parse(line));
181
180
  }
@@ -260,4 +259,4 @@ module.exports = {
260
259
 
261
260
  // Helpers
262
261
  formatTimeAgo
263
- };
262
+ };
package/store/profiles.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Stores rich user data for matchmaking:
5
5
  * - What they're building
6
- * - Interests (broad topics they care about)
6
+ * - Interests (broad topics they care about)
7
7
  * - Tags (specific skills/technologies)
8
8
  * - Activity patterns
9
9
  * - Connection history
@@ -41,17 +41,19 @@ function saveProfiles(profiles) {
41
41
  async function getProfile(handle) {
42
42
  const profiles = loadProfiles();
43
43
  const key = handle.toLowerCase().replace('@', '');
44
-
45
- return profiles[key] || {
46
- handle: key,
47
- building: null,
48
- interests: [],
49
- tags: [],
50
- lastSeen: null,
51
- firstSeen: null,
52
- connections: [],
53
- ships: []
54
- };
44
+
45
+ return (
46
+ profiles[key] || {
47
+ handle: key,
48
+ building: null,
49
+ interests: [],
50
+ tags: [],
51
+ lastSeen: null,
52
+ firstSeen: null,
53
+ connections: [],
54
+ ships: []
55
+ }
56
+ );
55
57
  }
56
58
 
57
59
  // Update user profile
@@ -77,17 +79,19 @@ async function updateProfile(handle, updates) {
77
79
  if (updates.interests) {
78
80
  updated.interests = Array.isArray(updates.interests)
79
81
  ? updates.interests
80
- : updates.interests.split(',').map(s => s.trim()).filter(s => s);
81
-
82
- // IMPORTANT: Clear inferred flag when user explicitly sets interests
83
- // This prevents inferMissingInterests() from overwriting explicit choices
84
- updated.interests_inferred = false;
82
+ : updates.interests
83
+ .split(',')
84
+ .map(s => s.trim())
85
+ .filter(s => s);
85
86
  }
86
87
 
87
88
  if (updates.tags) {
88
89
  updated.tags = Array.isArray(updates.tags)
89
90
  ? updates.tags
90
- : updates.tags.split(',').map(s => s.trim()).filter(s => s);
91
+ : updates.tags
92
+ .split(',')
93
+ .map(s => s.trim())
94
+ .filter(s => s);
91
95
  }
92
96
 
93
97
  // Update timestamps
@@ -113,23 +117,23 @@ async function recordConnection(from, to, reason) {
113
117
  const profiles = loadProfiles();
114
118
  const fromKey = from.toLowerCase().replace('@', '');
115
119
  const toKey = to.toLowerCase().replace('@', '');
116
-
120
+
117
121
  const connection = {
118
122
  timestamp: Date.now(),
119
123
  reason,
120
124
  suggested_by: 'discovery-agent'
121
125
  };
122
-
126
+
123
127
  // Add to both profiles
124
128
  if (!profiles[fromKey]) profiles[fromKey] = createEmptyProfile(fromKey);
125
129
  if (!profiles[toKey]) profiles[toKey] = createEmptyProfile(toKey);
126
-
130
+
127
131
  if (!profiles[fromKey].connections) profiles[fromKey].connections = [];
128
132
  if (!profiles[toKey].connections) profiles[toKey].connections = [];
129
-
133
+
130
134
  profiles[fromKey].connections.push({ handle: toKey, ...connection });
131
135
  profiles[toKey].connections.push({ handle: fromKey, ...connection });
132
-
136
+
133
137
  saveProfiles(profiles);
134
138
  }
135
139
 
@@ -137,23 +141,23 @@ async function recordConnection(from, to, reason) {
137
141
  async function recordShip(handle, what) {
138
142
  const profiles = loadProfiles();
139
143
  const key = handle.toLowerCase().replace('@', '');
140
-
144
+
141
145
  if (!profiles[key]) {
142
146
  profiles[key] = createEmptyProfile(key);
143
147
  }
144
-
148
+
145
149
  if (!profiles[key].ships) {
146
150
  profiles[key].ships = [];
147
151
  }
148
-
152
+
149
153
  profiles[key].ships.unshift({
150
154
  what,
151
155
  timestamp: Date.now()
152
156
  });
153
-
157
+
154
158
  // Keep only last 10 ships
155
159
  profiles[key].ships = profiles[key].ships.slice(0, 10);
156
-
160
+
157
161
  saveProfiles(profiles);
158
162
  }
159
163
 
@@ -161,7 +165,7 @@ async function recordShip(handle, what) {
161
165
  async function updateLastSeen(handle) {
162
166
  const profiles = loadProfiles();
163
167
  const key = handle.toLowerCase().replace('@', '');
164
-
168
+
165
169
  if (profiles[key]) {
166
170
  profiles[key].lastSeen = Date.now();
167
171
  saveProfiles(profiles);
@@ -172,7 +176,7 @@ async function updateLastSeen(handle) {
172
176
  async function getConnectionHistory(handle1, handle2) {
173
177
  const profile = await getProfile(handle1);
174
178
  const key2 = handle2.toLowerCase().replace('@', '');
175
-
179
+
176
180
  return profile.connections?.filter(c => c.handle === key2) || [];
177
181
  }
178
182
 
@@ -186,27 +190,23 @@ async function hasBeenConnected(handle1, handle2) {
186
190
  async function getUsersByInterest(interest) {
187
191
  const profiles = await getAllProfiles();
188
192
  const searchTerm = interest.toLowerCase();
189
-
190
- return profiles.filter(p =>
191
- p.interests?.some(i => i.toLowerCase().includes(searchTerm))
192
- );
193
+
194
+ return profiles.filter(p => p.interests?.some(i => i.toLowerCase().includes(searchTerm)));
193
195
  }
194
196
 
195
197
  // Get users by tag
196
198
  async function getUsersByTag(tag) {
197
199
  const profiles = await getAllProfiles();
198
200
  const searchTerm = tag.toLowerCase();
199
-
200
- return profiles.filter(p =>
201
- p.tags?.some(t => t.toLowerCase().includes(searchTerm))
202
- );
201
+
202
+ return profiles.filter(p => p.tags?.some(t => t.toLowerCase().includes(searchTerm)));
203
203
  }
204
204
 
205
205
  // Get trending interests
206
206
  async function getTrendingInterests() {
207
207
  const profiles = await getAllProfiles();
208
208
  const interestCount = {};
209
-
209
+
210
210
  for (const profile of profiles) {
211
211
  if (profile.interests) {
212
212
  for (const interest of profile.interests) {
@@ -214,18 +214,18 @@ async function getTrendingInterests() {
214
214
  }
215
215
  }
216
216
  }
217
-
217
+
218
218
  return Object.entries(interestCount)
219
- .sort(([,a], [,b]) => b - a)
219
+ .sort(([, a], [, b]) => b - a)
220
220
  .slice(0, 10)
221
221
  .map(([interest, count]) => ({ interest, count }));
222
222
  }
223
223
 
224
- // Get trending tags
224
+ // Get trending tags
225
225
  async function getTrendingTags() {
226
226
  const profiles = await getAllProfiles();
227
227
  const tagCount = {};
228
-
228
+
229
229
  for (const profile of profiles) {
230
230
  if (profile.tags) {
231
231
  for (const tag of profile.tags) {
@@ -233,9 +233,9 @@ async function getTrendingTags() {
233
233
  }
234
234
  }
235
235
  }
236
-
236
+
237
237
  return Object.entries(tagCount)
238
- .sort(([,a], [,b]) => b - a)
238
+ .sort(([, a], [, b]) => b - a)
239
239
  .slice(0, 15)
240
240
  .map(([tag, count]) => ({ tag, count }));
241
241
  }
@@ -257,7 +257,7 @@ function createEmptyProfile(handle) {
257
257
  // Clean up old profiles (for maintenance)
258
258
  async function cleanupOldProfiles(daysThreshold = 30) {
259
259
  const profiles = loadProfiles();
260
- const cutoff = Date.now() - (daysThreshold * 24 * 60 * 60 * 1000);
260
+ const cutoff = Date.now() - daysThreshold * 24 * 60 * 60 * 1000;
261
261
 
262
262
  let cleaned = 0;
263
263
  for (const [key, profile] of Object.entries(profiles)) {
@@ -274,146 +274,6 @@ async function cleanupOldProfiles(daysThreshold = 30) {
274
274
  return cleaned;
275
275
  }
276
276
 
277
- /**
278
- * Sync profile data from API presence
279
- * Called after fetching active users to keep local profiles up-to-date
280
- *
281
- * @param {Array} presenceData - Array of user objects from /api/presence
282
- * @returns {number} Number of profiles synced
283
- */
284
- async function syncFromPresence(presenceData) {
285
- if (!presenceData || !Array.isArray(presenceData)) return 0;
286
-
287
- const profiles = loadProfiles();
288
- let changed = false;
289
- let buildingsUpdated = 0;
290
-
291
- for (const user of presenceData) {
292
- // Skip users without a handle
293
- if (!user.handle && !user.username) continue;
294
-
295
- const handle = user.handle || user.username;
296
- const key = handle.toLowerCase().replace('@', '');
297
-
298
- // Create profile if it doesn't exist
299
- const isNew = !profiles[key];
300
- if (isNew) {
301
- profiles[key] = createEmptyProfile(key);
302
- changed = true;
303
- }
304
-
305
- // Sync building description (one_liner from presence)
306
- const building = user.one_liner || user.workingOn;
307
- if (building && building !== profiles[key].building) {
308
- profiles[key].building = building;
309
- buildingsUpdated++;
310
- changed = true;
311
- }
312
-
313
- // Update timestamps (always sync these)
314
- if (user.lastSeen) {
315
- const ts = typeof user.lastSeen === 'number'
316
- ? user.lastSeen
317
- : new Date(user.lastSeen).getTime();
318
- // Only update if valid and different
319
- if (!isNaN(ts) && ts !== profiles[key].lastSeen) {
320
- profiles[key].lastSeen = ts;
321
- changed = true;
322
- }
323
- }
324
- if (user.firstSeen && !profiles[key].firstSeen) {
325
- const ts = typeof user.firstSeen === 'number'
326
- ? user.firstSeen
327
- : new Date(user.firstSeen).getTime();
328
- if (!isNaN(ts)) {
329
- profiles[key].firstSeen = ts;
330
- changed = true;
331
- }
332
- }
333
-
334
- // Sync mood if present and different
335
- if (user.mood && user.mood !== profiles[key].mood) {
336
- profiles[key].mood = user.mood;
337
- changed = true;
338
- }
339
- }
340
-
341
- // Only write if something actually changed
342
- if (changed) {
343
- saveProfiles(profiles);
344
- if (buildingsUpdated > 0) {
345
- console.error(`[profiles] Synced ${buildingsUpdated} building descriptions from presence`);
346
- }
347
- }
348
-
349
- return buildingsUpdated;
350
- }
351
-
352
- /**
353
- * Infer interests from building descriptions for profiles that don't have them
354
- * Uses suggestInterestsFromBuilding() from _discovery.js
355
- *
356
- * @returns {number} Number of profiles with newly inferred interests
357
- */
358
- async function inferMissingInterests() {
359
- // Import discovery module (lazy load to avoid circular deps)
360
- let suggestInterestsFromBuilding;
361
- try {
362
- const discovery = require('../tools/_discovery');
363
- suggestInterestsFromBuilding = discovery.suggestInterestsFromBuilding;
364
- } catch (e) {
365
- console.error('[profiles] Failed to load discovery module:', e.message);
366
- return 0;
367
- }
368
-
369
- const profiles = loadProfiles();
370
- let inferred = 0;
371
- let changed = false;
372
-
373
- for (const [key, profile] of Object.entries(profiles)) {
374
- // Skip if user has explicit (non-inferred) interests
375
- if (profile.interests?.length > 0 && !profile.interests_inferred) {
376
- continue;
377
- }
378
-
379
- // Infer from building description
380
- if (profile.building) {
381
- const suggested = suggestInterestsFromBuilding(profile.building);
382
- if (suggested.length > 0) {
383
- // Only update if interests actually changed (avoid repeated writes)
384
- const existingInterests = (profile.interests || []).slice().sort().join(',');
385
- const newInterests = suggested.slice().sort().join(',');
386
-
387
- if (existingInterests !== newInterests) {
388
- profiles[key].interests = suggested;
389
- profiles[key].interests_inferred = true; // Mark as auto-generated
390
- profiles[key].interests_updated_at = Date.now();
391
- inferred++;
392
- changed = true;
393
- }
394
- }
395
- }
396
- }
397
-
398
- if (changed) {
399
- saveProfiles(profiles);
400
- console.error(`[profiles] Inferred interests for ${inferred} profiles`);
401
- }
402
-
403
- return inferred;
404
- }
405
-
406
- /**
407
- * Get profiles with inferred interests for discovery
408
- * Combines explicit and inferred interests
409
- *
410
- * @returns {Array} All profiles with interests (explicit or inferred)
411
- */
412
- async function getProfilesWithInterests() {
413
- const profiles = await getAllProfiles();
414
- return profiles.filter(p => p.interests?.length > 0);
415
- }
416
-
417
277
  module.exports = {
418
278
  getProfile,
419
279
  updateProfile,
@@ -427,9 +287,5 @@ module.exports = {
427
287
  getUsersByTag,
428
288
  getTrendingInterests,
429
289
  getTrendingTags,
430
- cleanupOldProfiles,
431
- // Presence sync (Phase 1)
432
- syncFromPresence,
433
- inferMissingInterests,
434
- getProfilesWithInterests
435
- };
290
+ cleanupOldProfiles
291
+ };
@@ -186,12 +186,7 @@ function checkConflicts(paths, exclusive = true, scope = null) {
186
186
 
187
187
  // Create a new reservation
188
188
  function create(owner, paths, options = {}) {
189
- const {
190
- ttl_seconds = 3600,
191
- exclusive = true,
192
- reason = null,
193
- thread_id = null
194
- } = options;
189
+ const { ttl_seconds = 3600, exclusive = true, reason = null, thread_id = null } = options;
195
190
 
196
191
  const scope = getScope();
197
192
  const now = new Date();
@@ -273,9 +268,7 @@ function list(options = {}) {
273
268
 
274
269
  // Filter by path
275
270
  if (path_filter) {
276
- reservations = reservations.filter(r =>
277
- r.paths.some(p => p.includes(path_filter) || path_filter.includes(p))
278
- );
271
+ reservations = reservations.filter(r => r.paths.some(p => p.includes(path_filter) || path_filter.includes(p)));
279
272
  }
280
273
 
281
274
  return reservations;