wayfind 2.0.49 → 2.0.50
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/connectors/llm.js
CHANGED
|
@@ -356,6 +356,59 @@ async function detect() {
|
|
|
356
356
|
|
|
357
357
|
const OPENAI_EMBEDDINGS_URL = 'https://api.openai.com/v1/embeddings';
|
|
358
358
|
const DEFAULT_EMBEDDING_MODEL = 'text-embedding-3-small';
|
|
359
|
+
const LOCAL_EMBEDDING_MODEL = 'Xenova/all-MiniLM-L6-v2';
|
|
360
|
+
|
|
361
|
+
// Cached pipeline instance — expensive to initialize, reuse across calls.
|
|
362
|
+
let _localPipeline = null;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Try to generate an embedding using the local ONNX model (@xenova/transformers).
|
|
366
|
+
* Returns null if the package is not installed or the model fails to load.
|
|
367
|
+
* Downloads the model (~80MB) on first use into the transformers cache.
|
|
368
|
+
* @param {string} text
|
|
369
|
+
* @returns {Promise<number[]|null>}
|
|
370
|
+
*/
|
|
371
|
+
async function generateEmbeddingLocal(text) {
|
|
372
|
+
try {
|
|
373
|
+
if (!_localPipeline) {
|
|
374
|
+
// Dynamic require — optional dep, may not be installed
|
|
375
|
+
const { pipeline, env } = require('@xenova/transformers');
|
|
376
|
+
// Suppress progress output in non-interactive contexts
|
|
377
|
+
if (!process.stdout.isTTY) {
|
|
378
|
+
env.allowLocalModels = false;
|
|
379
|
+
}
|
|
380
|
+
process.stderr.write('[wayfind] Loading local embedding model (first use — may take a moment)...\n');
|
|
381
|
+
_localPipeline = await pipeline('feature-extraction', LOCAL_EMBEDDING_MODEL);
|
|
382
|
+
}
|
|
383
|
+
const output = await _localPipeline(text, { pooling: 'mean', normalize: true });
|
|
384
|
+
return Array.from(output.data);
|
|
385
|
+
} catch (_) {
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Detect which embedding provider is active based on env vars and installed packages.
|
|
392
|
+
* Returns an object describing the provider so callers can surface this to users.
|
|
393
|
+
* @returns {{ provider: string, model: string, requiresKey: boolean, available: boolean }}
|
|
394
|
+
*/
|
|
395
|
+
function getEmbeddingProviderInfo() {
|
|
396
|
+
if (isSimulation()) {
|
|
397
|
+
return { provider: 'simulation', model: 'fake-1536d', requiresKey: false, available: true };
|
|
398
|
+
}
|
|
399
|
+
if (process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT) {
|
|
400
|
+
const hasKey = !!process.env.AZURE_OPENAI_EMBEDDING_KEY;
|
|
401
|
+
return { provider: 'azure', model: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT || 'text-embedding-3-small', requiresKey: true, available: hasKey };
|
|
402
|
+
}
|
|
403
|
+
if (process.env.OPENAI_API_KEY) {
|
|
404
|
+
return { provider: 'openai', model: DEFAULT_EMBEDDING_MODEL, requiresKey: true, available: true };
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
require.resolve('@xenova/transformers');
|
|
408
|
+
return { provider: 'local', model: LOCAL_EMBEDDING_MODEL, requiresKey: false, available: true };
|
|
409
|
+
} catch (_) {}
|
|
410
|
+
return { provider: 'none', model: null, requiresKey: false, available: false };
|
|
411
|
+
}
|
|
359
412
|
|
|
360
413
|
/**
|
|
361
414
|
* Generate an embedding vector for the given text.
|
|
@@ -392,7 +445,14 @@ async function generateEmbedding(text, options = {}) {
|
|
|
392
445
|
const apiKeyEnv = options.apiKeyEnv || 'OPENAI_API_KEY';
|
|
393
446
|
const apiKey = process.env[apiKeyEnv];
|
|
394
447
|
if (!apiKey) {
|
|
395
|
-
|
|
448
|
+
// No cloud key — try local model before failing
|
|
449
|
+
const localVec = await generateEmbeddingLocal(text);
|
|
450
|
+
if (localVec !== null) return localVec;
|
|
451
|
+
throw new Error(
|
|
452
|
+
'Embeddings: No provider configured.\n' +
|
|
453
|
+
' Option 1 (cloud): set OPENAI_API_KEY or AZURE_OPENAI_EMBEDDING_ENDPOINT\n' +
|
|
454
|
+
' Option 2 (local, no key): npm install -g @xenova/transformers'
|
|
455
|
+
);
|
|
396
456
|
}
|
|
397
457
|
|
|
398
458
|
const baseUrl = options.baseUrl || OPENAI_EMBEDDINGS_URL.replace('/embeddings', '');
|
|
@@ -477,5 +537,6 @@ module.exports = {
|
|
|
477
537
|
call,
|
|
478
538
|
detect,
|
|
479
539
|
generateEmbedding,
|
|
540
|
+
getEmbeddingProviderInfo,
|
|
480
541
|
isSimulation,
|
|
481
542
|
};
|
package/bin/content-store.js
CHANGED
|
@@ -426,7 +426,7 @@ async function indexJournals(options = {}) {
|
|
|
426
426
|
const storePath = options.storePath || resolveStorePath();
|
|
427
427
|
const doEmbeddings = options.embeddings !== undefined
|
|
428
428
|
? options.embeddings
|
|
429
|
-
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation());
|
|
429
|
+
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation() || llm.getEmbeddingProviderInfo().available);
|
|
430
430
|
|
|
431
431
|
if (!journalDir || !storePath) {
|
|
432
432
|
throw new Error('Journal directory and store path are required.');
|
|
@@ -546,11 +546,13 @@ async function indexJournals(options = {}) {
|
|
|
546
546
|
stats.entryCount = Object.keys(finalEntries).length;
|
|
547
547
|
|
|
548
548
|
// Save
|
|
549
|
+
const embeddingModel = doEmbeddings ? llm.getEmbeddingProviderInfo().model : null;
|
|
549
550
|
const index = {
|
|
550
551
|
version: INDEX_VERSION,
|
|
551
552
|
lastUpdated: Date.now(),
|
|
552
553
|
entryCount: stats.entryCount,
|
|
553
554
|
entries: finalEntries,
|
|
555
|
+
...(embeddingModel ? { embedding_model: embeddingModel } : {}),
|
|
554
556
|
};
|
|
555
557
|
|
|
556
558
|
backend.saveIndex(index);
|
|
@@ -563,6 +565,7 @@ async function indexJournals(options = {}) {
|
|
|
563
565
|
entry_count: stats.entryCount,
|
|
564
566
|
new_entries: stats.newEntries,
|
|
565
567
|
has_embeddings: doEmbeddings,
|
|
568
|
+
embedding_model: embeddingModel,
|
|
566
569
|
});
|
|
567
570
|
|
|
568
571
|
return stats;
|
|
@@ -1375,7 +1378,7 @@ async function indexConversations(options = {}) {
|
|
|
1375
1378
|
const storePath = options.storePath || resolveStorePath();
|
|
1376
1379
|
const doEmbeddings = options.embeddings !== undefined
|
|
1377
1380
|
? options.embeddings
|
|
1378
|
-
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation());
|
|
1381
|
+
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation() || llm.getEmbeddingProviderInfo().available);
|
|
1379
1382
|
|
|
1380
1383
|
if (!projectsDir || !storePath) {
|
|
1381
1384
|
throw new Error('Projects directory and store path are required.');
|
|
@@ -1566,6 +1569,10 @@ async function indexConversations(options = {}) {
|
|
|
1566
1569
|
|
|
1567
1570
|
// Save everything
|
|
1568
1571
|
existingIndex.entryCount = Object.keys(existingIndex.entries).length;
|
|
1572
|
+
if (doEmbeddings) {
|
|
1573
|
+
const model = llm.getEmbeddingProviderInfo().model;
|
|
1574
|
+
if (model) existingIndex.embedding_model = model;
|
|
1575
|
+
}
|
|
1569
1576
|
backend.saveIndex(existingIndex);
|
|
1570
1577
|
if (doEmbeddings) {
|
|
1571
1578
|
backend.saveEmbeddings(existingEmbeddings);
|
|
@@ -1801,7 +1808,7 @@ async function indexSignals(options = {}) {
|
|
|
1801
1808
|
const storePath = options.storePath || resolveStorePath();
|
|
1802
1809
|
const doEmbeddings = options.embeddings !== undefined
|
|
1803
1810
|
? options.embeddings
|
|
1804
|
-
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation());
|
|
1811
|
+
: !!(process.env.OPENAI_API_KEY || process.env.AZURE_OPENAI_EMBEDDING_ENDPOINT || llm.isSimulation() || llm.getEmbeddingProviderInfo().available);
|
|
1805
1812
|
|
|
1806
1813
|
if (!signalsDir || !storePath) {
|
|
1807
1814
|
throw new Error('Signals directory and store path are required.');
|
|
@@ -2076,6 +2083,10 @@ async function indexSignals(options = {}) {
|
|
|
2076
2083
|
|
|
2077
2084
|
// Save
|
|
2078
2085
|
existingIndex.entryCount = Object.keys(existingIndex.entries).length;
|
|
2086
|
+
if (doEmbeddings) {
|
|
2087
|
+
const model = llm.getEmbeddingProviderInfo().model;
|
|
2088
|
+
if (model) existingIndex.embedding_model = model;
|
|
2089
|
+
}
|
|
2079
2090
|
backend.saveIndex(existingIndex);
|
|
2080
2091
|
if (doEmbeddings) {
|
|
2081
2092
|
backend.saveEmbeddings(existingEmbeddings);
|
|
@@ -2376,6 +2387,12 @@ module.exports = {
|
|
|
2376
2387
|
saveIndex: (storePath, index) => getBackend(storePath || resolveStorePath()).saveIndex(index),
|
|
2377
2388
|
loadEmbeddings: (storePath) => getBackend(storePath || resolveStorePath()).loadEmbeddings(),
|
|
2378
2389
|
saveEmbeddings: (storePath, embeddings) => getBackend(storePath || resolveStorePath()).saveEmbeddings(embeddings),
|
|
2390
|
+
getStoredEmbeddingModel: (storePath) => {
|
|
2391
|
+
try {
|
|
2392
|
+
const idx = getBackend(storePath || resolveStorePath()).loadIndex();
|
|
2393
|
+
return idx ? (idx.embedding_model || null) : null;
|
|
2394
|
+
} catch { return null; }
|
|
2395
|
+
},
|
|
2379
2396
|
loadConversationIndex: (storePath) => getBackend(storePath || resolveStorePath()).loadConversationIndex(),
|
|
2380
2397
|
saveConversationIndex: (storePath, convIndex) => getBackend(storePath || resolveStorePath()).saveConversationIndex(convIndex),
|
|
2381
2398
|
|
package/bin/team-context.js
CHANGED
|
@@ -38,6 +38,7 @@ const digest = require('./digest');
|
|
|
38
38
|
const slack = require('./slack');
|
|
39
39
|
const slackBot = require('./slack-bot');
|
|
40
40
|
const contentStore = require('./content-store');
|
|
41
|
+
const llm = require('./connectors/llm');
|
|
41
42
|
const rebuildStatus = require('./rebuild-status');
|
|
42
43
|
const telemetry = require('./telemetry');
|
|
43
44
|
|
|
@@ -1310,7 +1311,20 @@ async function runReindex(args) {
|
|
|
1310
1311
|
const force = args.includes('--force');
|
|
1311
1312
|
|
|
1312
1313
|
if (force) {
|
|
1313
|
-
|
|
1314
|
+
// Warn if stored embeddings used a different model than the current provider
|
|
1315
|
+
const storedModel = contentStore.getStoredEmbeddingModel();
|
|
1316
|
+
const currentProvider = llm.getEmbeddingProviderInfo();
|
|
1317
|
+
if (storedModel && currentProvider.model && storedModel !== currentProvider.model) {
|
|
1318
|
+
console.log(`⚠️ Embedding model mismatch:`);
|
|
1319
|
+
console.log(` Stored embeddings: ${storedModel}`);
|
|
1320
|
+
console.log(` Current provider: ${currentProvider.model} (${currentProvider.provider})`);
|
|
1321
|
+
console.log(` All embeddings will be cleared and regenerated with the current provider.`);
|
|
1322
|
+
console.log(` Entries without embeddings will fall back to full-text search until reindexed.`);
|
|
1323
|
+
} else if (storedModel) {
|
|
1324
|
+
console.log(`Force mode: clearing content store — will regenerate ${storedModel} embeddings...`);
|
|
1325
|
+
} else {
|
|
1326
|
+
console.log('Force mode: clearing content store for full reindex...');
|
|
1327
|
+
}
|
|
1314
1328
|
try {
|
|
1315
1329
|
const backend = contentStore.getBackend();
|
|
1316
1330
|
const emptyIndex = { version: contentStore.INDEX_VERSION, entries: {}, lastUpdated: Date.now(), entryCount: 0 };
|
|
@@ -3581,7 +3595,7 @@ function contextSync() {
|
|
|
3581
3595
|
*
|
|
3582
3596
|
* @param {string[]} args - CLI arguments (--quiet suppresses output)
|
|
3583
3597
|
*/
|
|
3584
|
-
function contextPull(args) {
|
|
3598
|
+
async function contextPull(args) {
|
|
3585
3599
|
const quiet = args.includes('--quiet');
|
|
3586
3600
|
const background = args.includes('--background');
|
|
3587
3601
|
const log = quiet ? () => {} : console.log;
|
|
@@ -3614,6 +3628,16 @@ function contextPull(args) {
|
|
|
3614
3628
|
log('[wayfind] Pulled latest team-context');
|
|
3615
3629
|
// Mark success — doctor checks this to warn on prolonged failures
|
|
3616
3630
|
try { fs.writeFileSync(markerFile, new Date().toISOString()); } catch {}
|
|
3631
|
+
// Index any new team journals into the local content store
|
|
3632
|
+
const journalsDir = path.join(teamPath, 'journals');
|
|
3633
|
+
if (fs.existsSync(journalsDir)) {
|
|
3634
|
+
try {
|
|
3635
|
+
const stats = await contentStore.indexJournals({ journalDir: journalsDir });
|
|
3636
|
+
if (!quiet && stats.newEntries > 0) {
|
|
3637
|
+
log(`[wayfind] Indexed ${stats.newEntries} new team journal entries`);
|
|
3638
|
+
}
|
|
3639
|
+
} catch (_) {}
|
|
3640
|
+
}
|
|
3617
3641
|
} else if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
3618
3642
|
log('[wayfind] Team-context pull timed out — using local state');
|
|
3619
3643
|
} else {
|
|
@@ -5404,12 +5428,48 @@ async function runContainerDoctor() {
|
|
|
5404
5428
|
console.log(' Run: wayfind reindex');
|
|
5405
5429
|
issues++;
|
|
5406
5430
|
}
|
|
5431
|
+
|
|
5432
|
+
// Embedding provider + model mismatch check
|
|
5433
|
+
try {
|
|
5434
|
+
const providerInfo = llm.getEmbeddingProviderInfo();
|
|
5435
|
+
const storedModel = contentStore.getStoredEmbeddingModel(storePath);
|
|
5436
|
+
if (!providerInfo.available) {
|
|
5437
|
+
warn('Embedding provider: none configured — semantic search unavailable (full-text only)');
|
|
5438
|
+
console.log(' Option 1 (cloud): set OPENAI_API_KEY');
|
|
5439
|
+
console.log(' Option 2 (local, no key): npm install -g @xenova/transformers');
|
|
5440
|
+
issues++;
|
|
5441
|
+
} else {
|
|
5442
|
+
const label = providerInfo.provider === 'local'
|
|
5443
|
+
? `local (${providerInfo.model}) — no API key required`
|
|
5444
|
+
: `${providerInfo.provider} (${providerInfo.model})`;
|
|
5445
|
+
pass(`Embedding provider: ${label}`);
|
|
5446
|
+
if (storedModel && providerInfo.model && storedModel !== providerInfo.model) {
|
|
5447
|
+
warn(`Embedding model mismatch: stored=${storedModel}, current=${providerInfo.model}`);
|
|
5448
|
+
console.log(' Semantic search results may be degraded — stored embeddings are from a different model.');
|
|
5449
|
+
console.log(' Fix: wayfind reindex --force (clears and regenerates all embeddings)');
|
|
5450
|
+
issues++;
|
|
5451
|
+
}
|
|
5452
|
+
}
|
|
5453
|
+
} catch (_) {}
|
|
5407
5454
|
} catch (e) {
|
|
5408
5455
|
warn(`Embedding coverage: error — ${e.message}`);
|
|
5409
5456
|
issues++;
|
|
5410
5457
|
}
|
|
5411
5458
|
}
|
|
5412
5459
|
|
|
5460
|
+
// ── Embedding provider (standalone, when no entries yet) ───────────────────
|
|
5461
|
+
if (entryCount === 0) {
|
|
5462
|
+
try {
|
|
5463
|
+
const providerInfo = llm.getEmbeddingProviderInfo();
|
|
5464
|
+
if (!providerInfo.available) {
|
|
5465
|
+
warn('Embedding provider: none — semantic search will not be available');
|
|
5466
|
+
console.log(' Option 1 (cloud): set OPENAI_API_KEY');
|
|
5467
|
+
console.log(' Option 2 (local, no key): npm install -g @xenova/transformers');
|
|
5468
|
+
issues++;
|
|
5469
|
+
}
|
|
5470
|
+
} catch (_) {}
|
|
5471
|
+
}
|
|
5472
|
+
|
|
5413
5473
|
// 4. Signal freshness — are there signal files from today?
|
|
5414
5474
|
const signalsDir = path.join(EFFECTIVE_DIR, 'signals');
|
|
5415
5475
|
const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wayfind",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.50",
|
|
4
4
|
"description": "Team decision trail for AI-assisted development. The connective tissue between product, engineering, and strategy.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"wayfind": "./bin/team-context.js",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"posthog-node": "^5.28.0"
|
|
55
55
|
},
|
|
56
56
|
"optionalDependencies": {
|
|
57
|
-
"better-sqlite3": "^11.0.0"
|
|
57
|
+
"better-sqlite3": "^11.0.0",
|
|
58
|
+
"@xenova/transformers": "^2.17.2"
|
|
58
59
|
}
|
|
59
60
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wayfind",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.49",
|
|
4
4
|
"description": "Team decision trail for AI-assisted development. Session memory, decision journals, and team digests.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Wayfind",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
"homepage": "https://github.com/usewayfind/wayfind",
|
|
10
10
|
"repository": "https://github.com/usewayfind/wayfind",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"team",
|
|
14
|
+
"context",
|
|
15
|
+
"memory",
|
|
16
|
+
"decisions",
|
|
17
|
+
"digest",
|
|
18
|
+
"journal",
|
|
19
|
+
"session"
|
|
20
|
+
],
|
|
13
21
|
"skills": "./skills/"
|
|
14
22
|
}
|
|
@@ -45,6 +45,53 @@ Check if `.claude/wayfind.json` already exists in the repo.
|
|
|
45
45
|
|
|
46
46
|
**Verify `.gitignore` coverage:** `.claude/wayfind.json` must be gitignored. Step 3 already includes it in the required entries — confirm this is still the case. If someone removed it, Step 3 will restore it.
|
|
47
47
|
|
|
48
|
+
## Step 1.7: Embedding provider (first-time only)
|
|
49
|
+
|
|
50
|
+
Read `~/.claude/team-context/context.json`. Check for an `embedding_provider` field.
|
|
51
|
+
|
|
52
|
+
**If `embedding_provider` is already set:** Skip this step silently.
|
|
53
|
+
|
|
54
|
+
**If not set:** Present the following choice to the user:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Wayfind uses embeddings for semantic search (e.g. "find the auth refactor discussion").
|
|
58
|
+
|
|
59
|
+
Choose your embedding provider:
|
|
60
|
+
|
|
61
|
+
1. Local model (recommended for getting started)
|
|
62
|
+
- No API key needed
|
|
63
|
+
- ~80MB download on first use, cached after that
|
|
64
|
+
- Works offline
|
|
65
|
+
- Good quality for most queries
|
|
66
|
+
|
|
67
|
+
2. OpenAI (higher quality)
|
|
68
|
+
- Requires OPENAI_API_KEY
|
|
69
|
+
- ~$0/month at normal usage
|
|
70
|
+
- Best retrieval quality
|
|
71
|
+
|
|
72
|
+
3. Azure OpenAI
|
|
73
|
+
- Requires AZURE_OPENAI_EMBEDDING_ENDPOINT + key
|
|
74
|
+
- For enterprise deployments
|
|
75
|
+
|
|
76
|
+
⚠️ Switching providers later requires reindexing your content store.
|
|
77
|
+
Run: wayfind reindex --force
|
|
78
|
+
Embeddings are model-specific — mixing models breaks semantic search.
|
|
79
|
+
|
|
80
|
+
Which provider? [1/2/3, default: 1]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Wait for their answer (default to 1 if they press enter). Write their choice to `~/.claude/team-context/context.json` as:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{ "embedding_provider": "local" } // for choice 1
|
|
87
|
+
{ "embedding_provider": "openai" } // for choice 2
|
|
88
|
+
{ "embedding_provider": "azure" } // for choice 3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
(Merge into existing context.json — do not overwrite other fields.)
|
|
92
|
+
|
|
93
|
+
Report: "Embedding provider set to: <name>"
|
|
94
|
+
|
|
48
95
|
## Step 2: Create state files (if missing)
|
|
49
96
|
|
|
50
97
|
This repo uses TWO state files with different visibility:
|
|
@@ -45,6 +45,38 @@ Before starting, verify:
|
|
|
45
45
|
|
|
46
46
|
If any prerequisite fails, tell the user what's needed and stop.
|
|
47
47
|
|
|
48
|
+
## Step 0.5: Embedding Provider
|
|
49
|
+
|
|
50
|
+
Read `~/.claude/team-context/context.json`. Check for an `embedding_provider` field.
|
|
51
|
+
|
|
52
|
+
**If already set:** Report the current provider and skip this step.
|
|
53
|
+
|
|
54
|
+
**If not set:** Present this choice:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
Wayfind uses embeddings for semantic search across your team's decision trail.
|
|
58
|
+
|
|
59
|
+
Choose your embedding provider:
|
|
60
|
+
|
|
61
|
+
1. Local model (recommended for getting started)
|
|
62
|
+
- No API key needed, works offline
|
|
63
|
+
- ~80MB download on first use, cached after
|
|
64
|
+
- Good quality for most queries
|
|
65
|
+
|
|
66
|
+
2. OpenAI (higher quality)
|
|
67
|
+
- Requires OPENAI_API_KEY (~$0/month at normal usage)
|
|
68
|
+
|
|
69
|
+
3. Azure OpenAI
|
|
70
|
+
- Requires AZURE_OPENAI_EMBEDDING_ENDPOINT + key
|
|
71
|
+
|
|
72
|
+
⚠️ Switching providers later requires reindexing: wayfind reindex --force
|
|
73
|
+
Embeddings are model-specific — changing models after indexing breaks semantic search.
|
|
74
|
+
|
|
75
|
+
Which provider? [1/2/3, default: 1]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Write choice to `~/.claude/team-context/context.json` as `embedding_provider: "local"|"openai"|"azure"`.
|
|
79
|
+
|
|
48
80
|
## Step 1: Team Context Repo
|
|
49
81
|
|
|
50
82
|
This repo holds shared journals, strategy state, digest archives, and the GitHub
|