wayfind 2.0.13 → 2.0.14

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.
@@ -41,7 +41,7 @@ const DATE_FILE_RE = /^(\d{4}-\d{2}-\d{2})(?:-([a-z0-9._-]+))?\.md$/;
41
41
 
42
42
  // Team repo allowlist — when set, only entries from matching repos are indexed/queried.
43
43
  // Replaces the old TEAM_CONTEXT_EXCLUDE_REPOS blocklist approach.
44
- // Format: comma-separated, supports org/* wildcards (e.g., "HopSkipInc/*,Doorbell/*")
44
+ // Format: comma-separated, supports org/* wildcards (e.g., "AcmeCorp/*,Frontend/*")
45
45
  const INCLUDE_REPOS = (process.env.TEAM_CONTEXT_INCLUDE_REPOS || '')
46
46
  .split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
47
47
 
@@ -24,6 +24,13 @@ const NEXT_HEADERS = [
24
24
  "What's Left",
25
25
  ];
26
26
 
27
+ const BLOCKER_HEADERS = [
28
+ 'Blockers',
29
+ 'Blocking',
30
+ "What I'm Watching",
31
+ 'Open Questions',
32
+ ];
33
+
27
34
  // ── Scanning ─────────────────────────────────────────────────────────────────
28
35
 
29
36
  /**
@@ -64,17 +71,18 @@ function scanDir(dir, results, seen, depth) {
64
71
  const personalState = path.join(claudeDir, 'personal-state.md');
65
72
  const plainState = path.join(claudeDir, 'state.md');
66
73
 
67
- // Prefer team-state.md, fall back to state.md
74
+ // Prefer team-state.md, fall back to state.md, then personal-state.md
68
75
  let stateFile = null;
69
76
  if (fs.existsSync(teamState)) stateFile = teamState;
70
77
  else if (fs.existsSync(plainState)) stateFile = plainState;
78
+ else if (fs.existsSync(personalState)) stateFile = personalState;
71
79
 
72
80
  if (stateFile && !seen.has(dir)) {
73
81
  seen.add(dir);
74
82
  results.push({ repoDir: dir, stateFile });
75
83
 
76
- // Also note personal-state.md if it exists (for richer parsing)
77
- if (fs.existsSync(personalState)) {
84
+ // Also note personal-state.md if it exists and wasn't chosen as primary
85
+ if (stateFile !== personalState && fs.existsSync(personalState)) {
78
86
  results[results.length - 1].personalStateFile = personalState;
79
87
  }
80
88
  }
@@ -294,4 +302,5 @@ module.exports = {
294
302
  DEFAULT_ROOTS,
295
303
  STATUS_HEADERS,
296
304
  NEXT_HEADERS,
305
+ BLOCKER_HEADERS,
297
306
  };
@@ -2061,6 +2061,439 @@ function commitAndPushTeamJournals(teamContextPath, copied) {
2061
2061
  }
2062
2062
  }
2063
2063
 
2064
+ // ── Standup command ─────────────────────────────────────────────────────────
2065
+
2066
+ /**
2067
+ * Find and parse the most recent journal entry.
2068
+ * @param {string} journalDir - Journal directory path
2069
+ * @param {string} [repoFilter] - If set, only match entries whose repo header
2070
+ * matches this name (case-insensitive). Skips non-matching entries.
2071
+ * @returns {{ date: string, repo: string, title: string, what: string } | null}
2072
+ */
2073
+ function getLastJournalEntry(journalDir, repoFilter) {
2074
+ if (!journalDir || !fs.existsSync(journalDir)) return null;
2075
+
2076
+ const files = fs.readdirSync(journalDir)
2077
+ .filter(f => /^\d{4}-\d{2}-\d{2}/.test(f) && f.endsWith('.md'))
2078
+ .sort()
2079
+ .reverse();
2080
+
2081
+ const filterLower = repoFilter ? repoFilter.toLowerCase() : null;
2082
+
2083
+ for (const file of files) {
2084
+ let content;
2085
+ try {
2086
+ content = fs.readFileSync(path.join(journalDir, file), 'utf8');
2087
+ } catch {
2088
+ continue;
2089
+ }
2090
+
2091
+ const lines = content.split('\n');
2092
+
2093
+ // Find all entry start indices (## Repo — Title or ## Repo)
2094
+ const entryStarts = [];
2095
+ for (let i = 0; i < lines.length; i++) {
2096
+ if (/^## /.test(lines[i])) entryStarts.push(i);
2097
+ }
2098
+ if (entryStarts.length === 0) continue;
2099
+
2100
+ // Walk entries in reverse (most recent first within each file)
2101
+ for (let e = entryStarts.length - 1; e >= 0; e--) {
2102
+ const start = entryStarts[e];
2103
+ const end = e + 1 < entryStarts.length ? entryStarts[e + 1] : lines.length;
2104
+ const entryLines = lines.slice(start, end);
2105
+
2106
+ const headerMatch = entryLines[0].match(/^## (.+?)(?:\s*[—–-]\s*(.+))?$/);
2107
+ const repo = headerMatch ? headerMatch[1].trim() : '';
2108
+ const title = headerMatch && headerMatch[2] ? headerMatch[2].trim() : '';
2109
+
2110
+ // Apply repo filter if set
2111
+ if (filterLower && repo.toLowerCase() !== filterLower) continue;
2112
+
2113
+ // Extract **What:** field
2114
+ let what = '';
2115
+ for (let i = 1; i < entryLines.length; i++) {
2116
+ const match = entryLines[i].match(/^\*\*What:\*\*\s*(.*)$/);
2117
+ if (match) {
2118
+ what = match[1].trim();
2119
+ if (!what) {
2120
+ for (let j = i + 1; j < entryLines.length; j++) {
2121
+ if (!entryLines[j].trim() || /^\*\*/.test(entryLines[j])) break;
2122
+ what += (what ? ' ' : '') + entryLines[j].trim();
2123
+ }
2124
+ }
2125
+ break;
2126
+ }
2127
+ }
2128
+
2129
+ const dateMatch = file.match(/^(\d{4}-\d{2}-\d{2})/);
2130
+ const date = dateMatch ? dateMatch[1] : '';
2131
+
2132
+ if (repo || what) return { date, repo, title, what };
2133
+ }
2134
+ }
2135
+
2136
+ return null;
2137
+ }
2138
+
2139
+ /**
2140
+ * Emit a daily standup summary.
2141
+ * Default: scoped to the current repo. --all scans every repo with state files.
2142
+ * Shows: what was done last (from journal), what's planned, and blockers.
2143
+ * @param {string[]} args - CLI arguments (supports --all)
2144
+ */
2145
+ function runStandup(args) {
2146
+ const explicitAll = args.includes('--all');
2147
+ const journalDir = contentStore.DEFAULT_JOURNAL_DIR;
2148
+ const cwd = process.cwd();
2149
+
2150
+ // Detect if we're inside a repo (.git + .claude/ state files)
2151
+ // Home dir may have ~/.claude/state.md but isn't a repo
2152
+ const claudeDir = path.join(cwd, '.claude');
2153
+ const hasGit = fs.existsSync(path.join(cwd, '.git'));
2154
+ const inRepo = hasGit && fs.existsSync(claudeDir) && (
2155
+ fs.existsSync(path.join(claudeDir, 'team-state.md')) ||
2156
+ fs.existsSync(path.join(claudeDir, 'state.md')) ||
2157
+ fs.existsSync(path.join(claudeDir, 'personal-state.md'))
2158
+ );
2159
+
2160
+ // If not in a repo, behave like --all
2161
+ const showAll = explicitAll || !inRepo;
2162
+
2163
+ const PLAN_HEADERS = [
2164
+ ...rebuildStatus.NEXT_HEADERS,
2165
+ 'My Current Focus',
2166
+ 'Current Sprint Focus',
2167
+ 'Current Focus',
2168
+ ];
2169
+
2170
+ // Determine which state files to read
2171
+ let stateEntries;
2172
+ if (showAll) {
2173
+ const envRoots = process.env.AI_MEMORY_SCAN_ROOTS;
2174
+ const roots = envRoots
2175
+ ? envRoots.split(':').filter(Boolean)
2176
+ : rebuildStatus.DEFAULT_ROOTS;
2177
+ stateEntries = rebuildStatus.scanStateFiles(roots);
2178
+ } else {
2179
+ // Current repo only — look for .claude/ state files in cwd
2180
+ stateEntries = [];
2181
+ const teamState = path.join(claudeDir, 'team-state.md');
2182
+ const personalState = path.join(claudeDir, 'personal-state.md');
2183
+ const plainState = path.join(claudeDir, 'state.md');
2184
+
2185
+ let stateFile = null;
2186
+ if (fs.existsSync(teamState)) stateFile = teamState;
2187
+ else if (fs.existsSync(plainState)) stateFile = plainState;
2188
+ else if (fs.existsSync(personalState)) stateFile = personalState;
2189
+
2190
+ if (stateFile) {
2191
+ const entry = { repoDir: cwd, stateFile };
2192
+ if (stateFile !== personalState && fs.existsSync(personalState)) {
2193
+ entry.personalStateFile = personalState;
2194
+ }
2195
+ stateEntries.push(entry);
2196
+ }
2197
+ }
2198
+
2199
+ // Gather next steps and blockers from state files
2200
+ const nextItems = [];
2201
+ const blockerItems = [];
2202
+ const seenNext = new Set();
2203
+ const seenBlockers = new Set();
2204
+
2205
+ for (const { stateFile, personalStateFile } of stateEntries) {
2206
+ const filesToCheck = [stateFile, personalStateFile].filter(Boolean);
2207
+ for (const filePath of filesToCheck) {
2208
+ let fileContent;
2209
+ try {
2210
+ fileContent = fs.readFileSync(filePath, 'utf8');
2211
+ } catch {
2212
+ continue;
2213
+ }
2214
+ const lines = fileContent.split('\n').map(l => l.replace(/\r$/, ''));
2215
+
2216
+ const h1 = lines.find(l => /^# /.test(l));
2217
+ const project = h1
2218
+ ? h1.replace(/^# /, '').replace(/\s*[—–-].*$/, '').trim()
2219
+ : path.basename(path.dirname(path.dirname(filePath)));
2220
+
2221
+ const next = extractStandupSection(lines, PLAN_HEADERS);
2222
+ if (next && !seenNext.has(next)) {
2223
+ seenNext.add(next);
2224
+ nextItems.push({ project, text: next });
2225
+ }
2226
+
2227
+ const blocker = extractStandupSection(lines, rebuildStatus.BLOCKER_HEADERS);
2228
+ if (blocker && !seenBlockers.has(blocker)) {
2229
+ seenBlockers.add(blocker);
2230
+ blockerItems.push({ project, text: blocker });
2231
+ }
2232
+ }
2233
+ }
2234
+
2235
+ // Filter journal to current repo when scoped, global when --all or home dir
2236
+ const repoName = !showAll ? path.basename(cwd) : null;
2237
+ const lastEntry = getLastJournalEntry(journalDir, repoName);
2238
+ const scope = showAll ? 'all repos' : repoName;
2239
+
2240
+ console.log('');
2241
+ console.log(`━━━ Standup (${scope}) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
2242
+ console.log('');
2243
+
2244
+ // Last session
2245
+ if (lastEntry) {
2246
+ const dateStr = lastEntry.date ? ` (${lastEntry.date})` : '';
2247
+ const repoStr = lastEntry.repo ? ` [${lastEntry.repo}]` : '';
2248
+ console.log(`▶ Last session${dateStr}${repoStr}:`);
2249
+ const summary = lastEntry.what || lastEntry.title || '(no details recorded)';
2250
+ console.log(` ${summary}`);
2251
+ } else {
2252
+ console.log('▶ Last session:');
2253
+ console.log(' (no journal entries found)');
2254
+ }
2255
+
2256
+ // Plan for today
2257
+ console.log('');
2258
+ console.log('▶ Plan for today:');
2259
+ if (nextItems.length > 0) {
2260
+ for (const item of nextItems) {
2261
+ const prefix = showAll ? `${item.project}: ` : '';
2262
+ console.log(` ${prefix}${item.text}`);
2263
+ }
2264
+ } else {
2265
+ console.log(' (no next steps recorded — update your state file to set a goal)');
2266
+ }
2267
+
2268
+ // Blockers
2269
+ console.log('');
2270
+ console.log('▶ Blockers:');
2271
+ if (blockerItems.length > 0) {
2272
+ for (const item of blockerItems) {
2273
+ const prefix = showAll ? `${item.project}: ` : '';
2274
+ console.log(` ${prefix}${item.text}`);
2275
+ }
2276
+ } else {
2277
+ console.log(' None');
2278
+ }
2279
+
2280
+ console.log('');
2281
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
2282
+ if (!showAll && inRepo) {
2283
+ console.log(' Use --all for a cross-repo standup.');
2284
+ }
2285
+ console.log('');
2286
+ }
2287
+
2288
+ /**
2289
+ * Like extractSection but without the 120-char truncation used for status tables.
2290
+ * Returns full paragraph text for standup display.
2291
+ */
2292
+ function extractStandupSection(lines, headers) {
2293
+ for (const header of headers) {
2294
+ const idx = lines.findIndex(l => {
2295
+ const match = l.match(/^#{2,3}\s+(.+)$/);
2296
+ return match && match[1].trim() === header;
2297
+ });
2298
+ if (idx === -1) continue;
2299
+
2300
+ const para = [];
2301
+ for (let i = idx + 1; i < lines.length; i++) {
2302
+ const line = lines[i];
2303
+ if (/^#{1,3}\s/.test(line)) break;
2304
+ if (line.trim() === '' && para.length > 0) break;
2305
+ if (line.trim() === '') continue;
2306
+ para.push(line.trim());
2307
+ }
2308
+
2309
+ if (para.length === 0) continue;
2310
+
2311
+ let text = para.join(' ');
2312
+ text = text.replace(/\*\*/g, '').replace(/`/g, '');
2313
+ return text;
2314
+ }
2315
+ return '';
2316
+ }
2317
+
2318
+ // ── Update command ─────────────────────────────────────────────────────────
2319
+
2320
+ /**
2321
+ * Re-sync hooks and commands from the installed Wayfind package to ~/.claude/.
2322
+ * Copies hook scripts and slash-command files, overwriting stale copies.
2323
+ */
2324
+ function runUpdate() {
2325
+ const specDir = path.join(ROOT, 'specializations', 'claude-code');
2326
+ const hooksDir = path.join(HOME, '.claude', 'hooks');
2327
+ const commandsDir = path.join(HOME, '.claude', 'commands');
2328
+
2329
+ // Ensure target dirs exist
2330
+ if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
2331
+ if (!fs.existsSync(commandsDir)) fs.mkdirSync(commandsDir, { recursive: true });
2332
+
2333
+ let updated = 0;
2334
+ let skipped = 0;
2335
+
2336
+ // Hook files to sync
2337
+ const hookFiles = ['check-global-state.sh', 'session-end.sh'];
2338
+ const sourceHooksDir = path.join(specDir, 'hooks');
2339
+
2340
+ for (const file of hookFiles) {
2341
+ const src = path.join(sourceHooksDir, file);
2342
+ const dest = path.join(hooksDir, file);
2343
+ if (!fs.existsSync(src)) continue;
2344
+
2345
+ const srcContent = fs.readFileSync(src, 'utf8');
2346
+ const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
2347
+
2348
+ if (srcContent === destContent) {
2349
+ skipped++;
2350
+ continue;
2351
+ }
2352
+
2353
+ fs.writeFileSync(dest, srcContent);
2354
+ fs.chmodSync(dest, 0o755);
2355
+ console.log(` Updated: ${file}`);
2356
+ updated++;
2357
+ }
2358
+
2359
+ // Command files to sync
2360
+ const sourceCommandsDir = path.join(specDir, 'commands');
2361
+ if (fs.existsSync(sourceCommandsDir)) {
2362
+ const cmdFiles = fs.readdirSync(sourceCommandsDir).filter(f => f.endsWith('.md'));
2363
+ for (const file of cmdFiles) {
2364
+ const src = path.join(sourceCommandsDir, file);
2365
+ const dest = path.join(commandsDir, file);
2366
+
2367
+ const srcContent = fs.readFileSync(src, 'utf8');
2368
+ const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
2369
+
2370
+ if (srcContent === destContent) {
2371
+ skipped++;
2372
+ continue;
2373
+ }
2374
+
2375
+ fs.writeFileSync(dest, srcContent);
2376
+ console.log(` Updated: ${file}`);
2377
+ updated++;
2378
+ }
2379
+ }
2380
+
2381
+ // Write version marker
2382
+ const versionFile = path.join(HOME, '.claude', 'team-context', '.wayfind-version');
2383
+ try {
2384
+ const pkg = require(path.join(ROOT, 'package.json'));
2385
+ const versionDir = path.dirname(versionFile);
2386
+ if (!fs.existsSync(versionDir)) fs.mkdirSync(versionDir, { recursive: true });
2387
+ fs.writeFileSync(versionFile, pkg.version);
2388
+ } catch { /* ignore */ }
2389
+
2390
+ if (updated > 0) {
2391
+ console.log(`\n ${updated} file(s) updated, ${skipped} already current.`);
2392
+ } else {
2393
+ console.log(' Everything up to date.');
2394
+ }
2395
+ }
2396
+
2397
+ // ── Migrate to plugin ───────────────────────────────────────────────────────
2398
+
2399
+ function runMigrateToPlugin(args) {
2400
+ const dryRun = args.includes('--dry-run');
2401
+ const settingsPath = path.join(HOME, '.claude', 'settings.json');
2402
+ const hooksDir = path.join(HOME, '.claude', 'hooks');
2403
+ const commandsDir = path.join(HOME, '.claude', 'commands');
2404
+
2405
+ console.log('Wayfind — Migrate to Plugin');
2406
+ console.log('===========================\n');
2407
+
2408
+ if (dryRun) console.log('(dry run — no changes will be made)\n');
2409
+
2410
+ let changes = 0;
2411
+
2412
+ // Step 1: Remove hook entries from settings.json
2413
+ if (fs.existsSync(settingsPath)) {
2414
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
2415
+ const hooks = settings.hooks || {};
2416
+ let hooksModified = false;
2417
+
2418
+ // Remove SessionStart hooks that reference check-global-state
2419
+ if (hooks.SessionStart) {
2420
+ const before = hooks.SessionStart.length;
2421
+ hooks.SessionStart = hooks.SessionStart.filter(group => {
2422
+ const cmds = (group.hooks || []);
2423
+ return !cmds.some(h => (h.command || '').includes('check-global-state'));
2424
+ });
2425
+ if (hooks.SessionStart.length === 0) delete hooks.SessionStart;
2426
+ if ((hooks.SessionStart || []).length !== before) hooksModified = true;
2427
+ }
2428
+
2429
+ // Remove Stop hooks that reference session-end.sh or wayfind reindex
2430
+ if (hooks.Stop) {
2431
+ const before = hooks.Stop.length;
2432
+ hooks.Stop = hooks.Stop.filter(group => {
2433
+ const cmds = (group.hooks || []);
2434
+ return !cmds.some(h => {
2435
+ const cmd = h.command || '';
2436
+ return cmd.includes('session-end.sh') || cmd.includes('wayfind reindex') || cmd.includes('team-context.js reindex');
2437
+ });
2438
+ });
2439
+ if (hooks.Stop.length === 0) delete hooks.Stop;
2440
+ if ((hooks.Stop || []).length !== before) hooksModified = true;
2441
+ }
2442
+
2443
+ if (hooksModified) {
2444
+ settings.hooks = hooks;
2445
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
2446
+ if (!dryRun) {
2447
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
2448
+ }
2449
+ console.log(` ${dryRun ? 'Would remove' : 'Removed'} Wayfind hook entries from ~/.claude/settings.json`);
2450
+ changes++;
2451
+ } else {
2452
+ console.log(' No Wayfind hook entries in settings.json (already clean)');
2453
+ }
2454
+ } else {
2455
+ console.log(' No ~/.claude/settings.json found');
2456
+ }
2457
+
2458
+ // Step 2: Remove old hook scripts
2459
+ const oldHookFiles = ['check-global-state.sh', 'session-end.sh'];
2460
+ for (const file of oldHookFiles) {
2461
+ const hookPath = path.join(hooksDir, file);
2462
+ if (fs.existsSync(hookPath)) {
2463
+ if (!dryRun) fs.unlinkSync(hookPath);
2464
+ console.log(` ${dryRun ? 'Would remove' : 'Removed'} ~/.claude/hooks/${file}`);
2465
+ changes++;
2466
+ }
2467
+ }
2468
+
2469
+ // Step 3: Remove old command files (plugin skills replace these)
2470
+ const oldCommandFiles = ['init-memory.md', 'init-team.md', 'doctor.md', 'journal.md', 'standup.md', 'review-prs.md'];
2471
+ for (const file of oldCommandFiles) {
2472
+ const cmdPath = path.join(commandsDir, file);
2473
+ if (fs.existsSync(cmdPath)) {
2474
+ if (!dryRun) fs.unlinkSync(cmdPath);
2475
+ console.log(` ${dryRun ? 'Would remove' : 'Removed'} ~/.claude/commands/${file}`);
2476
+ changes++;
2477
+ }
2478
+ }
2479
+
2480
+ // Step 4: Summary
2481
+ console.log('');
2482
+ if (changes === 0) {
2483
+ console.log('Nothing to migrate — already clean.');
2484
+ } else if (dryRun) {
2485
+ console.log(`Would make ${changes} change(s). Run without --dry-run to apply.`);
2486
+ } else {
2487
+ console.log(`Done — ${changes} change(s) applied.`);
2488
+ console.log('');
2489
+ console.log('The Wayfind plugin now handles hooks and skills.');
2490
+ console.log('Verify with: /wayfind:doctor');
2491
+ console.log('');
2492
+ console.log('Your old /standup, /doctor, etc. are now /wayfind:standup, /wayfind:doctor, etc.');
2493
+ console.log('The CLI (wayfind digest, wayfind reindex, etc.) still works — only hooks and skills moved.');
2494
+ }
2495
+ }
2496
+
2064
2497
  // ── Status command ──────────────────────────────────────────────────────────
2065
2498
 
2066
2499
  function runStatus(args) {
@@ -3896,6 +4329,14 @@ const COMMANDS = {
3896
4329
  desc: 'Check if installed version meets team minimum (used by hooks)',
3897
4330
  run: () => runCheckVersion(),
3898
4331
  },
4332
+ update: {
4333
+ desc: 'Re-sync hooks and commands from the installed Wayfind package',
4334
+ run: () => runUpdate(),
4335
+ },
4336
+ 'migrate-to-plugin': {
4337
+ desc: 'Remove old hooks/commands — let the Claude Code plugin handle them',
4338
+ run: (args) => runMigrateToPlugin(args),
4339
+ },
3899
4340
  doctor: {
3900
4341
  desc: 'Check your Wayfind installation for issues',
3901
4342
  run: (args) => {
@@ -3929,6 +4370,10 @@ const COMMANDS = {
3929
4370
  desc: 'Show cross-project status (or rebuild Active Projects table)',
3930
4371
  run: (args) => runStatus(args),
3931
4372
  },
4373
+ standup: {
4374
+ desc: 'Show a daily standup summary (last session, plan, blockers)',
4375
+ run: (args) => runStandup(args),
4376
+ },
3932
4377
  signals: {
3933
4378
  desc: 'Show configured signal channels and last pull times',
3934
4379
  run: () => runSignals(),
@@ -4002,8 +4447,8 @@ const COMMANDS = {
4002
4447
 
4003
4448
  // Files and directories to sync
4004
4449
  const syncItems = [
4005
- 'bin/', 'templates/', 'specializations/', 'tests/', 'simulation/',
4006
- 'backup/', '.github/', 'Dockerfile', 'package.json', 'setup.sh',
4450
+ 'bin/', 'templates/', 'specializations/', 'plugin/', 'tests/', 'simulation/',
4451
+ 'backup/', '.github/', 'Dockerfile', 'package.json', 'marketplace.json', 'setup.sh',
4007
4452
  'install.sh', 'uninstall.sh', 'doctor.sh', 'journal-summary.sh',
4008
4453
  'BOOTSTRAP_PROMPT.md', '.gitattributes', '.gitignore', 'VERSIONS.md',
4009
4454
  ];
@@ -4237,6 +4682,13 @@ function showHelp() {
4237
4682
  console.log(' wayfind status --write Rebuild Active Projects in global-state.md');
4238
4683
  console.log(' wayfind status --json Machine-readable output');
4239
4684
  console.log(' wayfind status --quiet Suppress output (for hooks)');
4685
+ console.log(' wayfind standup Daily standup for current repo (last session, plan, blockers)');
4686
+ console.log(' wayfind standup --all Daily standup across all repos');
4687
+ console.log('');
4688
+ console.log('Maintenance:');
4689
+ console.log(' wayfind update Re-sync hooks and commands from package');
4690
+ console.log(' wayfind migrate-to-plugin Remove old hooks/commands — let the plugin handle them');
4691
+ console.log(' wayfind doctor Check installation health');
4240
4692
  console.log('');
4241
4693
  console.log('Publishing:');
4242
4694
  console.log(' wayfind sync-public Sync code to usewayfind/wayfind (triggers npm + Docker publish)');
package/doctor.sh CHANGED
@@ -42,6 +42,24 @@ check_hook_registered() {
42
42
  ISSUES=$((ISSUES + 1))
43
43
  fi
44
44
 
45
+ # Check if installed hooks are stale vs package source
46
+ local SCRIPT_DIR
47
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
48
+ local SOURCE_HOOKS_DIR="$SCRIPT_DIR/specializations/claude-code/hooks"
49
+ local INSTALLED_HOOKS_DIR="$HOME/.claude/hooks"
50
+ for hook_file in check-global-state.sh session-end.sh; do
51
+ local src="$SOURCE_HOOKS_DIR/$hook_file"
52
+ local dest="$INSTALLED_HOOKS_DIR/$hook_file"
53
+ if [ -f "$src" ] && [ -f "$dest" ]; then
54
+ if ! diff -q "$src" "$dest" >/dev/null 2>&1; then
55
+ warn "$hook_file is out of date — run 'wayfind update' to sync"
56
+ ISSUES=$((ISSUES + 1))
57
+ else
58
+ [ "$VERBOSE" = true ] && ok "$hook_file is current"
59
+ fi
60
+ fi
61
+ done
62
+
45
63
  # Validate hook structure — correct format: {matcher: str, hooks: [{type, command}]}
46
64
  if command -v python3 &>/dev/null; then
47
65
  local STRUCT_RESULT
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "usewayfind",
3
+ "owner": {
4
+ "name": "Wayfind",
5
+ "url": "https://github.com/usewayfind"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "wayfind",
10
+ "source": "./plugin",
11
+ "description": "Team decision trail — session memory, decision journals, and team digests for AI-assisted development."
12
+ }
13
+ ]
14
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wayfind",
3
- "version": "2.0.13",
3
+ "version": "2.0.14",
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"
@@ -27,6 +27,8 @@
27
27
  },
28
28
  "files": [
29
29
  "bin/",
30
+ "plugin/",
31
+ "marketplace.json",
30
32
  "setup.sh",
31
33
  "install.sh",
32
34
  "uninstall.sh",
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "wayfind",
3
+ "version": "2.0.14",
4
+ "description": "Team decision trail for AI-assisted development. Session memory, decision journals, and team digests.",
5
+ "author": {
6
+ "name": "Wayfind",
7
+ "url": "https://github.com/usewayfind"
8
+ },
9
+ "homepage": "https://github.com/usewayfind/wayfind",
10
+ "repository": "https://github.com/usewayfind/wayfind",
11
+ "license": "Apache-2.0",
12
+ "keywords": ["team", "context", "memory", "decisions", "digest", "journal", "session"],
13
+ "skills": "./skills/",
14
+ "hooks": "./hooks/hooks.json"
15
+ }
@@ -0,0 +1,40 @@
1
+ # Wayfind — Claude Code Plugin
2
+
3
+ Team decision trail for AI-assisted development. Session memory, decision journals, and team digests.
4
+
5
+ ## Install
6
+
7
+ From the GitHub marketplace:
8
+ ```
9
+ /plugin marketplace add usewayfind/wayfind
10
+ /plugin install wayfind@usewayfind
11
+ ```
12
+
13
+ Or from the official Anthropic marketplace (once approved):
14
+ ```
15
+ /plugin install wayfind
16
+ ```
17
+
18
+ ## What you get
19
+
20
+ ### Tier 1: Plugin only (no npm install)
21
+
22
+ - **Session memory protocol** — loads team and personal state files at session start, saves at session end
23
+ - **Slash commands** — `/wayfind:init-memory`, `/wayfind:init-team`, `/wayfind:doctor`, `/wayfind:journal`, `/wayfind:standup`, `/wayfind:review-prs`
24
+ - **Hooks** — auto-rebuild project index on session start, auto-extract decisions on session end
25
+ - **Drift detection** — flags when work diverges from the stated session goal
26
+
27
+ ### Tier 2: Plugin + npm CLI (`npm i -g wayfind`)
28
+
29
+ Everything in Tier 1, plus:
30
+
31
+ - **Decision indexing** — `wayfind reindex` extracts decisions from conversation transcripts
32
+ - **Journal sync** — `wayfind journal sync` pushes local journals to the team context repo
33
+ - **Weekly digests** — `wayfind digest` generates multi-persona summaries (engineering, product, strategy)
34
+ - **Team coordination** — `wayfind team create/join`, shared context distribution
35
+ - **Slack + Notion delivery** — digests post to Slack channels and Notion pages via GitHub Actions
36
+ - **Bot mode** — `wayfind bot` runs a Slack bot for on-demand queries
37
+
38
+ ## Documentation
39
+
40
+ Full docs: https://github.com/usewayfind/wayfind
@@ -0,0 +1,28 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "matcher": "",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-start.sh",
10
+ "timeout": 15
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "Stop": [
16
+ {
17
+ "matcher": "",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/session-end.sh",
22
+ "timeout": 30
23
+ }
24
+ ]
25
+ }
26
+ ]
27
+ }
28
+ }