wayfind 2.0.53 → 2.0.55
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/content-store.js +7 -0
- package/bin/distill.js +122 -0
- package/bin/team-context.js +53 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/templates/gha-distill.yml +72 -0
package/bin/content-store.js
CHANGED
|
@@ -979,6 +979,13 @@ function getEntryContent(entryId, options = {}) {
|
|
|
979
979
|
return null;
|
|
980
980
|
}
|
|
981
981
|
|
|
982
|
+
// ── Distilled entries ───────────────────────────────────────────────────
|
|
983
|
+
if (entry.source === 'distilled') {
|
|
984
|
+
try {
|
|
985
|
+
return fs.readFileSync(path.join(storePath, 'distilled', `${entryId}.md`), 'utf8');
|
|
986
|
+
} catch { return null; }
|
|
987
|
+
}
|
|
988
|
+
|
|
982
989
|
// ── Conversation entries ────────────────────────────────────────────────
|
|
983
990
|
if (entry.source === 'conversation') {
|
|
984
991
|
// Try journal path first (for --export'd conversations)
|
package/bin/distill.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
3
5
|
const contentStore = require('./content-store');
|
|
4
6
|
const llm = require('./connectors/llm');
|
|
5
7
|
|
|
@@ -319,6 +321,16 @@ async function distillEntries(options = {}) {
|
|
|
319
321
|
|
|
320
322
|
index.entryCount = Object.keys(index.entries).length;
|
|
321
323
|
backend.saveIndex(index);
|
|
324
|
+
|
|
325
|
+
// Persist merged content to disk so it survives export/import
|
|
326
|
+
try {
|
|
327
|
+
const distilledDir = path.join(storePath, 'distilled');
|
|
328
|
+
if (!fs.existsSync(distilledDir)) fs.mkdirSync(distilledDir, { recursive: true });
|
|
329
|
+
fs.writeFileSync(path.join(distilledDir, `${id}.md`), content, 'utf8');
|
|
330
|
+
} catch (writeErr) {
|
|
331
|
+
console.log(` Warning: could not persist distilled content: ${writeErr.message}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
322
334
|
totalStats.merged += cluster.length;
|
|
323
335
|
} catch (err) {
|
|
324
336
|
console.log(` Merge failed for cluster: ${err.message}`);
|
|
@@ -343,6 +355,114 @@ async function distillEntries(options = {}) {
|
|
|
343
355
|
return totalStats;
|
|
344
356
|
}
|
|
345
357
|
|
|
358
|
+
// ── Export / Import ──────────────────────────────────────────────────────────
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Export all LLM-merged distilled entries (with content) from the content store.
|
|
362
|
+
* Used by the GHA distillation pipeline to commit results back to the team repo.
|
|
363
|
+
* @param {string} [storePath]
|
|
364
|
+
* @returns {Array<Object>}
|
|
365
|
+
*/
|
|
366
|
+
function exportDistilled(storePath) {
|
|
367
|
+
const resolvedPath = storePath || contentStore.resolveStorePath();
|
|
368
|
+
const backend = contentStore.getBackend(resolvedPath);
|
|
369
|
+
const index = backend.loadIndex();
|
|
370
|
+
const distilledDir = path.join(resolvedPath, 'distilled');
|
|
371
|
+
const entries = [];
|
|
372
|
+
|
|
373
|
+
for (const [id, entry] of Object.entries(index.entries || {})) {
|
|
374
|
+
if (!entry.distilledFrom || !entry.distilledFrom.length) continue;
|
|
375
|
+
|
|
376
|
+
let content = null;
|
|
377
|
+
try {
|
|
378
|
+
content = fs.readFileSync(path.join(distilledDir, `${id}.md`), 'utf8');
|
|
379
|
+
} catch { /* content not persisted yet — skip */ }
|
|
380
|
+
if (!content) continue;
|
|
381
|
+
|
|
382
|
+
entries.push({
|
|
383
|
+
id,
|
|
384
|
+
date: entry.date,
|
|
385
|
+
repo: entry.repo,
|
|
386
|
+
title: entry.title,
|
|
387
|
+
content,
|
|
388
|
+
distill_tier: entry.distillTier,
|
|
389
|
+
distilled_from: entry.distilledFrom,
|
|
390
|
+
distilled_at: entry.distilledAt,
|
|
391
|
+
content_hash: entry.contentHash,
|
|
392
|
+
quality_score: entry.qualityScore || 3,
|
|
393
|
+
tags: entry.tags || [],
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return entries;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Idempotently import distilled entries (from a team repo's distilled.json) into
|
|
402
|
+
* the local content store. Entries already present (by content_hash) are skipped.
|
|
403
|
+
* @param {Array<Object>} entries - Array of entries from exportDistilled()
|
|
404
|
+
* @param {string} [storePath]
|
|
405
|
+
* @returns {{ imported: number, skipped: number }}
|
|
406
|
+
*/
|
|
407
|
+
function importDistilled(entries, storePath) {
|
|
408
|
+
const resolvedPath = storePath || contentStore.resolveStorePath();
|
|
409
|
+
const backend = contentStore.getBackend(resolvedPath);
|
|
410
|
+
const index = backend.loadIndex();
|
|
411
|
+
const distilledDir = path.join(resolvedPath, 'distilled');
|
|
412
|
+
|
|
413
|
+
const existingHashes = new Set(
|
|
414
|
+
Object.values(index.entries || {}).map(e => e.contentHash).filter(Boolean)
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
let imported = 0;
|
|
418
|
+
let skipped = 0;
|
|
419
|
+
|
|
420
|
+
for (const entry of entries) {
|
|
421
|
+
if (existingHashes.has(entry.content_hash)) {
|
|
422
|
+
skipped++;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Persist content file
|
|
427
|
+
try {
|
|
428
|
+
if (!fs.existsSync(distilledDir)) fs.mkdirSync(distilledDir, { recursive: true });
|
|
429
|
+
fs.writeFileSync(path.join(distilledDir, `${entry.id}.md`), entry.content || '', 'utf8');
|
|
430
|
+
} catch (writeErr) {
|
|
431
|
+
console.log(` Warning: could not write content for ${entry.id}: ${writeErr.message}`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
index.entries[entry.id] = {
|
|
436
|
+
date: entry.date,
|
|
437
|
+
repo: entry.repo,
|
|
438
|
+
title: entry.title,
|
|
439
|
+
source: 'distilled',
|
|
440
|
+
user: '',
|
|
441
|
+
drifted: false,
|
|
442
|
+
contentHash: entry.content_hash,
|
|
443
|
+
contentLength: (entry.content || '').length,
|
|
444
|
+
tags: entry.tags || [],
|
|
445
|
+
hasEmbedding: false,
|
|
446
|
+
hasReasoning: true,
|
|
447
|
+
hasAlternatives: false,
|
|
448
|
+
qualityScore: entry.quality_score || 3,
|
|
449
|
+
distillTier: entry.distill_tier,
|
|
450
|
+
distilledFrom: entry.distilled_from,
|
|
451
|
+
distilledAt: entry.distilled_at,
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
existingHashes.add(entry.content_hash);
|
|
455
|
+
imported++;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (imported > 0) {
|
|
459
|
+
index.entryCount = Object.keys(index.entries).length;
|
|
460
|
+
backend.saveIndex(index);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return { imported, skipped };
|
|
464
|
+
}
|
|
465
|
+
|
|
346
466
|
// ── Exports ─────────────────────────────────────────────────────────────────
|
|
347
467
|
|
|
348
468
|
module.exports = {
|
|
@@ -351,5 +471,7 @@ module.exports = {
|
|
|
351
471
|
deduplicateGroup,
|
|
352
472
|
mergeEntries,
|
|
353
473
|
titleSimilarity,
|
|
474
|
+
exportDistilled,
|
|
475
|
+
importDistilled,
|
|
354
476
|
TIERS,
|
|
355
477
|
};
|
package/bin/team-context.js
CHANGED
|
@@ -1366,6 +1366,45 @@ async function runReindex(args) {
|
|
|
1366
1366
|
|
|
1367
1367
|
async function runDistill(args) {
|
|
1368
1368
|
const distill = require('./distill');
|
|
1369
|
+
|
|
1370
|
+
// Sub-subcommands: export and import
|
|
1371
|
+
const sub = args[0];
|
|
1372
|
+
|
|
1373
|
+
if (sub === 'export') {
|
|
1374
|
+
const outputIdx = args.indexOf('--output');
|
|
1375
|
+
const outputPath = outputIdx !== -1 ? args[outputIdx + 1] : null;
|
|
1376
|
+
const entries = distill.exportDistilled();
|
|
1377
|
+
const payload = JSON.stringify({ version: '1', generated_at: new Date().toISOString(), entries }, null, 2);
|
|
1378
|
+
if (outputPath) {
|
|
1379
|
+
const outDir = path.dirname(outputPath);
|
|
1380
|
+
if (outDir && outDir !== '.') fs.mkdirSync(outDir, { recursive: true });
|
|
1381
|
+
fs.writeFileSync(outputPath, payload, 'utf8');
|
|
1382
|
+
console.log(`Exported ${entries.length} distilled entries → ${outputPath}`);
|
|
1383
|
+
} else {
|
|
1384
|
+
process.stdout.write(payload + '\n');
|
|
1385
|
+
}
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
if (sub === 'import') {
|
|
1390
|
+
const filePath = args[1];
|
|
1391
|
+
if (!filePath) {
|
|
1392
|
+
console.error('Usage: wayfind distill import <path/to/distilled.json>');
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
}
|
|
1395
|
+
let payload;
|
|
1396
|
+
try {
|
|
1397
|
+
payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
console.error(`Could not read ${filePath}: ${err.message}`);
|
|
1400
|
+
process.exit(1);
|
|
1401
|
+
}
|
|
1402
|
+
const entries = Array.isArray(payload) ? payload : (payload.entries || []);
|
|
1403
|
+
const { imported, skipped } = distill.importDistilled(entries);
|
|
1404
|
+
console.log(`Import complete: ${imported} new entries, ${skipped} already present`);
|
|
1405
|
+
return;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1369
1408
|
const dryRun = args.includes('--dry-run');
|
|
1370
1409
|
const tierIdx = args.indexOf('--tier');
|
|
1371
1410
|
const tier = (tierIdx !== -1 && args[tierIdx + 1]) ? args[tierIdx + 1] : 'daily';
|
|
@@ -3638,6 +3677,19 @@ async function contextPull(args) {
|
|
|
3638
3677
|
}
|
|
3639
3678
|
} catch (_) {}
|
|
3640
3679
|
}
|
|
3680
|
+
// Import distilled entries if the team repo has a distilled.json
|
|
3681
|
+
const distilledJson = path.join(teamPath, '.wayfind', 'distilled.json');
|
|
3682
|
+
if (fs.existsSync(distilledJson)) {
|
|
3683
|
+
try {
|
|
3684
|
+
const { importDistilled } = require('./distill');
|
|
3685
|
+
const payload = JSON.parse(fs.readFileSync(distilledJson, 'utf8'));
|
|
3686
|
+
const entries = Array.isArray(payload) ? payload : (payload.entries || []);
|
|
3687
|
+
const { imported } = importDistilled(entries);
|
|
3688
|
+
if (!quiet && imported > 0) {
|
|
3689
|
+
log(`[wayfind] Imported ${imported} distilled entries from team repo`);
|
|
3690
|
+
}
|
|
3691
|
+
} catch (_) {}
|
|
3692
|
+
}
|
|
3641
3693
|
} else if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
3642
3694
|
log('[wayfind] Team-context pull timed out — using local state');
|
|
3643
3695
|
} else {
|
|
@@ -5762,7 +5814,7 @@ const COMMANDS = {
|
|
|
5762
5814
|
console.log(`\nUpdating ${containerName} (compose: ${composeDir})...`);
|
|
5763
5815
|
const pullResult = spawnSync('docker', ['compose', 'pull'], { cwd: composeDir, stdio: 'inherit' });
|
|
5764
5816
|
if (!pullResult.error && pullResult.status === 0) {
|
|
5765
|
-
spawnSync('docker', ['compose', 'up', '-d'], { cwd: composeDir, stdio: 'inherit' });
|
|
5817
|
+
spawnSync('docker', ['compose', 'up', '-d', '--force-recreate'], { cwd: composeDir, stdio: 'inherit' });
|
|
5766
5818
|
console.log(`${containerName} updated.`);
|
|
5767
5819
|
|
|
5768
5820
|
// Post-deploy smoke check
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Wayfind distillation pipeline
|
|
2
|
+
#
|
|
3
|
+
# Copy this file to .github/workflows/wayfind-distill.yml in your team-context repo.
|
|
4
|
+
#
|
|
5
|
+
# What it does:
|
|
6
|
+
# - Runs on every push that touches journals/
|
|
7
|
+
# - Indexes all journals into a fresh content store
|
|
8
|
+
# - Runs the distillation pipeline (dedup + LLM merge for daily/weekly/archive tiers)
|
|
9
|
+
# - Exports distilled entries to .wayfind/distilled.json
|
|
10
|
+
# - Commits the result back to the repo
|
|
11
|
+
#
|
|
12
|
+
# Team members get the distilled entries automatically on their next `wayfind context pull`.
|
|
13
|
+
#
|
|
14
|
+
# Required secrets:
|
|
15
|
+
# ANTHROPIC_API_KEY — for LLM merge calls during distillation
|
|
16
|
+
#
|
|
17
|
+
# Optional: set WAYFIND_DISTILL_TIER to 'daily', 'weekly', 'archive', or 'all' (default: all)
|
|
18
|
+
|
|
19
|
+
name: Wayfind Distillation
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
paths:
|
|
24
|
+
- 'journals/**'
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
distill:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
permissions:
|
|
31
|
+
contents: write
|
|
32
|
+
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
with:
|
|
36
|
+
token: ${{ secrets.GITHUB_TOKEN }}
|
|
37
|
+
|
|
38
|
+
- uses: actions/setup-node@v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: '20'
|
|
41
|
+
|
|
42
|
+
- name: Install Wayfind
|
|
43
|
+
run: npm install -g wayfind
|
|
44
|
+
|
|
45
|
+
- name: Index journals
|
|
46
|
+
env:
|
|
47
|
+
TEAM_CONTEXT_STORE_PATH: ${{ github.workspace }}/.wayfind/store
|
|
48
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
49
|
+
run: wayfind index-journals --dir ./journals --no-embeddings
|
|
50
|
+
|
|
51
|
+
- name: Distill
|
|
52
|
+
env:
|
|
53
|
+
TEAM_CONTEXT_STORE_PATH: ${{ github.workspace }}/.wayfind/store
|
|
54
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
55
|
+
run: wayfind distill --tier ${{ vars.WAYFIND_DISTILL_TIER || 'all' }}
|
|
56
|
+
|
|
57
|
+
- name: Export distilled entries
|
|
58
|
+
env:
|
|
59
|
+
TEAM_CONTEXT_STORE_PATH: ${{ github.workspace }}/.wayfind/store
|
|
60
|
+
run: wayfind distill export --output .wayfind/distilled.json
|
|
61
|
+
|
|
62
|
+
- name: Commit distilled.json if changed
|
|
63
|
+
run: |
|
|
64
|
+
git config user.name "github-actions[bot]"
|
|
65
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
66
|
+
git add .wayfind/distilled.json
|
|
67
|
+
if git diff --cached --quiet; then
|
|
68
|
+
echo "No changes to distilled.json"
|
|
69
|
+
else
|
|
70
|
+
git commit -m "chore: update distilled context [skip ci]"
|
|
71
|
+
git push
|
|
72
|
+
fi
|