wayfind 2.0.9 → 2.0.10

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.
@@ -39,11 +39,14 @@ const ENTRY_HEADER_RE = /^##\s+(.+?)\s+[—–]\s+(.+)$/;
39
39
  const FIELD_RE = /^\*\*([^*:]+):\*\*\s*(.*)$/;
40
40
  const DATE_FILE_RE = /^(\d{4}-\d{2}-\d{2})(?:-([a-z0-9._-]+))?\.md$/;
41
41
 
42
- // Repo exclusion list (comma-separated, case-insensitive, supports org/repo or just repo).
43
- // NOTE: Team boundaries are now enforced at export/sync time via opt-in .claude/wayfind.json
44
- // bindings (see buildRepoToTeamResolver in team-context.js). This env var is still useful
45
- // for filtering repos out of indexing and queries entirely, but is no longer needed for
46
- // preventing unbound repos from leaking into team digests.
42
+ // Team repo allowlist when set, only entries from matching repos are indexed/queried.
43
+ // Replaces the old TEAM_CONTEXT_EXCLUDE_REPOS blocklist approach.
44
+ // Format: comma-separated, supports org/* wildcards (e.g., "HopSkipInc/*,Doorbell/*")
45
+ const INCLUDE_REPOS = (process.env.TEAM_CONTEXT_INCLUDE_REPOS || '')
46
+ .split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
47
+
48
+ // DEPRECATED: Legacy blocklist — use TEAM_CONTEXT_INCLUDE_REPOS instead.
49
+ // Still read for backward compatibility during migration.
47
50
  const EXCLUDE_REPOS = (process.env.TEAM_CONTEXT_EXCLUDE_REPOS || '')
48
51
  .split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
49
52
 
@@ -52,13 +55,33 @@ const DRIFT_POSITIVE = ['drift', 'drifted', 'tangent', 'pivoted', 'sidetracked',
52
55
  const DRIFT_NEGATIVE = ['no drift', 'no tangent', 'on track', 'focused', 'laser focused', 'stayed focused'];
53
56
 
54
57
  /**
55
- * Check if a repo name matches the exclusion list.
56
- * Matches against repo name alone or org/repo format.
58
+ * Check if a repo should be filtered out.
59
+ * If INCLUDE_REPOS is set: only allow repos matching the allowlist.
60
+ * If only EXCLUDE_REPOS is set (legacy): block repos on the blocklist.
61
+ * Supports org/* wildcard patterns.
57
62
  */
58
63
  function isRepoExcluded(repo) {
59
- if (!EXCLUDE_REPOS.length || !repo) return false;
64
+ if (!repo) return (INCLUDE_REPOS.length > 0); // exclude null repo only when allowlist active
60
65
  const lower = repo.toLowerCase();
61
- return EXCLUDE_REPOS.some(ex => lower === ex || lower.endsWith('/' + ex) || lower.startsWith(ex + '/'));
66
+
67
+ // Allowlist takes priority — if set, only matching repos pass
68
+ if (INCLUDE_REPOS.length > 0) {
69
+ return !INCLUDE_REPOS.some(pattern => {
70
+ if (pattern.endsWith('/*')) {
71
+ // org/* wildcard — match org prefix
72
+ const org = pattern.slice(0, -2);
73
+ return lower.startsWith(org + '/');
74
+ }
75
+ return lower === pattern || lower.endsWith('/' + pattern) || lower.startsWith(pattern + '/');
76
+ });
77
+ }
78
+
79
+ // Legacy blocklist fallback
80
+ if (EXCLUDE_REPOS.length > 0) {
81
+ return EXCLUDE_REPOS.some(ex => lower === ex || lower.endsWith('/' + ex) || lower.startsWith(ex + '/'));
82
+ }
83
+
84
+ return false;
62
85
  }
63
86
 
64
87
  // ── Journal parsing ─────────────────────────────────────────────────────────
@@ -1974,6 +1997,7 @@ module.exports = {
1974
1997
  saveConversationIndex: (storePath, convIndex) => getBackend(storePath || DEFAULT_STORE_PATH).saveConversationIndex(convIndex),
1975
1998
 
1976
1999
  // Filtering
2000
+ isRepoExcluded,
1977
2001
  applyFilters,
1978
2002
 
1979
2003
  // Core operations
package/bin/digest.js CHANGED
@@ -11,13 +11,32 @@ const HOME = process.env.HOME || process.env.USERPROFILE;
11
11
  const WAYFIND_DIR = path.join(HOME, '.claude', 'team-context');
12
12
  const ENV_FILE = path.join(WAYFIND_DIR, '.env');
13
13
 
14
- // Build regex for content-level filtering of excluded repos
14
+ // Team repo allowlist — mirrors content-store.js logic for digest-time filtering.
15
+ // When INCLUDE_REPOS is set, sections mentioning repos NOT on the list are removed.
16
+ // Falls back to EXCLUDE_REPOS (legacy) for backward compatibility.
17
+ const INCLUDE_REPOS_RAW = (process.env.TEAM_CONTEXT_INCLUDE_REPOS || '')
18
+ .split(',').map(r => r.trim()).filter(Boolean);
15
19
  const EXCLUDE_REPOS_RAW = (process.env.TEAM_CONTEXT_EXCLUDE_REPOS || '')
16
20
  .split(',').map(r => r.trim()).filter(Boolean);
17
21
 
22
+ /**
23
+ * Check if a repo name matches the include list.
24
+ * Supports org/* wildcards.
25
+ */
26
+ function isRepoIncluded(repo) {
27
+ if (INCLUDE_REPOS_RAW.length === 0) return true; // no filter = include all
28
+ const lower = repo.toLowerCase();
29
+ return INCLUDE_REPOS_RAW.some(pattern => {
30
+ const p = pattern.toLowerCase();
31
+ if (p.endsWith('/*')) {
32
+ return lower.startsWith(p.slice(0, -2) + '/');
33
+ }
34
+ return lower === p || lower.endsWith('/' + p) || lower.startsWith(p + '/');
35
+ });
36
+ }
37
+
18
38
  function buildExcludePattern() {
19
39
  if (EXCLUDE_REPOS_RAW.length === 0) return null;
20
- // Match repo names as whole words (case-insensitive)
21
40
  const escaped = EXCLUDE_REPOS_RAW.map(r => r.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
22
41
  return new RegExp(`\\b(${escaped.join('|')})\\b`, 'i');
23
42
  }
@@ -25,15 +44,33 @@ function buildExcludePattern() {
25
44
  const EXCLUDE_CONTENT_RE = buildExcludePattern();
26
45
 
27
46
  /**
28
- * Filter assembled content sections by removing any section whose body
29
- * mentions an excluded repo. Sections are separated by \n\n---\n\n.
47
+ * Filter assembled content sections by team boundaries.
48
+ * Sections are separated by \n\n---\n\n.
49
+ * If INCLUDE_REPOS is set: keep only sections mentioning included repos.
50
+ * Falls back to EXCLUDE_REPOS regex for backward compatibility.
30
51
  */
31
52
  function filterExcludedContent(content) {
32
- if (!EXCLUDE_CONTENT_RE || !content) return content;
33
- return content
34
- .split('\n\n---\n\n')
35
- .filter(section => !EXCLUDE_CONTENT_RE.test(section))
36
- .join('\n\n---\n\n');
53
+ if (!content) return content;
54
+
55
+ const sections = content.split('\n\n---\n\n');
56
+
57
+ if (INCLUDE_REPOS_RAW.length > 0) {
58
+ // Allowlist mode: extract repo from section header (### DATE — REPO) and check
59
+ return sections.filter(section => {
60
+ const repoMatch = section.match(/^###\s+\S+\s+[—–]\s+(\S+)/);
61
+ if (!repoMatch) return true; // keep non-journal sections (signals, etc.)
62
+ return isRepoIncluded(repoMatch[1]);
63
+ }).join('\n\n---\n\n');
64
+ }
65
+
66
+ // Legacy: regex blocklist
67
+ if (EXCLUDE_CONTENT_RE) {
68
+ return sections
69
+ .filter(section => !EXCLUDE_CONTENT_RE.test(section))
70
+ .join('\n\n---\n\n');
71
+ }
72
+
73
+ return content;
37
74
  }
38
75
 
39
76
  // ── Env file helpers ────────────────────────────────────────────────────────
@@ -636,7 +673,7 @@ async function generateDigest(config, personaIds, sinceDate, onProgress) {
636
673
  journalContent = collectJournals(sinceDate, config.journal_dir);
637
674
  }
638
675
 
639
- // Filter out content mentioning excluded repos (TEAM_CONTEXT_EXCLUDE_REPOS)
676
+ // Filter by team boundaries (TEAM_CONTEXT_INCLUDE_REPOS allowlist, falls back to EXCLUDE_REPOS)
640
677
  journalContent = filterExcludedContent(journalContent);
641
678
  signalContent = filterExcludedContent(signalContent);
642
679
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wayfind",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
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"
@@ -35,8 +35,11 @@ GITHUB_TOKEN=
35
35
  # Tenant identifier (prefixes storage paths)
36
36
  # TEAM_CONTEXT_TENANT_ID=my-team
37
37
 
38
- # Exclude repos from digests and bot queries (comma-separated, case-insensitive)
39
- # Useful when engineers work on repos belonging to different teams on the same machine
38
+ # Team repo allowlist — only journals from these repos appear in digests and queries.
39
+ # Supports org/* wildcards. This is the recommended way to scope a container to one team.
40
+ # TEAM_CONTEXT_INCLUDE_REPOS=MyOrg/*,MyOrg-Libs/*
41
+
42
+ # DEPRECATED: Blocklist approach. Use INCLUDE_REPOS instead.
40
43
  # TEAM_CONTEXT_EXCLUDE_REPOS=wayfind,personal-project
41
44
 
42
45
  # Encryption key — generate with: openssl rand -base64 32
@@ -18,7 +18,9 @@ services:
18
18
  SLACK_DIGEST_CHANNEL: ${SLACK_DIGEST_CHANNEL:-}
19
19
  TEAM_CONTEXT_SLACK_WEBHOOK: ${TEAM_CONTEXT_SLACK_WEBHOOK:-}
20
20
 
21
- # Exclude repos from digests/queries (comma-separated, case-insensitive)
21
+ # Team repo allowlist (recommended over EXCLUDE_REPOS)
22
+ TEAM_CONTEXT_INCLUDE_REPOS: ${TEAM_CONTEXT_INCLUDE_REPOS:-}
23
+ # DEPRECATED: Blocklist approach — use INCLUDE_REPOS instead
22
24
  TEAM_CONTEXT_EXCLUDE_REPOS: ${TEAM_CONTEXT_EXCLUDE_REPOS:-}
23
25
 
24
26
  # Telemetry (opt-in, sends anonymous usage data to improve Wayfind)