wayfind 2.0.47 → 2.0.49
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/intelligence.js
CHANGED
|
@@ -59,6 +59,10 @@ function splitAndLabel(content, startId) {
|
|
|
59
59
|
* @param {Object} llmConfig - LLM config for the scoring call
|
|
60
60
|
* @returns {Promise<Array<{id: number, [personaId]: number}>|null>} Scores array or null on failure
|
|
61
61
|
*/
|
|
62
|
+
// Maximum characters to send to the scoring LLM in a single call.
|
|
63
|
+
// Beyond this, scoring is skipped and budget truncation handles content selection.
|
|
64
|
+
const SCORE_MAX_CHARS = 40000;
|
|
65
|
+
|
|
62
66
|
async function scoreItems(signalContent, journalContent, personas, llmConfig) {
|
|
63
67
|
const signalResult = splitAndLabel(signalContent, 0);
|
|
64
68
|
const journalResult = splitAndLabel(journalContent, signalResult.items.length);
|
|
@@ -66,6 +70,11 @@ async function scoreItems(signalContent, journalContent, personas, llmConfig) {
|
|
|
66
70
|
const totalItems = signalResult.items.length + journalResult.items.length;
|
|
67
71
|
if (totalItems === 0) return null;
|
|
68
72
|
|
|
73
|
+
// Skip scoring if content is too large for a reliable single LLM call.
|
|
74
|
+
// The token budget step will handle content selection without scoring.
|
|
75
|
+
const totalChars = (signalResult.labeled || '').length + (journalResult.labeled || '').length;
|
|
76
|
+
if (totalChars > SCORE_MAX_CHARS) return null;
|
|
77
|
+
|
|
69
78
|
const systemPrompt = buildScoringPrompt(personas);
|
|
70
79
|
|
|
71
80
|
const userParts = [];
|
package/bin/team-context.js
CHANGED
|
@@ -327,28 +327,134 @@ async function teamCreate() {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
async function teamJoin(args) {
|
|
330
|
-
const
|
|
330
|
+
const input = args[0];
|
|
331
|
+
if (!input) {
|
|
332
|
+
console.error('Error: repo URL or path is required.');
|
|
333
|
+
console.error('Usage: wayfind team join <repo-url-or-path>');
|
|
334
|
+
console.error('Example: wayfind team join https://github.com/acme/team-context');
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Determine if input is a URL to clone or a local path
|
|
339
|
+
const isUrl = /^https?:\/\/|^git@|^github\.com\//.test(input);
|
|
340
|
+
let repoPath;
|
|
341
|
+
|
|
342
|
+
if (isUrl) {
|
|
343
|
+
// Parse org/repo from URL for clone destination suggestion
|
|
344
|
+
const urlMatch = input.match(/[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/);
|
|
345
|
+
if (!urlMatch) {
|
|
346
|
+
console.error(`Could not parse org/repo from URL: ${input}`);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
const [, org, repo] = urlMatch;
|
|
350
|
+
const orgDir = path.join(HOME, 'repos', org);
|
|
351
|
+
const suggested = fs.existsSync(orgDir)
|
|
352
|
+
? path.join(orgDir, repo)
|
|
353
|
+
: path.join(HOME, '.claude', 'team-context', repo);
|
|
354
|
+
|
|
355
|
+
let dest = suggested;
|
|
356
|
+
if (fs.existsSync(suggested)) {
|
|
357
|
+
console.log(`\nRepo already cloned at: ${suggested}`);
|
|
358
|
+
const useExisting = await ask(`Use existing clone? [Y/n]: `);
|
|
359
|
+
if (useExisting.toLowerCase() === 'n') {
|
|
360
|
+
const custom = await ask(`Clone to [${suggested}]: `);
|
|
361
|
+
dest = custom ? path.resolve(custom.replace(/^~/, HOME)) : suggested;
|
|
362
|
+
}
|
|
363
|
+
} else {
|
|
364
|
+
const confirm = await ask(`\nClone to ${suggested}? [Y/n]: `);
|
|
365
|
+
if (confirm.toLowerCase() === 'n') {
|
|
366
|
+
const custom = await ask(`Clone to: `);
|
|
367
|
+
if (!custom) { console.error('Destination required.'); process.exit(1); }
|
|
368
|
+
dest = path.resolve(custom.replace(/^~/, HOME));
|
|
369
|
+
}
|
|
370
|
+
console.log(`Cloning ${input}...`);
|
|
371
|
+
const cloneUrl = /^https?:\/\//.test(input) ? input : `https://${input}`;
|
|
372
|
+
const result = spawnSync('git', ['clone', cloneUrl, dest], { stdio: 'inherit' });
|
|
373
|
+
if (result.status !== 0) {
|
|
374
|
+
console.error('Clone failed.');
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
repoPath = dest;
|
|
379
|
+
} else {
|
|
380
|
+
repoPath = path.resolve(input.replace(/^~/, HOME));
|
|
381
|
+
if (!fs.existsSync(repoPath)) {
|
|
382
|
+
console.error(`Directory not found: ${repoPath}`);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Read wayfind.json from the repo
|
|
388
|
+
const sharedConfig = readJSONFile(path.join(repoPath, 'wayfind.json')) || {};
|
|
389
|
+
const teamId = sharedConfig.team_id;
|
|
331
390
|
if (!teamId) {
|
|
332
|
-
console.error('
|
|
333
|
-
console.error(
|
|
391
|
+
console.error('');
|
|
392
|
+
console.error(`Error: wayfind.json in that repo has no team_id.`);
|
|
393
|
+
console.error('Ask your team admin to run:');
|
|
394
|
+
console.error(` wayfind context add <team-id> ${repoPath}`);
|
|
334
395
|
process.exit(1);
|
|
335
396
|
}
|
|
397
|
+
const teamName = sharedConfig.team_name || teamId;
|
|
398
|
+
const containerEndpoint = sharedConfig.container_endpoint || null;
|
|
336
399
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
400
|
+
// Register in local context.json
|
|
401
|
+
const config = readContextConfig();
|
|
402
|
+
if (!config.teams) config.teams = {};
|
|
403
|
+
const existing = config.teams[teamId];
|
|
404
|
+
config.teams[teamId] = {
|
|
405
|
+
path: repoPath,
|
|
406
|
+
name: teamName,
|
|
407
|
+
configured_at: new Date().toISOString(),
|
|
408
|
+
...(containerEndpoint ? { container_endpoint: containerEndpoint } : {}),
|
|
409
|
+
...(existing && existing.bound_repos ? { bound_repos: existing.bound_repos } : {}),
|
|
340
410
|
};
|
|
411
|
+
if (!config.default) config.default = teamId;
|
|
412
|
+
writeContextConfig(config);
|
|
341
413
|
|
|
342
|
-
|
|
414
|
+
// Check API key status
|
|
415
|
+
const keyFile = path.join(repoPath, '.wayfind-api-key');
|
|
416
|
+
const keyReady = fs.existsSync(keyFile) && (() => {
|
|
417
|
+
try { return fs.readFileSync(keyFile, 'utf8').trim().length >= 32; } catch { return false; }
|
|
418
|
+
})();
|
|
419
|
+
|
|
420
|
+
// Print confirmation
|
|
343
421
|
console.log('');
|
|
344
|
-
console.log(`Joined team ${teamId}
|
|
422
|
+
console.log(`Joined team '${teamName}' (${teamId})`);
|
|
423
|
+
console.log(` Repo: ${repoPath}`);
|
|
424
|
+
if (containerEndpoint) {
|
|
425
|
+
console.log(` Semantic search: available | ${containerEndpoint}`);
|
|
426
|
+
} else {
|
|
427
|
+
console.log(` Semantic search: not configured`);
|
|
428
|
+
console.log(` Ask your team admin: wayfind deploy set-endpoint <url> --team ${teamId}`);
|
|
429
|
+
}
|
|
430
|
+
if (keyReady) {
|
|
431
|
+
console.log(` Search API key: ready — rotates daily, committed to team repo`);
|
|
432
|
+
} else {
|
|
433
|
+
console.log(` Search API key: pending — will appear after the container's first key rotation`);
|
|
434
|
+
console.log(` Run \`git pull\` in ${repoPath} after the container starts`);
|
|
435
|
+
}
|
|
436
|
+
console.log('');
|
|
437
|
+
console.log('How the search key works:');
|
|
438
|
+
console.log(' The container rotates this key every 24 hours and commits it to the team repo.');
|
|
439
|
+
console.log(' Your Claude Code sessions read the latest key automatically from the cloned repo.');
|
|
440
|
+
console.log(' You never need to manage it — just keep the repo up to date (git pull).');
|
|
441
|
+
console.log('');
|
|
442
|
+
console.log('Next: bind your repos to this team with:');
|
|
443
|
+
console.log(` wayfind context bind ${teamId} (run from each repo you work in)`);
|
|
444
|
+
|
|
445
|
+
if (existing) {
|
|
446
|
+
console.log('');
|
|
447
|
+
console.log(` (Updated existing registration for team ${teamId})`);
|
|
448
|
+
}
|
|
345
449
|
|
|
450
|
+
// Register profile in team directory
|
|
346
451
|
const profile = readJSONFile(PROFILE_FILE);
|
|
347
452
|
if (profile) {
|
|
348
453
|
syncMemberToRegistry(profile, teamId);
|
|
349
454
|
await announceToSlack(profile, teamId);
|
|
350
455
|
} else {
|
|
351
|
-
console.log(
|
|
456
|
+
console.log('');
|
|
457
|
+
console.log("Run 'wayfind whoami --setup' to register your profile in the team directory.");
|
|
352
458
|
}
|
|
353
459
|
console.log('');
|
|
354
460
|
}
|
|
@@ -947,12 +1053,26 @@ async function runDigest(args) {
|
|
|
947
1053
|
process.exit(1);
|
|
948
1054
|
}
|
|
949
1055
|
|
|
1056
|
+
// Sanitize configured paths — connectors.json may have been written from inside a container
|
|
1057
|
+
// with paths like /home/node/... or /data/... that don't exist on the host. Fall back to
|
|
1058
|
+
// local defaults for any path that doesn't resolve on this machine.
|
|
1059
|
+
const digestConfig = { ...config.digest };
|
|
1060
|
+
if (digestConfig.store_path && !fs.existsSync(digestConfig.store_path)) {
|
|
1061
|
+
digestConfig.store_path = contentStore.resolveStorePath();
|
|
1062
|
+
}
|
|
1063
|
+
if (digestConfig.journal_dir && !fs.existsSync(digestConfig.journal_dir)) {
|
|
1064
|
+
digestConfig.journal_dir = contentStore.DEFAULT_JOURNAL_DIR;
|
|
1065
|
+
}
|
|
1066
|
+
if (digestConfig.signals_dir && !fs.existsSync(digestConfig.signals_dir)) {
|
|
1067
|
+
digestConfig.signals_dir = contentStore.resolveSignalsDir();
|
|
1068
|
+
}
|
|
1069
|
+
|
|
950
1070
|
// Generate digests
|
|
951
1071
|
console.log(`Generating digests for: ${personaIds.join(', ')}`);
|
|
952
1072
|
console.log(`Period: ${sinceDate} to today`);
|
|
953
1073
|
console.log('');
|
|
954
1074
|
|
|
955
|
-
const result = await digest.generateDigest(
|
|
1075
|
+
const result = await digest.generateDigest(digestConfig, personaIds, sinceDate, (progress) => {
|
|
956
1076
|
if (progress.phase === 'start') {
|
|
957
1077
|
process.stdout.write(` ${progress.personaId} (${progress.index + 1}/${progress.total})... `);
|
|
958
1078
|
} else if (progress.phase === 'done') {
|
|
@@ -2033,6 +2153,15 @@ function journalSync(args) {
|
|
|
2033
2153
|
teamFiles[teamId].push({ file, srcPath: path.join(journalDir, file) });
|
|
2034
2154
|
}
|
|
2035
2155
|
|
|
2156
|
+
// Always update member version stamp for all registered teams, even if no files to sync.
|
|
2157
|
+
// This ensures the stamp stays current regardless of whether journals are flowing.
|
|
2158
|
+
if (config.teams) {
|
|
2159
|
+
for (const teamId of Object.keys(config.teams)) {
|
|
2160
|
+
const teamPath = getTeamContextPath(teamId);
|
|
2161
|
+
if (teamPath) stampMemberVersion(teamPath);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2036
2165
|
if (Object.keys(teamFiles).length === 0) {
|
|
2037
2166
|
console.log('No journal files to sync.');
|
|
2038
2167
|
return;
|
|
@@ -3549,9 +3678,20 @@ function contextAdd(args) {
|
|
|
3549
3678
|
const config = readContextConfig();
|
|
3550
3679
|
if (!config.teams) config.teams = {};
|
|
3551
3680
|
|
|
3552
|
-
//
|
|
3553
|
-
|
|
3681
|
+
// Read and update wayfind.json in the repo — write team_id/team_name so joiners
|
|
3682
|
+
// can read them without needing to know the ID out of band
|
|
3683
|
+
const sharedConfigPath = path.join(resolved, 'wayfind.json');
|
|
3684
|
+
const sharedConfig = readJSONFile(sharedConfigPath) || {};
|
|
3554
3685
|
const teamName = sharedConfig.team_name || teamId;
|
|
3686
|
+
if (!sharedConfig.team_id || !sharedConfig.team_name) {
|
|
3687
|
+
sharedConfig.team_id = teamId;
|
|
3688
|
+
sharedConfig.team_name = teamName;
|
|
3689
|
+
try {
|
|
3690
|
+
fs.writeFileSync(sharedConfigPath, JSON.stringify(sharedConfig, null, 2) + '\n');
|
|
3691
|
+
} catch (err) {
|
|
3692
|
+
console.error(`Warning: could not write wayfind.json: ${err.message}`);
|
|
3693
|
+
}
|
|
3694
|
+
}
|
|
3555
3695
|
|
|
3556
3696
|
config.teams[teamId] = {
|
|
3557
3697
|
path: resolved,
|
|
@@ -3566,6 +3706,23 @@ function contextAdd(args) {
|
|
|
3566
3706
|
if (Object.keys(config.teams).length === 1) {
|
|
3567
3707
|
console.log(' Set as default (only team).');
|
|
3568
3708
|
}
|
|
3709
|
+
|
|
3710
|
+
// Generate first search API key if one doesn't exist yet
|
|
3711
|
+
const keyFile = path.join(resolved, '.wayfind-api-key');
|
|
3712
|
+
if (!fs.existsSync(keyFile)) {
|
|
3713
|
+
const key = crypto.randomBytes(32).toString('hex');
|
|
3714
|
+
try {
|
|
3715
|
+
fs.writeFileSync(keyFile, key + '\n', 'utf8');
|
|
3716
|
+
// Resolve token for git push (CLI context — use gh CLI)
|
|
3717
|
+
const token = detectGitHubToken();
|
|
3718
|
+
if (token && !process.env.GITHUB_TOKEN) process.env.GITHUB_TOKEN = token;
|
|
3719
|
+
pushApiKey(resolved);
|
|
3720
|
+
console.log(' Generated initial search API key → committed to team repo.');
|
|
3721
|
+
console.log(' Teammates who join will read it automatically. It rotates daily.');
|
|
3722
|
+
} catch (err) {
|
|
3723
|
+
console.error(` Warning: could not generate API key: ${err.message}`);
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3569
3726
|
}
|
|
3570
3727
|
|
|
3571
3728
|
function contextBind(args) {
|
|
@@ -3899,7 +4056,8 @@ function deployTeamInit(teamId, { port } = {}) {
|
|
|
3899
4056
|
let composeContent = fs.readFileSync(templatePath, 'utf8');
|
|
3900
4057
|
composeContent = composeContent
|
|
3901
4058
|
.replace(/container_name: wayfind/, `container_name: ${containerName}`)
|
|
3902
|
-
.replace(/- "3141:3141"/, `- "${assignedPort}:3141"`)
|
|
4059
|
+
.replace(/- "3141:3141"/, `- "${assignedPort}:3141"`)
|
|
4060
|
+
.replace(/^(services:)/m, `name: ${containerName}\n\n$1`);
|
|
3903
4061
|
|
|
3904
4062
|
// Inject Docker label for discovery
|
|
3905
4063
|
composeContent = composeContent.replace(
|
|
@@ -4690,7 +4848,7 @@ async function indexJournalsIfAvailable() {
|
|
|
4690
4848
|
console.log('No journal files found — skipping index.');
|
|
4691
4849
|
return;
|
|
4692
4850
|
}
|
|
4693
|
-
const hasEmbeddingKey = !!process.env.OPENAI_API_KEY;
|
|
4851
|
+
const hasEmbeddingKey = !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT);
|
|
4694
4852
|
console.log(`Indexing ${entries.length} journal files from ${journalDir}${hasEmbeddingKey ? ' (with embeddings)' : ''}...`);
|
|
4695
4853
|
try {
|
|
4696
4854
|
const stats = await contentStore.indexJournals({
|
|
@@ -5767,6 +5925,21 @@ const COMMANDS = {
|
|
|
5767
5925
|
// Also sync public-staging docs if they exist
|
|
5768
5926
|
const publicDocsDir = path.join(sourceRoot, 'public-staging', 'docs');
|
|
5769
5927
|
|
|
5928
|
+
// Keep plugin.json version in sync with package.json before syncing
|
|
5929
|
+
const pluginJsonPath = path.join(sourceRoot, 'plugin', '.claude-plugin', 'plugin.json');
|
|
5930
|
+
const pkgJsonPath = path.join(sourceRoot, 'package.json');
|
|
5931
|
+
if (fs.existsSync(pluginJsonPath) && fs.existsSync(pkgJsonPath)) {
|
|
5932
|
+
try {
|
|
5933
|
+
const pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
|
|
5934
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
|
|
5935
|
+
if (pluginJson.version !== pkgJson.version) {
|
|
5936
|
+
pluginJson.version = pkgJson.version;
|
|
5937
|
+
fs.writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + '\n');
|
|
5938
|
+
console.log(`Updated plugin.json version to ${pkgJson.version}`);
|
|
5939
|
+
}
|
|
5940
|
+
} catch {}
|
|
5941
|
+
}
|
|
5942
|
+
|
|
5770
5943
|
console.log('Syncing files...');
|
|
5771
5944
|
for (const item of syncItems) {
|
|
5772
5945
|
const isDir = item.endsWith('/');
|
|
@@ -5932,8 +6105,8 @@ function showHelp() {
|
|
|
5932
6105
|
console.log(' /doctor Check installation health');
|
|
5933
6106
|
console.log('');
|
|
5934
6107
|
console.log('Team setup:');
|
|
5935
|
-
console.log(' wayfind team create
|
|
5936
|
-
console.log(' wayfind team join <
|
|
6108
|
+
console.log(' wayfind team create Create a new team');
|
|
6109
|
+
console.log(' wayfind team join <repo-url-or-path> Join an existing team');
|
|
5937
6110
|
console.log(' wayfind team status Show current team info');
|
|
5938
6111
|
console.log(' wayfind whoami Show your profile');
|
|
5939
6112
|
console.log(' wayfind whoami --setup Set up your profile and personas');
|
package/package.json
CHANGED
|
@@ -37,7 +37,8 @@ LAST_RUN_FILE="$HOME/.claude/team-context/.last-reindex"
|
|
|
37
37
|
if [ -f "$LAST_RUN_FILE" ]; then
|
|
38
38
|
CHANGED=$(find "$HOME/.claude/projects" -name "*.jsonl" -newer "$LAST_RUN_FILE" -print -quit 2>/dev/null)
|
|
39
39
|
if [ -z "$CHANGED" ]; then
|
|
40
|
-
# No conversation files changed — skip expensive reindex, just sync journals
|
|
40
|
+
# No conversation files changed — skip expensive reindex, just split and sync journals
|
|
41
|
+
$WAYFIND journal split 2>/dev/null
|
|
41
42
|
$WAYFIND journal sync 2>/dev/null &
|
|
42
43
|
exit 0
|
|
43
44
|
fi
|
|
@@ -50,5 +51,6 @@ $WAYFIND reindex --conversations-only --export --detect-shifts --write-stats 2>/
|
|
|
50
51
|
mkdir -p "$HOME/.claude/team-context"
|
|
51
52
|
touch "$LAST_RUN_FILE"
|
|
52
53
|
|
|
53
|
-
#
|
|
54
|
+
# Split any unsuffixed journals, then sync to team-context repo (sync backgrounded)
|
|
55
|
+
$WAYFIND journal split 2>/dev/null
|
|
54
56
|
$WAYFIND journal sync 2>/dev/null &
|