wayfind 2.0.24 → 2.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/digest.js +1 -1
- package/bin/intelligence.js +64 -0
- package/bin/slack.js +30 -4
- package/bin/team-context.js +24 -2
- package/package.json +1 -1
package/bin/digest.js
CHANGED
|
@@ -805,7 +805,7 @@ async function generateDigest(config, personaIds, sinceDate, onProgress) {
|
|
|
805
805
|
fs.writeFileSync(combinedFile, combinedContent, 'utf8');
|
|
806
806
|
files.push(combinedFile);
|
|
807
807
|
|
|
808
|
-
return { files, personas: personaIds, dateRange };
|
|
808
|
+
return { files, personas: personaIds, dateRange, scores };
|
|
809
809
|
}
|
|
810
810
|
|
|
811
811
|
/**
|
package/bin/intelligence.js
CHANGED
|
@@ -145,9 +145,73 @@ function filterForPersona(signalContent, journalContent, scores, personaId, thre
|
|
|
145
145
|
return { signals: filteredSignals, journals: filteredJournals };
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Build per-member mention data for a digest persona.
|
|
150
|
+
* Counts how many items scored 2 (directly relevant) for each member's personas.
|
|
151
|
+
*
|
|
152
|
+
* @param {Array<{id: number, [personaId]: number}>} scores - Scoring results
|
|
153
|
+
* @param {Array<{name: string, slack_user_id?: string, personas?: string[]}>} members - Team members
|
|
154
|
+
* @param {string} digestPersonaId - The persona this digest is for
|
|
155
|
+
* @returns {Array<{name: string, slackId: string, count: number}>} Members to mention, sorted by count desc
|
|
156
|
+
*/
|
|
157
|
+
function buildMentions(scores, members, digestPersonaId) {
|
|
158
|
+
if (!scores || !members || members.length === 0) return [];
|
|
159
|
+
|
|
160
|
+
const MENTION_THRESHOLD = 2; // Only mention for directly relevant items
|
|
161
|
+
const mentions = [];
|
|
162
|
+
|
|
163
|
+
for (const member of members) {
|
|
164
|
+
if (!member.slack_user_id) continue;
|
|
165
|
+
const memberPersonas = member.personas || [];
|
|
166
|
+
|
|
167
|
+
// For unified digest: check all of the member's personas
|
|
168
|
+
// For persona-specific digest: only mention if member has that persona
|
|
169
|
+
const relevantPersonas = digestPersonaId === 'unified'
|
|
170
|
+
? memberPersonas
|
|
171
|
+
: memberPersonas.filter(p => p === digestPersonaId);
|
|
172
|
+
|
|
173
|
+
if (relevantPersonas.length === 0) continue;
|
|
174
|
+
|
|
175
|
+
// Count items that scored >= MENTION_THRESHOLD for any of the member's relevant personas
|
|
176
|
+
let count = 0;
|
|
177
|
+
for (const score of scores) {
|
|
178
|
+
const isRelevant = relevantPersonas.some(p => (score[p] ?? 0) >= MENTION_THRESHOLD);
|
|
179
|
+
if (isRelevant) count++;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (count > 0) {
|
|
183
|
+
mentions.push({
|
|
184
|
+
name: member.name || 'unknown',
|
|
185
|
+
slackId: member.slack_user_id,
|
|
186
|
+
count,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return mentions.sort((a, b) => b.count - a.count);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Format mentions into a Slack mrkdwn message for thread reply.
|
|
196
|
+
* @param {Array<{name: string, slackId: string, count: number}>} mentions
|
|
197
|
+
* @returns {string|null} Formatted message or null if no mentions
|
|
198
|
+
*/
|
|
199
|
+
function formatMentionsMessage(mentions) {
|
|
200
|
+
if (!mentions || mentions.length === 0) return null;
|
|
201
|
+
|
|
202
|
+
const lines = mentions.map(m => {
|
|
203
|
+
const items = m.count === 1 ? '1 item' : `${m.count} items`;
|
|
204
|
+
return `<@${m.slackId}> — ${items} directly relevant to you`;
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return `:bell: *Heads up*\n${lines.join('\n')}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
148
210
|
module.exports = {
|
|
149
211
|
scoreItems,
|
|
150
212
|
filterForPersona,
|
|
213
|
+
buildMentions,
|
|
214
|
+
formatMentionsMessage,
|
|
151
215
|
DEFAULT_THRESHOLDS,
|
|
152
216
|
// Exported for testing
|
|
153
217
|
buildScoringPrompt,
|
package/bin/slack.js
CHANGED
|
@@ -203,9 +203,11 @@ function postToWebhook(webhookUrl, payload) {
|
|
|
203
203
|
* @param {string} header - One-liner header (e.g. ":compass: *Wayfind Digest* (Mar 11–18)")
|
|
204
204
|
* @param {string} content - Full digest body as mrkdwn
|
|
205
205
|
* @param {string} personaName - Persona ID
|
|
206
|
+
* @param {Object} [extras] - Optional extras
|
|
207
|
+
* @param {string} [extras.mentionsMessage] - Pre-formatted @mentions message for thread reply
|
|
206
208
|
* @returns {Promise<{ ok: true, persona: string, ts: string, channel: string }>}
|
|
207
209
|
*/
|
|
208
|
-
async function deliverViaBot(botToken, channel, header, content, personaName) {
|
|
210
|
+
async function deliverViaBot(botToken, channel, header, content, personaName, extras) {
|
|
209
211
|
const { WebClient } = require('@slack/web-api');
|
|
210
212
|
const client = new WebClient(botToken);
|
|
211
213
|
|
|
@@ -225,7 +227,22 @@ async function deliverViaBot(botToken, channel, header, content, personaName) {
|
|
|
225
227
|
unfurl_links: false,
|
|
226
228
|
});
|
|
227
229
|
|
|
228
|
-
// Post
|
|
230
|
+
// Post @mentions as thread reply (before feedback prompt)
|
|
231
|
+
const ext = extras || {};
|
|
232
|
+
if (ext.mentionsMessage) {
|
|
233
|
+
try {
|
|
234
|
+
await client.chat.postMessage({
|
|
235
|
+
channel,
|
|
236
|
+
thread_ts: headerResult.ts,
|
|
237
|
+
text: ext.mentionsMessage,
|
|
238
|
+
unfurl_links: false,
|
|
239
|
+
});
|
|
240
|
+
} catch (err) {
|
|
241
|
+
// Non-fatal — digest was delivered, mentions are optional
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Post feedback prompt as thread reply
|
|
229
246
|
try {
|
|
230
247
|
await client.chat.postMessage({
|
|
231
248
|
channel,
|
|
@@ -254,6 +271,7 @@ async function deliverViaBot(botToken, channel, header, content, personaName) {
|
|
|
254
271
|
* @param {Object} [options] - Optional delivery options
|
|
255
272
|
* @param {string} [options.botToken] - Slack bot token for chat.postMessage delivery
|
|
256
273
|
* @param {string} [options.channel] - Slack channel for bot delivery
|
|
274
|
+
* @param {string} [options.mentionsMessage] - Pre-formatted @mentions for thread reply
|
|
257
275
|
* @returns {Promise<{ ok: true, persona: string, ts?: string, channel?: string }>}
|
|
258
276
|
*/
|
|
259
277
|
async function deliver(webhookUrl, digestContent, personaName, dateRange, options) {
|
|
@@ -283,7 +301,9 @@ async function deliver(webhookUrl, digestContent, personaName, dateRange, option
|
|
|
283
301
|
const opts = options || {};
|
|
284
302
|
if (opts.botToken && opts.channel) {
|
|
285
303
|
try {
|
|
286
|
-
return await deliverViaBot(opts.botToken, opts.channel, header, mrkdwn, personaName
|
|
304
|
+
return await deliverViaBot(opts.botToken, opts.channel, header, mrkdwn, personaName, {
|
|
305
|
+
mentionsMessage: opts.mentionsMessage,
|
|
306
|
+
});
|
|
287
307
|
} catch (err) {
|
|
288
308
|
console.error(`Bot delivery failed for ${personaName}, falling back to webhook: ${err.message}`);
|
|
289
309
|
}
|
|
@@ -308,6 +328,7 @@ async function deliver(webhookUrl, digestContent, personaName, dateRange, option
|
|
|
308
328
|
* @param {Object} [options] - Optional delivery options
|
|
309
329
|
* @param {string} [options.botToken] - Slack bot token for chat.postMessage delivery
|
|
310
330
|
* @param {string} [options.channel] - Slack channel for bot delivery
|
|
331
|
+
* @param {Object} [options.mentionsByPersona] - Map of personaId → formatted mentions message
|
|
311
332
|
* @returns {Promise<Array<{ ok: true, persona: string, ts?: string, channel?: string }>>}
|
|
312
333
|
*/
|
|
313
334
|
async function deliverAll(webhookUrl, digestResult, personaIds, options) {
|
|
@@ -336,7 +357,12 @@ async function deliverAll(webhookUrl, digestResult, personaIds, options) {
|
|
|
336
357
|
await new Promise((r) => setTimeout(r, 1000));
|
|
337
358
|
}
|
|
338
359
|
|
|
339
|
-
const
|
|
360
|
+
const deliverOpts = { ...(options || {}) };
|
|
361
|
+
// Attach per-persona mentions if available
|
|
362
|
+
if (deliverOpts.mentionsByPersona && deliverOpts.mentionsByPersona[persona]) {
|
|
363
|
+
deliverOpts.mentionsMessage = deliverOpts.mentionsByPersona[persona];
|
|
364
|
+
}
|
|
365
|
+
const result = await deliver(webhookUrl, content, persona, digestResult.dateRange, deliverOpts);
|
|
340
366
|
results.push(result);
|
|
341
367
|
} catch (err) {
|
|
342
368
|
results.push({ ok: false, persona, error: err.message });
|
package/bin/team-context.js
CHANGED
|
@@ -1018,10 +1018,32 @@ async function runDigest(args) {
|
|
|
1018
1018
|
process.exit(1);
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
|
+
// Build per-persona @mentions from intelligence scores + member profiles
|
|
1022
|
+
const mentionsByPersona = {};
|
|
1023
|
+
if (result.scores) {
|
|
1024
|
+
const intelligence = require('./intelligence');
|
|
1025
|
+
const teamContextPath = getTeamContextPath();
|
|
1026
|
+
const membersDir = teamContextPath ? path.join(teamContextPath, 'members') : null;
|
|
1027
|
+
if (membersDir && fs.existsSync(membersDir)) {
|
|
1028
|
+
const memberFiles = fs.readdirSync(membersDir).filter(f => f.endsWith('.json'));
|
|
1029
|
+
const members = memberFiles.map(f => {
|
|
1030
|
+
try { return JSON.parse(fs.readFileSync(path.join(membersDir, f), 'utf8')); }
|
|
1031
|
+
catch { return null; }
|
|
1032
|
+
}).filter(Boolean);
|
|
1033
|
+
|
|
1034
|
+
for (const pid of personaIds) {
|
|
1035
|
+
const mentions = intelligence.buildMentions(result.scores, members, pid);
|
|
1036
|
+
const msg = intelligence.formatMentionsMessage(mentions);
|
|
1037
|
+
if (msg) mentionsByPersona[pid] = msg;
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1021
1042
|
console.log('Delivering to Slack...');
|
|
1022
1043
|
const deliveryResults = await slack.deliverAll(webhookUrl, result, personaIds, {
|
|
1023
1044
|
botToken: process.env.SLACK_BOT_TOKEN,
|
|
1024
1045
|
channel: process.env.SLACK_DIGEST_CHANNEL,
|
|
1046
|
+
mentionsByPersona,
|
|
1025
1047
|
});
|
|
1026
1048
|
let failures = 0;
|
|
1027
1049
|
const dateStr = result.dateRange.to;
|
|
@@ -4508,11 +4530,11 @@ const COMMANDS = {
|
|
|
4508
4530
|
const skipNpm = args.includes('--skip-npm');
|
|
4509
4531
|
if (!skipNpm) {
|
|
4510
4532
|
console.log('Updating wayfind from npm...');
|
|
4511
|
-
const npmResult = spawnSync('npm', ['
|
|
4533
|
+
const npmResult = spawnSync('npm', ['install', '-g', 'wayfind@latest'], {
|
|
4512
4534
|
stdio: 'inherit',
|
|
4513
4535
|
});
|
|
4514
4536
|
if (npmResult.error || (npmResult.status && npmResult.status !== 0)) {
|
|
4515
|
-
console.error('npm
|
|
4537
|
+
console.error('npm install failed. Try running: npm install -g wayfind@latest');
|
|
4516
4538
|
console.error('Then re-run: wayfind update --skip-npm');
|
|
4517
4539
|
process.exit(1);
|
|
4518
4540
|
}
|
package/package.json
CHANGED