wayfind 2.0.31 → 2.0.33
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/storage/index.js +18 -1
- package/bin/team-context.js +78 -6
- package/doctor.sh +152 -4
- package/package.json +1 -1
- package/plugin/scripts/session-start.sh +4 -0
package/bin/storage/index.js
CHANGED
|
@@ -126,7 +126,9 @@ function getBackend(storePath) {
|
|
|
126
126
|
migrateFromJson(backend, storePath);
|
|
127
127
|
cache[storePath] = backend;
|
|
128
128
|
return backend;
|
|
129
|
-
} catch {
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error('Warning: SQLite backend failed to initialize, falling back to JSON.', err.message);
|
|
131
|
+
cache[storePath + ':fallback'] = true;
|
|
130
132
|
const backend = new JsonBackend(storePath);
|
|
131
133
|
backend.open();
|
|
132
134
|
cache[storePath] = backend;
|
|
@@ -164,8 +166,23 @@ function getBackendType(storePath) {
|
|
|
164
166
|
return null;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Returns detailed backend info including whether it was a silent fallback.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} storePath
|
|
173
|
+
* @returns {{ type: 'sqlite'|'json'|'unknown', storePath: string, fallback: boolean }|null}
|
|
174
|
+
*/
|
|
175
|
+
function getBackendInfo(storePath) {
|
|
176
|
+
const backend = cache[storePath];
|
|
177
|
+
if (!backend) return null;
|
|
178
|
+
const type = backend instanceof JsonBackend ? 'json' :
|
|
179
|
+
(backend.constructor && backend.constructor.name === 'SqliteBackend') ? 'sqlite' : 'unknown';
|
|
180
|
+
return { type, storePath, fallback: !!cache[storePath + ':fallback'] };
|
|
181
|
+
}
|
|
182
|
+
|
|
167
183
|
module.exports = {
|
|
168
184
|
getBackend,
|
|
169
185
|
clearCache,
|
|
170
186
|
getBackendType,
|
|
187
|
+
getBackendInfo,
|
|
171
188
|
};
|
package/bin/team-context.js
CHANGED
|
@@ -3283,9 +3283,12 @@ async function runContext(args) {
|
|
|
3283
3283
|
case 'default':
|
|
3284
3284
|
contextSetDefault(subArgs);
|
|
3285
3285
|
break;
|
|
3286
|
+
case 'pull':
|
|
3287
|
+
contextPull(subArgs);
|
|
3288
|
+
break;
|
|
3286
3289
|
default:
|
|
3287
3290
|
console.error(`Unknown context subcommand: ${sub}`);
|
|
3288
|
-
console.error('Available: init, sync, show, add, bind, list, default');
|
|
3291
|
+
console.error('Available: init, sync, show, add, bind, list, default, pull');
|
|
3289
3292
|
process.exit(1);
|
|
3290
3293
|
}
|
|
3291
3294
|
}
|
|
@@ -3428,6 +3431,61 @@ function contextSync() {
|
|
|
3428
3431
|
console.log(`\nSynced ${synced} file(s) to .claude/context/`);
|
|
3429
3432
|
}
|
|
3430
3433
|
|
|
3434
|
+
/**
|
|
3435
|
+
* Pull latest from the team-context repo so session state reflects
|
|
3436
|
+
* other engineers' recent work. Called by the session-start hook.
|
|
3437
|
+
*
|
|
3438
|
+
* Behavior:
|
|
3439
|
+
* - Pulls the team-context repo (journals, signals, shared context)
|
|
3440
|
+
* - Skips gracefully if offline, if pull fails, or if repo is dirty
|
|
3441
|
+
* - Does NOT touch the current working repo — devs handle that themselves
|
|
3442
|
+
* - Designed to run within the session-start hook's timeout
|
|
3443
|
+
*
|
|
3444
|
+
* @param {string[]} args - CLI arguments (--quiet suppresses output)
|
|
3445
|
+
*/
|
|
3446
|
+
function contextPull(args) {
|
|
3447
|
+
const quiet = args.includes('--quiet');
|
|
3448
|
+
const background = args.includes('--background');
|
|
3449
|
+
const log = quiet ? () => {} : console.log;
|
|
3450
|
+
|
|
3451
|
+
const teamPath = getTeamContextPath();
|
|
3452
|
+
if (!teamPath || !fs.existsSync(path.join(teamPath, '.git'))) {
|
|
3453
|
+
if (!quiet) log('[wayfind] No team-context repo configured — skipping pull');
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
const markerFile = path.join(teamPath, '.last-pull');
|
|
3458
|
+
|
|
3459
|
+
if (background) {
|
|
3460
|
+
// Fire-and-forget — don't block session start
|
|
3461
|
+
const child = require('child_process').spawn(
|
|
3462
|
+
process.execPath,
|
|
3463
|
+
[__filename, 'context', 'pull', '--quiet'],
|
|
3464
|
+
{ stdio: 'ignore', detached: true, env: { ...process.env } }
|
|
3465
|
+
);
|
|
3466
|
+
child.unref();
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
const result = spawnSync('git', ['-C', teamPath, 'pull', '--rebase', '--autostash', '--quiet'], {
|
|
3471
|
+
stdio: 'pipe',
|
|
3472
|
+
timeout: 10000,
|
|
3473
|
+
});
|
|
3474
|
+
|
|
3475
|
+
if (result.status === 0) {
|
|
3476
|
+
log('[wayfind] Pulled latest team-context');
|
|
3477
|
+
// Mark success — doctor checks this to warn on prolonged failures
|
|
3478
|
+
try { fs.writeFileSync(markerFile, new Date().toISOString()); } catch {}
|
|
3479
|
+
} else if (result.error && result.error.code === 'ETIMEDOUT') {
|
|
3480
|
+
log('[wayfind] Team-context pull timed out — using local state');
|
|
3481
|
+
} else {
|
|
3482
|
+
const stderr = (result.stderr || '').toString().trim();
|
|
3483
|
+
if (stderr && !quiet) {
|
|
3484
|
+
console.error(`[wayfind] Team-context pull skipped: ${stderr.split('\n')[0]}`);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3431
3489
|
function contextShow() {
|
|
3432
3490
|
const config = readContextConfig();
|
|
3433
3491
|
const repoBinding = readRepoTeamBinding();
|
|
@@ -4312,10 +4370,10 @@ function ensureContainerConfig() {
|
|
|
4312
4370
|
}
|
|
4313
4371
|
}
|
|
4314
4372
|
|
|
4315
|
-
// GitHub connector
|
|
4316
|
-
if (
|
|
4373
|
+
// GitHub connector — create if missing, or fix transport if mounted config has gh-cli
|
|
4374
|
+
if (process.env.GITHUB_TOKEN) {
|
|
4317
4375
|
const repos = process.env.TEAM_CONTEXT_GITHUB_REPOS;
|
|
4318
|
-
if (repos) {
|
|
4376
|
+
if (!config.github && repos) {
|
|
4319
4377
|
config.github = {
|
|
4320
4378
|
transport: 'https',
|
|
4321
4379
|
token: process.env.GITHUB_TOKEN,
|
|
@@ -4324,6 +4382,19 @@ function ensureContainerConfig() {
|
|
|
4324
4382
|
last_pull: null,
|
|
4325
4383
|
};
|
|
4326
4384
|
changed = true;
|
|
4385
|
+
} else if (config.github) {
|
|
4386
|
+
// Override gh-cli transport in container context — gh binary isn't available
|
|
4387
|
+
if (config.github.transport === 'gh-cli') {
|
|
4388
|
+
config.github.transport = 'https';
|
|
4389
|
+
config.github.token = process.env.GITHUB_TOKEN;
|
|
4390
|
+
config.github.token_env = 'GITHUB_TOKEN';
|
|
4391
|
+
changed = true;
|
|
4392
|
+
}
|
|
4393
|
+
// Backfill repos from env if config has none or env specifies them
|
|
4394
|
+
if (repos && (!config.github.repos || config.github.repos.length === 0)) {
|
|
4395
|
+
config.github.repos = repos.split(',').map((r) => r.trim());
|
|
4396
|
+
changed = true;
|
|
4397
|
+
}
|
|
4327
4398
|
}
|
|
4328
4399
|
}
|
|
4329
4400
|
|
|
@@ -5001,8 +5072,9 @@ const COMMANDS = {
|
|
|
5001
5072
|
}
|
|
5002
5073
|
console.log('Sanitization passed — no proprietary content detected.');
|
|
5003
5074
|
|
|
5004
|
-
//
|
|
5005
|
-
|
|
5075
|
+
// Refresh git index so status detects copied files reliably
|
|
5076
|
+
spawnSync('git', ['add', '-A'], { cwd: tmpDir, stdio: 'pipe' });
|
|
5077
|
+
const diffResult = spawnSync('git', ['diff', '--cached', '--name-only'], { cwd: tmpDir, stdio: 'pipe' });
|
|
5006
5078
|
const changes = (diffResult.stdout || '').toString().trim();
|
|
5007
5079
|
|
|
5008
5080
|
if (!changes) {
|
package/doctor.sh
CHANGED
|
@@ -269,6 +269,54 @@ check_memory_files() {
|
|
|
269
269
|
[ "$COUNT" -gt 0 ] && ok "$COUNT memory file(s) found" || info "No memory files yet"
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
check_team_context_freshness() {
|
|
273
|
+
echo ""
|
|
274
|
+
echo "Team context sync"
|
|
275
|
+
|
|
276
|
+
# Find team-context path via wayfind CLI
|
|
277
|
+
local TEAM_PATH=""
|
|
278
|
+
if [ -f "$HOME/repos/greg/wayfind/bin/team-context.js" ]; then
|
|
279
|
+
TEAM_PATH=$(node "$HOME/repos/greg/wayfind/bin/team-context.js" context show 2>/dev/null | grep 'Path:' | head -1 | sed 's/.*Path: *//' || true)
|
|
280
|
+
elif command -v wayfind >/dev/null 2>&1; then
|
|
281
|
+
TEAM_PATH=$(wayfind context show 2>/dev/null | grep 'Path:' | head -1 | sed 's/.*Path: *//' || true)
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
if [ -z "$TEAM_PATH" ] || [ ! -d "$TEAM_PATH" ]; then
|
|
285
|
+
info "No team-context repo configured (optional)"
|
|
286
|
+
return
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
local MARKER="$TEAM_PATH/.last-pull"
|
|
290
|
+
if [ ! -f "$MARKER" ]; then
|
|
291
|
+
# No pull has ever succeeded — check if this is a fresh install
|
|
292
|
+
# by looking at the git log age instead
|
|
293
|
+
local LAST_COMMIT
|
|
294
|
+
LAST_COMMIT=$(git -C "$TEAM_PATH" log -1 --format='%ci' 2>/dev/null || echo "")
|
|
295
|
+
if [ -z "$LAST_COMMIT" ]; then
|
|
296
|
+
info "Team-context repo exists but has no commits"
|
|
297
|
+
else
|
|
298
|
+
ok "Team-context configured (pull marker will appear after next session start)"
|
|
299
|
+
fi
|
|
300
|
+
return
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
local PULL_TIME
|
|
304
|
+
PULL_TIME=$(cat "$MARKER")
|
|
305
|
+
local PULL_EPOCH
|
|
306
|
+
PULL_EPOCH=$(date -d "$PULL_TIME" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%S" "${PULL_TIME%%.*}" +%s 2>/dev/null || echo 0)
|
|
307
|
+
local NOW_EPOCH
|
|
308
|
+
NOW_EPOCH=$(date +%s)
|
|
309
|
+
local AGE_HOURS=$(( (NOW_EPOCH - PULL_EPOCH) / 3600 ))
|
|
310
|
+
|
|
311
|
+
if [ "$AGE_HOURS" -gt 24 ]; then
|
|
312
|
+
warn "Team-context last pulled ${AGE_HOURS}h ago — other engineers' context may be stale"
|
|
313
|
+
info "Run: wayfind context pull"
|
|
314
|
+
ISSUES=$((ISSUES + 1))
|
|
315
|
+
else
|
|
316
|
+
ok "Team-context pulled ${AGE_HOURS}h ago"
|
|
317
|
+
fi
|
|
318
|
+
}
|
|
319
|
+
|
|
272
320
|
check_team_versions() {
|
|
273
321
|
echo ""
|
|
274
322
|
echo "Team version compliance"
|
|
@@ -309,17 +357,116 @@ check_storage_backend() {
|
|
|
309
357
|
echo ""
|
|
310
358
|
echo "Storage backend"
|
|
311
359
|
|
|
360
|
+
set +e # Temporarily disable errexit — node calls may fail in test environments
|
|
312
361
|
# Check for env var override
|
|
313
362
|
if [ -n "${TEAM_CONTEXT_STORAGE_BACKEND:-}" ]; then
|
|
314
363
|
info "TEAM_CONTEXT_STORAGE_BACKEND=$TEAM_CONTEXT_STORAGE_BACKEND (env override)"
|
|
315
364
|
fi
|
|
316
365
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
366
|
+
local WAYFIND_DIR="${WAYFIND_DIR:-$HOME/.claude/team-context}"
|
|
367
|
+
local SCRIPT_DIR
|
|
368
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
369
|
+
|
|
370
|
+
# ── 1. Which backend is active ───────────────────────────────────────────
|
|
371
|
+
local BACKEND_RESULT=""
|
|
372
|
+
BACKEND_RESULT=$(node -e "
|
|
373
|
+
try {
|
|
374
|
+
const storage = require('$SCRIPT_DIR/bin/storage/index.js');
|
|
375
|
+
const storePath = '$WAYFIND_DIR';
|
|
376
|
+
storage.getBackend(storePath);
|
|
377
|
+
const info = storage.getBackendInfo(storePath);
|
|
378
|
+
if (!info) { console.log('NONE'); process.exit(0); }
|
|
379
|
+
console.log(JSON.stringify(info));
|
|
380
|
+
} catch (e) {
|
|
381
|
+
console.log('SKIP:' + e.message.split('\n')[0]);
|
|
382
|
+
}
|
|
383
|
+
" 2>/dev/null) || BACKEND_RESULT="SKIP:storage module not available"
|
|
384
|
+
|
|
385
|
+
if [[ "$BACKEND_RESULT" == SKIP:* ]]; then
|
|
386
|
+
[ "$VERBOSE" = true ] && info "Storage check skipped: ${BACKEND_RESULT#SKIP:}"
|
|
387
|
+
elif [[ "$BACKEND_RESULT" == "NONE" ]]; then
|
|
388
|
+
info "No storage backend initialized (team-context not configured)"
|
|
389
|
+
else
|
|
390
|
+
local BACKEND_TYPE
|
|
391
|
+
BACKEND_TYPE=$(echo "$BACKEND_RESULT" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.type)" 2>/dev/null || echo "unknown")
|
|
392
|
+
local IS_FALLBACK
|
|
393
|
+
IS_FALLBACK=$(echo "$BACKEND_RESULT" | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); console.log(d.fallback)" 2>/dev/null || echo "false")
|
|
394
|
+
|
|
395
|
+
if [ "$BACKEND_TYPE" = "sqlite" ]; then
|
|
396
|
+
ok "Active backend: sqlite"
|
|
397
|
+
elif [ "$BACKEND_TYPE" = "json" ]; then
|
|
398
|
+
ok "Active backend: json"
|
|
399
|
+
else
|
|
400
|
+
warn "Active backend: $BACKEND_TYPE (unexpected)"
|
|
401
|
+
fi
|
|
402
|
+
|
|
403
|
+
# ── 2. Fallback detection ────────────────────────────────────────────
|
|
404
|
+
if [ "$IS_FALLBACK" = "true" ]; then
|
|
405
|
+
warn "JSON backend is a fallback — better-sqlite3 was expected but failed to load"
|
|
406
|
+
info "Run: npm install -g better-sqlite3 (or reinstall wayfind)"
|
|
407
|
+
ISSUES=$((ISSUES + 1))
|
|
408
|
+
elif [ "$BACKEND_TYPE" = "json" ]; then
|
|
409
|
+
# Not a runtime fallback — check if sqlite3 is available but unused
|
|
410
|
+
if node -e "require('better-sqlite3')" 2>/dev/null; then
|
|
411
|
+
warn "better-sqlite3 is installed but backend is JSON — possible unexpected fallback"
|
|
412
|
+
info "Check TEAM_CONTEXT_STORAGE_BACKEND env var or re-run setup"
|
|
413
|
+
ISSUES=$((ISSUES + 1))
|
|
414
|
+
else
|
|
415
|
+
[ "$VERBOSE" = true ] && info "better-sqlite3 not installed — JSON is expected"
|
|
416
|
+
fi
|
|
417
|
+
fi
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
# ── 3. Signal freshness (connectors.json last_pull) ──────────────────────
|
|
421
|
+
local CONNECTORS="$WAYFIND_DIR/connectors.json"
|
|
422
|
+
if [ -f "$CONNECTORS" ]; then
|
|
423
|
+
local FRESHNESS_RESULT
|
|
424
|
+
FRESHNESS_RESULT=$(node -e "
|
|
425
|
+
const fs = require('fs');
|
|
426
|
+
const config = JSON.parse(fs.readFileSync('$CONNECTORS', 'utf8'));
|
|
427
|
+
const connectors = ['github', 'intercom', 'notion'];
|
|
428
|
+
const now = Date.now();
|
|
429
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
430
|
+
const results = [];
|
|
431
|
+
for (const name of connectors) {
|
|
432
|
+
const c = config[name];
|
|
433
|
+
if (!c) continue;
|
|
434
|
+
const lp = c.last_pull;
|
|
435
|
+
if (!lp) {
|
|
436
|
+
results.push('WARN:' + name + ':never pulled');
|
|
437
|
+
} else {
|
|
438
|
+
const age = now - new Date(lp).getTime();
|
|
439
|
+
const hours = Math.floor(age / (60 * 60 * 1000));
|
|
440
|
+
if (age > DAY) {
|
|
441
|
+
results.push('WARN:' + name + ':' + hours + 'h ago');
|
|
442
|
+
} else {
|
|
443
|
+
results.push('OK:' + name + ':' + hours + 'h ago');
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
console.log(results.join('|'));
|
|
448
|
+
" 2>/dev/null) || FRESHNESS_RESULT=""
|
|
449
|
+
|
|
450
|
+
if [ -n "$FRESHNESS_RESULT" ]; then
|
|
451
|
+
IFS='|' read -ra ENTRIES <<< "$FRESHNESS_RESULT"
|
|
452
|
+
for entry in "${ENTRIES[@]}"; do
|
|
453
|
+
local STATUS="${entry%%:*}"
|
|
454
|
+
local REST="${entry#*:}"
|
|
455
|
+
local NAME="${REST%%:*}"
|
|
456
|
+
local DETAIL="${REST#*:}"
|
|
457
|
+
if [ "$STATUS" = "WARN" ]; then
|
|
458
|
+
warn "Signal $NAME: $DETAIL"
|
|
459
|
+
info "Run: wayfind pull $NAME"
|
|
460
|
+
ISSUES=$((ISSUES + 1))
|
|
461
|
+
else
|
|
462
|
+
ok "Signal $NAME: last pull $DETAIL"
|
|
463
|
+
fi
|
|
464
|
+
done
|
|
465
|
+
fi
|
|
320
466
|
else
|
|
321
|
-
|
|
467
|
+
[ "$VERBOSE" = true ] && info "No connectors.json — signal freshness check skipped"
|
|
322
468
|
fi
|
|
469
|
+
set -e # Restore errexit
|
|
323
470
|
}
|
|
324
471
|
|
|
325
472
|
# Run checks
|
|
@@ -330,6 +477,7 @@ echo "══════════════════════"
|
|
|
330
477
|
check_hook_registered
|
|
331
478
|
check_global_state
|
|
332
479
|
check_backup
|
|
480
|
+
check_team_context_freshness
|
|
333
481
|
check_team_versions
|
|
334
482
|
check_storage_backend
|
|
335
483
|
check_memory_files
|
package/package.json
CHANGED
|
@@ -25,6 +25,10 @@ if [ -z "$WAYFIND" ]; then
|
|
|
25
25
|
exit 0
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
|
+
# Pull latest team-context in the background so this session sees
|
|
29
|
+
# other engineers' recent work without blocking session start
|
|
30
|
+
$WAYFIND context pull --quiet --background 2>/dev/null || true
|
|
31
|
+
|
|
28
32
|
# Rebuild Active Projects table (idempotent, concurrent-safe)
|
|
29
33
|
$WAYFIND status --write --quiet 2>/dev/null || true
|
|
30
34
|
|