wayfind 2.0.41 → 2.0.44
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 +67 -0
- package/bin/team-context.js +74 -0
- package/doctor.sh +33 -3
- package/package.json +1 -1
- package/setup.sh +0 -42
package/bin/content-store.js
CHANGED
|
@@ -176,6 +176,27 @@ function isRepoExcluded(repo) {
|
|
|
176
176
|
return false;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Check if a repo name matches a team scope pattern list.
|
|
181
|
+
* Patterns ending with '/' are prefix matches (e.g., 'acme/' matches 'acme/api', 'acme/frontend').
|
|
182
|
+
* All other patterns are exact matches.
|
|
183
|
+
* @param {string|null} repo
|
|
184
|
+
* @param {string[]} patterns
|
|
185
|
+
* @returns {boolean}
|
|
186
|
+
*/
|
|
187
|
+
function matchesTeamScope(repo, patterns) {
|
|
188
|
+
if (!patterns || patterns.length === 0) return true;
|
|
189
|
+
if (!repo) return false;
|
|
190
|
+
for (const p of patterns) {
|
|
191
|
+
if (p.endsWith('/')) {
|
|
192
|
+
if (repo.startsWith(p)) return true;
|
|
193
|
+
} else if (repo === p) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
179
200
|
// ── Journal parsing ─────────────────────────────────────────────────────────
|
|
180
201
|
|
|
181
202
|
/**
|
|
@@ -432,6 +453,7 @@ async function indexJournals(options = {}) {
|
|
|
432
453
|
|
|
433
454
|
for (const entry of entries) {
|
|
434
455
|
if (isRepoExcluded(entry.repo)) continue;
|
|
456
|
+
if (options.repoAllowlist && !matchesTeamScope(entry.repo, options.repoAllowlist)) continue;
|
|
435
457
|
const id = generateEntryId(date, entry.repo, entry.title);
|
|
436
458
|
const author = entry.author || options.defaultAuthor || '';
|
|
437
459
|
const content = buildContent({ ...entry, date, author });
|
|
@@ -2290,6 +2312,47 @@ function deduplicateResults(results) {
|
|
|
2290
2312
|
return results.filter(r => !absorbedIds.has(r.id));
|
|
2291
2313
|
}
|
|
2292
2314
|
|
|
2315
|
+
/**
|
|
2316
|
+
* Remove entries from a store whose repo doesn't match the allowed patterns.
|
|
2317
|
+
* Trims both index and embeddings. Safe to call repeatedly (idempotent).
|
|
2318
|
+
* @param {string} storePath
|
|
2319
|
+
* @param {string[]} allowedPatterns - prefix patterns (ending '/') or exact names
|
|
2320
|
+
* @returns {{ kept: number, removed: number, removedRepos: string[] }}
|
|
2321
|
+
*/
|
|
2322
|
+
async function trimStore(storePath, allowedPatterns) {
|
|
2323
|
+
if (!allowedPatterns || allowedPatterns.length === 0) {
|
|
2324
|
+
throw new Error('allowedPatterns is required — refusing to trim to empty set');
|
|
2325
|
+
}
|
|
2326
|
+
const backend = getBackend(storePath);
|
|
2327
|
+
const idx = backend.loadIndex();
|
|
2328
|
+
const embeddings = backend.loadEmbeddings() || {};
|
|
2329
|
+
|
|
2330
|
+
const keptEntries = {};
|
|
2331
|
+
const keptEmbeddings = {};
|
|
2332
|
+
const removedRepos = [];
|
|
2333
|
+
|
|
2334
|
+
for (const [id, entry] of Object.entries(idx.entries || {})) {
|
|
2335
|
+
if (matchesTeamScope(entry.repo, allowedPatterns)) {
|
|
2336
|
+
keptEntries[id] = entry;
|
|
2337
|
+
if (embeddings[id]) keptEmbeddings[id] = embeddings[id];
|
|
2338
|
+
} else {
|
|
2339
|
+
removedRepos.push(entry.repo);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
idx.entries = keptEntries;
|
|
2344
|
+
idx.entryCount = Object.keys(keptEntries).length;
|
|
2345
|
+
idx.lastUpdated = new Date().toISOString();
|
|
2346
|
+
backend.saveIndex(idx);
|
|
2347
|
+
backend.saveEmbeddings(keptEmbeddings);
|
|
2348
|
+
|
|
2349
|
+
return {
|
|
2350
|
+
kept: idx.entryCount,
|
|
2351
|
+
removed: removedRepos.length,
|
|
2352
|
+
removedRepos: [...new Set(removedRepos)].sort(),
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2293
2356
|
module.exports = {
|
|
2294
2357
|
// Parsing
|
|
2295
2358
|
parseJournalFile,
|
|
@@ -2318,8 +2381,12 @@ module.exports = {
|
|
|
2318
2381
|
|
|
2319
2382
|
// Filtering
|
|
2320
2383
|
isRepoExcluded,
|
|
2384
|
+
matchesTeamScope,
|
|
2321
2385
|
applyFilters,
|
|
2322
2386
|
|
|
2387
|
+
// Store maintenance
|
|
2388
|
+
trimStore,
|
|
2389
|
+
|
|
2323
2390
|
// Quality & dedup
|
|
2324
2391
|
computeQualityScore,
|
|
2325
2392
|
deduplicateResults,
|
package/bin/team-context.js
CHANGED
|
@@ -1111,8 +1111,16 @@ async function runIndexJournals(args) {
|
|
|
1111
1111
|
const journalDir = opts.dir || contentStore.DEFAULT_JOURNAL_DIR;
|
|
1112
1112
|
const storePath = opts.store || contentStore.resolveStorePath();
|
|
1113
1113
|
|
|
1114
|
+
// Load team scope allowlist from context.json — only index repos bound to the active team.
|
|
1115
|
+
const ctxConfig = readContextConfig();
|
|
1116
|
+
const teamId = readRepoTeamBinding() || ctxConfig.default;
|
|
1117
|
+
const teamConfig = teamId && ctxConfig.teams && ctxConfig.teams[teamId];
|
|
1118
|
+
const repoAllowlist = (teamConfig && teamConfig.bound_repos && teamConfig.bound_repos.length > 0)
|
|
1119
|
+
? teamConfig.bound_repos : undefined;
|
|
1120
|
+
|
|
1114
1121
|
console.log(`Indexing journals from: ${journalDir}`);
|
|
1115
1122
|
console.log(`Store: ${storePath}`);
|
|
1123
|
+
if (repoAllowlist) console.log(`Team scope (${teamId}): ${repoAllowlist.join(', ')}`);
|
|
1116
1124
|
console.log('');
|
|
1117
1125
|
|
|
1118
1126
|
try {
|
|
@@ -1120,6 +1128,7 @@ async function runIndexJournals(args) {
|
|
|
1120
1128
|
journalDir,
|
|
1121
1129
|
storePath,
|
|
1122
1130
|
embeddings: opts.noEmbeddings ? false : undefined,
|
|
1131
|
+
repoAllowlist,
|
|
1123
1132
|
});
|
|
1124
1133
|
|
|
1125
1134
|
console.log(`Indexed: ${stats.entryCount} entries`);
|
|
@@ -3591,6 +3600,21 @@ function contextBind(args) {
|
|
|
3591
3600
|
writeRepoTeamBinding(teamId);
|
|
3592
3601
|
console.log(`Bound this repo to: ${config.teams[teamId].name} (${teamId})`);
|
|
3593
3602
|
console.log('Journals from this repo will sync to that team\'s context repo.');
|
|
3603
|
+
|
|
3604
|
+
// Derive repo label (e.g., "acme/api") and add to the team's bound_repos allowlist.
|
|
3605
|
+
const cwdParts = process.cwd().split(path.sep);
|
|
3606
|
+
const reposIdx = cwdParts.lastIndexOf('repos');
|
|
3607
|
+
const repoLabel = (reposIdx >= 0 && reposIdx + 2 <= cwdParts.length)
|
|
3608
|
+
? cwdParts.slice(reposIdx + 1).join('/')
|
|
3609
|
+
: cwdParts[cwdParts.length - 1];
|
|
3610
|
+
|
|
3611
|
+
const team = config.teams[teamId];
|
|
3612
|
+
if (!team.bound_repos) team.bound_repos = [];
|
|
3613
|
+
if (!team.bound_repos.includes(repoLabel)) {
|
|
3614
|
+
team.bound_repos.push(repoLabel);
|
|
3615
|
+
writeContextConfig(config);
|
|
3616
|
+
console.log(`Added "${repoLabel}" to team scope.`);
|
|
3617
|
+
}
|
|
3594
3618
|
}
|
|
3595
3619
|
|
|
3596
3620
|
function contextList() {
|
|
@@ -5000,6 +5024,52 @@ async function runContainerDoctor() {
|
|
|
5000
5024
|
}
|
|
5001
5025
|
}
|
|
5002
5026
|
|
|
5027
|
+
// ── Store management ────────────────────────────────────────────────────────
|
|
5028
|
+
|
|
5029
|
+
async function runStoreTrim(args) {
|
|
5030
|
+
const ctxConfig = readContextConfig();
|
|
5031
|
+
const teamId = args[0] || readRepoTeamBinding() || ctxConfig.default;
|
|
5032
|
+
if (!teamId) {
|
|
5033
|
+
console.error('No team ID resolved. Usage: wayfind store trim [team-id]');
|
|
5034
|
+
process.exit(1);
|
|
5035
|
+
}
|
|
5036
|
+
const team = ctxConfig.teams && ctxConfig.teams[teamId];
|
|
5037
|
+
if (!team) {
|
|
5038
|
+
console.error(`Team "${teamId}" not found.`);
|
|
5039
|
+
process.exit(1);
|
|
5040
|
+
}
|
|
5041
|
+
const allowedPatterns = team.bound_repos;
|
|
5042
|
+
if (!allowedPatterns || allowedPatterns.length === 0) {
|
|
5043
|
+
console.error(`Team "${teamId}" has no bound_repos in context.json. Configure them first.`);
|
|
5044
|
+
process.exit(1);
|
|
5045
|
+
}
|
|
5046
|
+
const storePath = contentStore.resolveStorePath(teamId);
|
|
5047
|
+
console.log(`Team: ${team.name} (${teamId})`);
|
|
5048
|
+
console.log(`Store: ${storePath}`);
|
|
5049
|
+
console.log(`Patterns: ${allowedPatterns.join(', ')}`);
|
|
5050
|
+
console.log('');
|
|
5051
|
+
const stats = await contentStore.trimStore(storePath, allowedPatterns);
|
|
5052
|
+
console.log(`Kept: ${stats.kept}`);
|
|
5053
|
+
console.log(`Removed: ${stats.removed}`);
|
|
5054
|
+
if (stats.removedRepos.length > 0) {
|
|
5055
|
+
console.log(`Repos removed:`);
|
|
5056
|
+
stats.removedRepos.forEach(r => console.log(` ${r}`));
|
|
5057
|
+
}
|
|
5058
|
+
}
|
|
5059
|
+
|
|
5060
|
+
async function runStore(args) {
|
|
5061
|
+
const [sub, ...subArgs] = args;
|
|
5062
|
+
switch (sub) {
|
|
5063
|
+
case 'trim':
|
|
5064
|
+
await runStoreTrim(subArgs);
|
|
5065
|
+
break;
|
|
5066
|
+
default:
|
|
5067
|
+
console.error(`Unknown store subcommand: ${sub || ''}`);
|
|
5068
|
+
console.error('Available: trim');
|
|
5069
|
+
process.exit(1);
|
|
5070
|
+
}
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5003
5073
|
// ── Command registry ────────────────────────────────────────────────────────
|
|
5004
5074
|
|
|
5005
5075
|
const COMMANDS = {
|
|
@@ -5274,6 +5344,10 @@ const COMMANDS = {
|
|
|
5274
5344
|
desc: 'Scaffold Docker deployment in your team context repo',
|
|
5275
5345
|
run: (args) => runDeploy(args),
|
|
5276
5346
|
},
|
|
5347
|
+
store: {
|
|
5348
|
+
desc: 'Manage content store (trim)',
|
|
5349
|
+
run: (args) => runStore(args),
|
|
5350
|
+
},
|
|
5277
5351
|
onboard: {
|
|
5278
5352
|
desc: 'Generate an onboarding context pack for a repo',
|
|
5279
5353
|
run: (args) => runOnboard(args),
|
package/doctor.sh
CHANGED
|
@@ -363,16 +363,45 @@ check_storage_backend() {
|
|
|
363
363
|
info "TEAM_CONTEXT_STORAGE_BACKEND=$TEAM_CONTEXT_STORAGE_BACKEND (env override)"
|
|
364
364
|
fi
|
|
365
365
|
|
|
366
|
-
local WAYFIND_DIR="${WAYFIND_DIR:-$HOME/.claude/team-context}"
|
|
367
366
|
local SCRIPT_DIR
|
|
368
367
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
369
368
|
|
|
369
|
+
# Resolve the team-scoped store path (respects .claude/wayfind.json + context.json default)
|
|
370
|
+
local RESOLVED_STORE ACTIVE_TEAM_ID
|
|
371
|
+
RESOLVED_STORE=$(node -e "
|
|
372
|
+
try {
|
|
373
|
+
const cs = require('$SCRIPT_DIR/bin/content-store.js');
|
|
374
|
+
console.log(cs.resolveStorePath());
|
|
375
|
+
} catch (e) {
|
|
376
|
+
console.log('SKIP:' + e.message.split('\n')[0]);
|
|
377
|
+
}
|
|
378
|
+
" 2>/dev/null) || RESOLVED_STORE="SKIP:content-store not available"
|
|
379
|
+
ACTIVE_TEAM_ID=$(node -e "
|
|
380
|
+
try {
|
|
381
|
+
const cs = require('$SCRIPT_DIR/bin/content-store.js');
|
|
382
|
+
// resolveStorePath triggers team ID resolution; extract from path
|
|
383
|
+
const p = cs.resolveStorePath();
|
|
384
|
+
const m = p && p.match(/teams\/([^/]+)\/content-store/);
|
|
385
|
+
console.log(m ? m[1] : 'default');
|
|
386
|
+
} catch (e) {
|
|
387
|
+
console.log('unknown');
|
|
388
|
+
}
|
|
389
|
+
" 2>/dev/null) || ACTIVE_TEAM_ID="unknown"
|
|
390
|
+
|
|
391
|
+
if [[ "$RESOLVED_STORE" != SKIP:* ]]; then
|
|
392
|
+
ok "Active team: $ACTIVE_TEAM_ID"
|
|
393
|
+
info "Store path: $RESOLVED_STORE"
|
|
394
|
+
fi
|
|
395
|
+
|
|
396
|
+
local WAYFIND_DIR="${WAYFIND_DIR:-$HOME/.claude/team-context}"
|
|
397
|
+
|
|
370
398
|
# ── 1. Which backend is active ───────────────────────────────────────────
|
|
371
399
|
local BACKEND_RESULT=""
|
|
372
400
|
BACKEND_RESULT=$(node -e "
|
|
373
401
|
try {
|
|
402
|
+
const cs = require('$SCRIPT_DIR/bin/content-store.js');
|
|
374
403
|
const storage = require('$SCRIPT_DIR/bin/storage/index.js');
|
|
375
|
-
const storePath =
|
|
404
|
+
const storePath = cs.resolveStorePath();
|
|
376
405
|
storage.getBackend(storePath);
|
|
377
406
|
const info = storage.getBackendInfo(storePath);
|
|
378
407
|
if (!info) { console.log('NONE'); process.exit(0); }
|
|
@@ -472,8 +501,9 @@ check_storage_backend() {
|
|
|
472
501
|
local EMBED_RESULT
|
|
473
502
|
EMBED_RESULT=$(node -e "
|
|
474
503
|
try {
|
|
504
|
+
const cs = require('$SCRIPT_DIR/bin/content-store.js');
|
|
475
505
|
const storage = require('$SCRIPT_DIR/bin/storage/index.js');
|
|
476
|
-
const storePath =
|
|
506
|
+
const storePath = cs.resolveStorePath();
|
|
477
507
|
const backend = storage.getBackend(storePath);
|
|
478
508
|
const idx = backend.loadIndex();
|
|
479
509
|
if (!idx || !idx.entries) { console.log('SKIP'); process.exit(0); }
|
package/package.json
CHANGED
package/setup.sh
CHANGED
|
@@ -441,48 +441,6 @@ PYEOF
|
|
|
441
441
|
fi
|
|
442
442
|
fi
|
|
443
443
|
|
|
444
|
-
# Status line script
|
|
445
|
-
STATUSLINE_DEST="$HOME/.claude/team-context/statusline.sh"
|
|
446
|
-
if [ ! -f "$STATUSLINE_DEST" ] || [ "$UPDATE" = true ]; then
|
|
447
|
-
run cp "$SCRIPT_DIR/templates/statusline.sh" "$STATUSLINE_DEST"
|
|
448
|
-
run chmod +x "$STATUSLINE_DEST"
|
|
449
|
-
log "Installed status line: $STATUSLINE_DEST"
|
|
450
|
-
else
|
|
451
|
-
info "Status line already exists: $STATUSLINE_DEST — skipped"
|
|
452
|
-
fi
|
|
453
|
-
|
|
454
|
-
# Merge statusLine config into settings.json
|
|
455
|
-
if [ -f "$SETTINGS" ] && ! grep -q "statusLine" "$SETTINGS" 2>/dev/null; then
|
|
456
|
-
if [ "$DRY_RUN" = false ]; then
|
|
457
|
-
TMP_SL="$(mktemp)"
|
|
458
|
-
if python3 - "$SETTINGS" "$STATUSLINE_DEST" "$TMP_SL" <<'PYEOF' 2>/dev/null; then
|
|
459
|
-
import json, sys
|
|
460
|
-
settings_path, sl_cmd, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
461
|
-
try:
|
|
462
|
-
with open(settings_path) as f:
|
|
463
|
-
settings = json.load(f)
|
|
464
|
-
except (json.JSONDecodeError, IOError):
|
|
465
|
-
sys.exit(1)
|
|
466
|
-
if "statusLine" not in settings:
|
|
467
|
-
settings["statusLine"] = {"type": "command", "command": sl_cmd, "padding": 2}
|
|
468
|
-
with open(out_path, "w") as f:
|
|
469
|
-
json.dump(settings, f, indent=2)
|
|
470
|
-
f.write("\n")
|
|
471
|
-
PYEOF
|
|
472
|
-
mv "$TMP_SL" "$SETTINGS"
|
|
473
|
-
log "Added statusLine config to $SETTINGS"
|
|
474
|
-
else
|
|
475
|
-
rm -f "$TMP_SL"
|
|
476
|
-
warn "Could not add statusLine to $SETTINGS (malformed JSON or python3 unavailable)"
|
|
477
|
-
fi
|
|
478
|
-
else
|
|
479
|
-
info "[dry-run] Would add statusLine to $SETTINGS"
|
|
480
|
-
fi
|
|
481
|
-
else
|
|
482
|
-
if [ -f "$SETTINGS" ]; then
|
|
483
|
-
info "statusLine already configured in settings.json — skipped"
|
|
484
|
-
fi
|
|
485
|
-
fi
|
|
486
444
|
;;
|
|
487
445
|
|
|
488
446
|
cursor)
|